Руководство для разработчика битрикс

Информацию по работе с «1С-Битрикс: Управление сайтом» можно найти в учебных курсах и в документации. Учебные курсы предназначены для освоения методов работы в программном продукте, а документация — для освоения принципов кастомизации CMS.

При работе с «1С-Битрикс: Управление сайтом» проблемы возникают в виде конкретных практических задач. Мы собрали в профильные темы разные страницы учебных курсов, что бы облегчить вам поиск ответов на вопросы.

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

Закладка Администраторам предназначена для тех, кто будет администрировать «1С-Битрикс: Управление сайтом».

Закладка Разработчикам предназначена для разработчиков проектов на основе «1С-Битрикс: Управление сайтом».

  • Контент-менеджерам
  • Администраторам
  • Разработчикам

Контент-менеджерам

Задачи по работе с системой Информация
Перед началом работы. Работа над контентом невозможна без авторизации. Как авторизоваться на сайте? Что такое одноразовые пароли и как с ними работать? Авторизация
Интерфейс программы. Работать с любой программой невозможно, если не понятно назначение той или иной кнопки или команды. Как работать с новым интерфейсом Эрмитаж? Как работать с интерфейсом в старых версиях программы? Элементы управления
Как работать с информацией. Информация бывает разной: статической и динамической, текстовой и графической. Как управлять той или иной информацией? Как размещать ее на страницах сайта? Как создать саму страницу или раздел и задать их свойства? Информация на сайте и работа с ней
Структура сайта. Любой сайт состоит из разделов и страниц. Как легко и просто управлять структурой сайта в текущей работе? Приемы работы. Медиабиблиотека, как хранилище видео, фото и аудиофайлов. Управление структурой
Визуальный редактор — это основной инструмент контент-менеджера при работе с сайтом. Все о работе с редактором: как вставлять текст, форматировать и редактировать его, работа с графикой и таблицами. Сниппеты и настройка редактора «под себя». Видео по работе с визредактором. Визуальный редактор
Информационные блоки — основное место хранения динамически выводимой информации. Необходимый уровень знаний для работы с этим инструментом системы. Информационные блоки
Компоненты. Любая динамическая информация выводится не сама по себе, а с помощью специальных программных инструментов — компонентов. Основные моменты по работе с этими инструментами: как добавить компонент на страницу, как настроить, как работать с ним. Компоненты
Практика. Теория — это хорошо. Но вот попробовать «ручками» — это всегда лучше. Помогут освоить работу многочисленные примеры с описанием как выполнять типовые, самые частые задачи, стоящие перед контент-менеджером. Примеры работы с системой
Типичные ошибки. Помочь в освоении системы поможет и список наиболее частых ошибок и способов их решения. Типичные ошибки при работе с системой

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

Администраторам

Администраторские задачи Информация
Перед началом работы. Необходимый минимум сведений о системе: как работает, термины, структура. Основные сведения
Установка и настройка. Как правильно установить, обновить или перенести сайт. Возможные ошибки этих процессов. Виртуальная машина для «1С-Битрикс: Управление сайтом». Установка и настройка
Основы администрирования. Основные принципы администрирования в системе: управление сайтами, пользователями, доступом к модулям и информации, настройка интерфейса, рабочие инструменты администратора, обеспечение безопасности. Основы администрирования
Поиск. Все о поиске информации на сайте. Настройка модуля Поиск, статистика, облако тегов и другие инструменты. Типичные ошибки. Поиск
Информационные блоки. Подробно о самом важном модуле системы. Экспорт и импорт, примеры работы. Информационные блоки
Примеры операций. Несколько практических примеров по работе с системой. Примеры операций
Модули системы. В «1С-Битрикс: Управление сайтом» более 40 различных модулей, позволяющих выполнять самые разные задачи: от проведения опросов до AD/LDAP интеграции и облачных хранилищ. Детальное описание настройки и работы с модулями системы некоммерческой направленности. Администратор. Модули
Интернет-торговля. Организация виртуального магазина и задачи, связанные с этим: работа с валютами, интеграция с 1С, торговый каталог и настройка интернет-магазина. Примеры работы. Администратор. Бизнес

Разработчикам

Документация для разработчиков — это описание API системы. Документация для пользователей — это описание компонентов и настроек системы.

Документация доступна как в он-лайн, так и в виде файла в формате chm. Рекомендуется пользоваться он-лайн версией, как более актуальной. Файлы формата chm обновляются периодически, в них может отсутствовать информация по последним изменениям в справочной системе.

Внимание! Если вы не видите содержимое файла формата .chm, то причина — настройки безопасности операционной системы. В свойствах файла нужно снять блокировку файла от просмотра. Подробнее читайте в FAQ.

Системы помощи .chm
Документация для разработчиков Онлайн-версия bsm_api.chm
Версия документа
от 04.05.2022
Документация по API D7 Онлайн-версия api_d7.chm
Версия документа
от 04.05.2022
Документация для пользователей Онлайн-версия bsm_user.chm
Версия документа
от 04.05.2021
REST API для разработчиков Онлайн-версия b24_rest.chm
Версия документа
от 04.05.2022

Документация — это справочная информация. Начинающему разработчику её не достаточно для работы с системой. В освоении принципов программирования в Bitrix Framework вам поможет специальный курс:

Разработчик Bitrix Framework Разделы
Перед началом работы. Что такое Bitrix Framework и чем эта платформа отличается от других систем. Сообщество разработчиков. Что такое Bitrix Framework?
Золотые правила работы с Bitrix Framework. Универсальные правила работы с продуктами Bitrix Framework на каком бы уровне подготовки вы не находились. Золотые правила
Как создать простой сайт. Чтобы уметь создавать сайты на начальном уровне необходимо освоить систему на уровне работы штатным функционалом. Практически без кодирования. Кодирование нужно только при создании шаблона и это кодирование — знание HTML. Junior
Средний уровень подготовки. Вы уже можете работать со штатным функционалом. Теперь надо овладеть API и другим возможностями системы: настройка системы, ORM, SEF, кастомизация компонентов, агенты и события и многое другое. Middle
Senior — высший уровень квалификации. Программист этого уровня должен уметь читать исходный код, уметь работать с Базой данных, разбираться в вопросах производительности и безопасности, уметь кастомизировать административный раздел и создавать новые компоненты и модули.

Senior

Содержание

  • Как отформатирован текст в курсе
  • Что такое Bitrix Framework?
    • Как изучать Bitrix Framework?
    • Где брать информацию?
    • Сообщество разработчиков
    • Для тех, кто переходит на Bitrix Framework с других платформ
    • Глоссарий
  • Золотые правила работы с Bitrix Framework
  • Junior, Как создать простой сайт
    • Типовой порядок действий
      • Сайт в понятии Bitrix Framework
      • Что такое страница
      • Язык и языковые файлы
      • Выбор кодировки сайта
      • Техническое задание на сайт
    • Базовый шаблон
      • Что такое Шаблон сайта
      • Создание и управление шаблоном
      • Разработка шаблона дизайна
      • Включаемые области
        • Пример. Использование Включаемых областей
      • Цепочка навигации
        • Управление навигационной цепочкой
        • Примеры работы с навигационной цепочкой
      • Меню сайта
        • Построение и показ меню
        • Шаблоны меню
        • Управление меню
        • Примеры создания меню
        • Примеры решения частных задач в меню
      • Рекламные области
        • Типы рекламы
        • Механизм управления показом с помощью ключевых слов
      • Использование прав доступа
      • Разные языки сайта
        • Механизм реализации
        • Загрузка и выгрузка локализации
        • Изменение фраз в компонентах и модулях
      • Настройка дополнительных элементов
      • Примеры работы и решения проблем
      • Как вывести произвольный контент в шаблоне сайта и компонента
      • Разработка шаблонов страниц
      • Руководство по оформлению HTML/CSS кода
        • Общие правила оформления кода
        • Правила оформления HTML
        • Правила форматирования HTML
        • Правила оформления CSS
        • Правила форматирования CSS
        • Тестирование верстки (чеклист)
        • Особенности интеграции с Битрикс
        • Верстка для мобильных устройств
        • Приёмы верстки
      • Простейший пример внедрения дизайна с табличной вёрсткой
        • Создание шаблона
          • Добавление кода тестового дизайна в шаблон
          • Добавление графики и стилей
        • Интеграция дизайна в систему
          • #WORK_AREA# – Рабочая область
          • Авторизация
            • Кастомизация шаблона компонента
          • Меню сайта
            • Левое меню
            • Нижнее горизонтальное меню
            • Верхнее частичное меню
          • Цепочка навигации
          • Компонент Новые сообщения блогов
          • Счетчик посещений
          • Баннер
          • Включаемые области
        • Work Area и разные шаблоны
      • Простейший пример внедрения дизайна с блочной вёрсткой (div)
        • Подготовка к работе
        • Разделение шаблона на header, footer и work_area
        • Шаблон сайта (создание папки шаблона)
        • Шаблон сайта (настройка и подключение)
        • Список новостей: кастомизация шаблона компонента
        • Детальный просмотр новости: шаблон страницы и компонента
        • Меню: кастомизация шаблона компонента
    • Управление служебными данными шаблона
      • Управление кодировкой страниц
      • Управление заголовком документа
        • Примеры работы
      • Управление стилями
        • Механизм реализации
        • Работа со стилями в визуальном HTML-редакторе
        • Пример таблицы стилей для шаблона сайта
      • Управление значениями метаданных
    • Интеграция компонентов
      • Что такое компонент
      • Простые и комплексные компоненты
      • Структура компонента
      • Структура комплексного компонента
      • Размещение в системе и подключение компонента
      • Описание компонента
      • Параметры компонента
      • Шаблоны компонента
      • Типичные ошибки
    • Кастомизация шаблонов компонентов
      • Кастомизация шаблона
      • Модификация шаблона простого компонента в составе комплексного
      • Пример. Вывод голосования
      • Пример. Добавление типа отсутствия
      • Пример. Внешние файлы css
      • Пример. Редактирование шаблона меню
      • Пример. Редактирование шаблонов «Корзина» и «Оформление заказа»
    • Создание структуры сайта
      • Структура файлов
    • Настройка инфоблоков
      • Инфоблоки
      • Работа с инфоблоками штатными средствами
    • Кеширование
      • Кеширование компонентов (Автокеширование)
    • Добавление произвольного PHP кода
  • Middle, Средний уровень подготовки
    • Обновление, бекап и восстановление
    • Ядро D7
      • Настройка параметров ядра
        • Подключения к Redis, Memcache
        • Локальные настройки SMTP-сервера
      • Пространства имён
      • Исключения
      • Приложения и контекст
      • Ошибки в D7
      • API
        • Если нет описания API
        • Практика. Некоторые классы
        • Практика. Работа с D7 на примере местоположений
      • Сервис Локатор
      • Контроллеры
        • Контроллер
        • Контроллеры и компонент
        • Практика. Советы
        • Практика. Взаимодействие с контроллерами из Javascript
        • Практика. Постраничная навигация
        • Практика. Интеграция с модулем REST
        • Практика. Внедрение зависимостей
      • Роутинг
        • Маршруты
        • Группы
        • Генерация ссылок
      • Логгеры
      • Вложенные транзакции
    • ORM
      • Концепция, описание сущности
      • Операции с сущностями
      • Объекты
        • Класс объекта
        • Именованные методы
        • Приведение типов
        • Чтение (get, require, remindActual, primary, collectValues, runtime)
        • Запись (set, reset, unset)
        • Проверки (isFilled, isChanged, has)
        • Состояние объекта
        • Создание и редактирование (save, new)
        • Удаление (delete)
        • Восстановление (wakeUp)
        • Заполнение (fill)
        • Отношения (addTo, removeFrom, removeAll)
        • ArrayAccess
      • Коллекции
        • Класс коллекции
        • Доступ к элементам коллекции
        • Групповые действия
        • Восстановление коллекции
      • Отношения
        • 1:N
        • 1:1
        • N:M
      • Аннотации классов
      • Обратная совместимость
      • Выборка данных
        • getList
        • Короткие вызовы
        • Объект Query
        • Предустановленные выборки
        • Выбор данных из хранимых процедур вместо таблиц
        • Выборки в отношениях 1:N и N:M
      • Взаимосвязи между сущностями (устаревший вариант)
      • Фильтр ORM
      • Автоматическая генерация ORM-классов
      • Использование ORM или почему поля из SELECT и ORDER BY автоматически попадают в GROUP BY
      • Постраничная навигация
      • Интеграция ORM в информационных блоках
        • Концепция и архитектура
        • Чтение и запись
        • События и кастомные типы свойств
        • Наследование
    • Работа с компонентами
      • Переменные в компоненте 2.0
      • Классы компонентов
      • Файл result_modifier.php
        • Примеры решения задач
        • Пример. Выборка из Информационного блока
        • Модификация шаблона или создание result_modifier?
      • Файл component_epilog.php
        • Пример. Компонент в элементе ИБ
        • Пример. Исключение шаблона компонента из кэша
      • Кеширование компонентов
        • Сache Dependencies (тегированный кеш)
        • Пример. Добавление своего тега
      • Пример. Сортировка в компонентах
      • Пример. Использование событий
      • Кастомизация компонентов
        • Простой пример кастомизации компонента
        • Модификация простого компонента в составе сложного
        • Тип параметров CUSTOM
        • Ещё пара примеров работы
    • Инфоблоки, работа с ними
      • Инфоблоки 2.0
      • Инфоблоки в Документообороте
      • Фильтрация
        • Фильтрация элементов инфоблока без компонента фильтра
      • Вычисляемые свойства SEO
      • Фасетный поиск
      • План действий при проблемах
      • Highload-блоки
        • Архитектура модуля
      • Практика. Работа с элементами, разделами и свойствами
        • Работа с пользовательскими свойствами инфоблоков
        • Примеры работы с множественными свойствами
        • Копирование значений полей элементов в свойства
        • Получение суммы значений полей связанных инфоблоков
        • Вывод свойств элемента инфоблока
      • Практика. Копирование инфоблока
      • Некоторые ошибки при работе с инфоблоками
      • Практика. Ограничение области поиска разделом
    • Программирование в Bitrix Framework
      • Командная PHP-строка
      • Организация разработки
        • Система контроля версий
        • Папка /local
        • Composer и Bitrix Framework
        • Bitrix CLI
      • Немного теории PHP
        • Замечания по $arParams и $arResult
        • HTTP POST запросы
        • Правила написания кода
      • Архитектура продукта
      • Права доступа
      • Файлы и База данных
        • Работа с базами данных
      • Отложенные функции
      • Файл init.php
      • Языковые файлы
        • Работа с языковыми файлами
      • Гаджеты и их создание
      • JS-библиотека
        • Подключение JS-кода
        • JS-класс к шаблону компонента
        • JS-расширение медиаплеера
        • Примеры кастомизации публичной части
        • Форматирование дат в Javascript
        • Типовые ошибки и советы
        • Расширения (extensions)
        • Инструмент @bitrix/cli
        • @bitrix/cli: сборка проекта с NPM
        • Вложенные библиотеки
      • Использование ES6
      • Работа с магазином
        • Товары и CIBlockElement::GetList
        • Пользовательские типы свойств заказа
        • Пользовательские ограничения
        • Пользовательские правила компаний
        • Кастомизация типов дополнительных услуг
        • Кастомизация служб доставок
        • Кастомизация платежных систем
        • Кастомизация шаблона платежной системы
        • Собственный обработчик онлайн-кассы
        • Принцип печати чеков через платёжную систему
        • Работа с REST служб доставки
          • Процесс создания и настройки службы доставки
          • Процесс использования службы доставки в сценариях центра продаж
        • Примеры
          • Пример создания собственной службы доставки
          • Пример создания заказа через API
          • Пример изменения заказа через API
          • Пример разделения оплаты на 2 части
        • Видео
      • Работа с модулем Push & Pull
        • Оптимизация количества запросов к серверу
        • Push & Pull для гостей
        • Подписка на события модуля
    • Агенты и их использование
      • Примеры агентов
      • Запуск агентов из cron
      • Ещё об агентах
    • События
      • События в D7
      • Использование событий
        • Как написать обработчик события
        • Добавление закладки в социальную сеть
        • Учет регистрации нового пользователя в статистике
        • Зацикливание обработчиков событий
        • Совместная работа пары событий
        • Данные до и после update
        • Дополнительно
        • «Ленивые» параметры в событиях
    • Настройка ЧПУ
      • Комплексный компонент и SEF режим
      • Примеры
    • Модуль Поиск
    • Пользовательские поля
      • Примеры работы
      • Добавление, редактирование, удаление пользовательских свойств и их значений
      • Поля к нештатным объектам и новые объекты
    • Тестирование проектов
      • Монитор качества
        • Сдача проекта
        • Способы использования
        • Модификация тестов
      • Несколько советов
      • Видео
  • Senior, выше некуда
    • Производительность
      • Особенности веб-программирования
      • Кеширование при проектировании сайта
        • Проблемы при кешировании меню
        • Примеры. Кешируем правильно
      • Оптимизация выборки дополнительных данных
      • Выборка и хранение в кеше только нужных данных
      • Оптимизация запросов к БД
      • Как сделать сайт быстрым
      • Если долго сохраняется элемент в административном разделе
      • Как снизить нагрузку с помощью API
      • Примеры оптимизации JS кода
      • Сессии и cookie
        • Переменная $_SESSION
        • Сессионный кеш (Local Session)
        • Сессия разделенный режим (hot&cold)
        • Настройка хранения данных сессии
        • Работа с сессиями
        • Шифрованные cookies
      • Использование постранички для массивов данных
    • Работа с БД
      • Пример работы с БД
      • Миграция на MySQL
      • Смена кодировки сайта
    • Кастомизация Административной части
      • Интерфейс «Эрмитаж» с точки зрения разработчика
        • Добавление кнопок на панель управления
        • Добавление контекстного меню
        • Toolbar компонента
        • Страница со списком элементов
        • Контекстное меню элементов списка
        • Административные страницы в публичке
      • Пользовательские формы редактирования элементов
      • Кастомизация административной формы заказа
    • Создание компонентов
      • Дополнительные методы
      • Переопределение входящих переменных
      • Пользовательские движки шаблонизации
      • Разработка верстки шаблона компонента
      • Способы передачи данных между компонентами
      • Простой пример создания компонента
      • Пример создания компонента
      • Компонент интеграции визуального редактора
      • Кеширование в собственных компонентах
    • Внешняя авторизация
    • Безопасность
      • Санитайзер
      • Защита от фреймов
      • Вирус на сайте
        • Чистка сайта от вирусов
        • Защита от троянов
    • Модули
      • Модули в D7
      • Структура файлов
      • Описание и параметры
      • Административные скрипты
      • Административное меню
      • Взаимодействие модулей
      • Установка и удаление
      • Кастомизация и создание модулей
        • Пример изменения работы модуля
    • BigData (сервис персонализации)
      • Сбор данных
      • Логика работы сервиса
      • API запросов
    • Веб-сервисы
      • Пример создания windows-приложения для добавления новостей
    • Vue.js и Bitrix Framework
    • Мобильное приложение BitrixMobile

Как отформатирован текст в курсе

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

Вкладки

Текст урока разбивается на тематические вкладки (табы). Сделано это с целью избавить читателя от прокрутки экрана или минимизировать её. Если вам неудобно пользоваться вкладками, то их [dw]можно отключить[/dw][di][/di], тогда весь текст урока будет выводиться в виде единого массива.

Внимание! При использовании вкладок поиск в браузере ищет только по открытой вкладке, а не по всему уроку.

Форматирование текста

Жирный шрифт используется для выделения важных в смысловом значении слов, терминов, фраз и предложений. То есть того, что особенно важно в контексте описываемого функционала:
  • Ключевое смысловое слово, фраза.
  • Термины, названия элементов интерфейса: кнопки, вкладки, поля, горячие клавиши, формат или название файла.
  • Название курса, глав курса, разделов и страниц сайтов. (Как правило, название глав или курсов даются ссылкой, но иногда это бывает излишним).
Примеры использования жирного шрифта
Пример названия курса или главы Изучение курса позволяет освоить основные методы администрирования системы, а также пополнить знания по темам, изученным в курсе Контент-менеджер.
Пример названий элементов интерфейса: При этом у компонента Список новостей нужно настроить входные параметры так, чтобы он мог формировать ссылки на страницу детальной новости (с кодом новости). Для этого нужно в группе параметров Шаблоны ссылок в поле URL страницы детального просмотра задать путь к этой странице, а также название параметра, в котором будет передаваться код новости для показа.
Пример ключевой фразы, слова При регистрации сервера обязательно укажите Домен для NTLM авторизации. Он должен полностью (включая регистр) совпадать с названием домена.

и Курсив и подчёркивание используются для выделения:
  • Названия продуктов компании 1С-Битрикс, других программ и технологических платформ.
  • Названия методов, классов, функций и событий, если нет их описания в API доке. (Если описание есть, то название метода или класса оформляется ссылкой на страницу документации.)
  • Пространство имён в новом ядре D7.
  • Ключевое слово, фраза, термин, если использование жирного шрифта нежелательно по каким-то причинам.
Примеры использования курсива
Пример названия метода: Усовершенствованные методы буферизации в шаблоне позволяют более не использовать CBitrixComponentTemplate::EndViewTarget() ввиду того, что конец шаблона вызывает завершение буферизации автоматически. (Если страница метода в документации есть, то будет так: Перед подключением компонента ко всем значениям параметров применяется функция
[ds]htmlspecialcharsEx[/ds][di]Функция переводит текст в HTML-безопасный вид, заменяя специальные символы их визуальным HTML представлением.
Подробнее…[/di])
Пример названий продуктов и платформ: Чтобы научиться эффективно работать в Bitrix Framework, нужно не сравнивать то, что вы знаете по другим системам, а стараться понять, как то или иное реализуется в этой системе. В плане обучения «сравнительный» подход не работает. Просто отвлекитесь от старых знаний и изучите новую систему, используя только знания PHP и сайтостроения, а не сравнивая идеологии и технологии.
Пример пространства имён в D7: При необходимости модуль может организовывать подпространства внутри своего пространства имен. Например, BitrixMainIO, BitrixForumSomeNameSomeNameTwo.

Серый фон текста используется для выделения:

  • путей в рамках файловой системы.
  • атрибутов, тегов html, параметров функций, переменных, значения полей и настроек и листинга кода, который нет смысла помещать в стиль для листинга.
Примеры использования серого фона
Пример выделения путей в рамках файловой системы: Результаты кеширования сохраняются в виде файлов в каталоге /bitrix/cache/.
Пример выделения атрибутов, тегов и так далее: Если в компоненте написать код:
$arParams = & $arSomeArray;
то переменная $arParams будет отвязана от члена класса компонента и привязана к массиву $arSomeArray.

Синий курсив с > используется для подсветки путей в продукте. То есть пути выглядят так же, как они оформлены в административном отделе системы.

Примеры использования синего курсива
Пример оформления путей в административном отделе системы: Файлы кеша можно удалить в административной части на закладке Очистка файлов кеша страницы Настройки > Настройки продукта > Автокеширование.

Синий шрифт используется для подсветки УРЛ без создания собственно ссылки — псевдо УРЛ.

Примеры использования синего шрифта
Пример псевдо УРЛ: При помощи access_token приложение совершает запросы к REST-сервису до его истечения.

https://имя_портала.bitrix24.ru/rest/user.current?auth=код_авторизации

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

Примеры использования цветного шрифта
Цветной шрифт:
  • Настроить инфоблок на контроллере:

    ……..

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

    ……

Форматирование абзацев текста

Форматирование целых абзацев текста используется для выделения какой-либо информации с целью указать направленность этой информации.

Определения каких-либо сущностей, явлений, терминов и так далее выводятся в абзацах такого вида:

Статическая информация – это информация, которая редко изменяется с течением времени. Например, рекламные тексты, история компании, контактная информация и т.п. Статическая информация создается, редактируется, дополняется и удаляется пользователями, обладающими правом на редактирование страниц сайта. Такое редактирование производится, как правило, в визуальном редакторе непосредственно в тексте самой страницы.

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

Примечание. В ядре D7 настройки кеширования производятся в [ds]специальном файле[/ds][di]Настройки выполняются в файле /bitrix/.settings.php.
Подробнее…[/di]

Предупреждения — это информация, имеющая критическое значение для работы того или иного функционала продукта. Например:

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

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

Всплывающие подсказки

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

  • Нужно пояснить какой-то термин.
    Пример: Как правило, все сайты имеют возможность авторизации из [dw]публичного раздела[/dw][di]Публичный раздел — то, что видит посетитель сайта, но для контент-менеджера — это основное место работы . Подробнее…[/di].
  • Дать наглядное представление о команде интерфейса.
    Пример: При включённом режиме Правки откройте на
    [dw]редактирование элемент инфоблока[/dw][di][/di], например, новость.
  • Дать дополнительную, но не относящуюся напрямую к выполняемым работам, информацию.
    Пример: Этот инструмент создаст ссылку с одним единственным [dw]слешом[/dw][di]Слеш — косая черта, которая используется для разделения слов в адресе сайта[/di] после [dw]домена сайта[/dw][di]Домен сайта — адрес по которому открывается самая главная страница. Например: yandex.ru[/di].
  • Дать описание и ссылку на страницу компонента в пользовательской документации.
    Пример: Простую фотогалерею в публичном разделе выводит комплексный компонент [comp include_photogallery]Фотогалерея 2.0[/comp].

Внимание! Всплывающие подсказки не работают в файлах формата CHM и Epub. Интернет на мобильных устройствах на сегодняшний день — не проблема, используйте эти файлы документации только в крайнем случае.

Спойлеры, скрывающие текст

В спойлерах размещается информация, которая имеет вторичное значение, но может быть полезна «для общего развития». Для открытия спойлера надо просто кликнуть на «+» в спойлере:

Задание окна для открытия ссылки при редактировании в режиме кода (для ознакомления)

Чтобы избежать лишней прокрутки экрана, в спойлерах может быть размещена большая по объёму информация.

Видеоролики

Видеоролики обычно располагаются в начале соответствующего урока. Также вы можете следить за новыми роликами:

  • на YouTube канале;
  • на Rutube канале.

Что такое Bitrix Framework?

Цитатник веб-разработчиков.

Александр Сербул: БУС это не «компоненты перетаскивать и галочки в админке тыкать», а это — хардкорная разработка модулей, доработка ядра, жесткий ORM, последние новинки PHP.

Bitrix Framework — это созданная на основе PHP платформа для разработки веб-приложений. На этой платформе компанией «1C-Битрикс» созданы два популярных продукта: «1C-Битрикс: Управление сайтом» и «1С-Битрикс: Корпоративный портал».

Продукт «1C-Битрикс: Управление сайтом» представляет собой программное ядро для всестороннего управления веб-проектами любой сложности.

Продукт «1С-Битрикс: Корпоративный портал» — готовый продукт, позволяющий создать корпоративный портал компании, с возможностью доработки штатного функционала под потребности компании.

В отличие от Zend Framework при разворачивании Bitrix Framework мы получаем не только набор классов, но и развитый интерфейс администрирования.

В базовой поставке идёт большой набор компонентов, и именно он обеспечивает быстрое развёртывание и внедрение проектов.

Продукты на базе Bitrix Framework выходят в нескольких редакциях. Изучая систему и повторяя уроки необходимо быть уверенным, что ваша локальная установка имеет модуль, с которым вы экспериментируете. Помодульное сравнение редакций: для 1С-Битрикс: Управление сайтом, для 1С-Битрикс: Корпоративный портал.

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

Список ссылок по теме:

  • Продукты, созданные на Bitrix Framework
  • Решения на базе продуктов
  • Виртуальная лаборатория для изучения продуктов и решений Bitrix Framework
  • Маркетплейс сайт продуктов и решений от партнёров 1С-Битрикс.

Как изучать Bitrix Framework?

Как построить обучение?

Цитатник веб-разработчиков.

Виктор Векслер:

Мы выбрали подход кейсов в обучении, стажеры получают 5-10 кейсов в течение первого месяца, которыми и занимаются, при этом выделяется куратор, который общается, поддерживает и направляет стажера. При этом стажер учится на реальных задачах и не сильно мешает куратору.

Самое удобное, расписать заранее мини ТЗ и выдавать верстку для работы (в случае, если стажер — будущий программист), в случае верстальщика — шаблоны на верстку. При этом, после того как он сделает свою верстку, он должен получить профессиональную уже сделанную работу.

Любой соискатель при собеседовании получает тестовое задание, оно сложное (для начинающего). Он получает 2 psd шаблона, дистрибутив Битрикса с окружением и мануал по интеграции. Его задача разобраться и интегрировать данные шаблоны в Битрикс, если он не может этого делать, мы его не принимаем.

С первого дня, стажер получает первый кейс — это сайт турфирмы, основная задача здесь разобрать инфоблоки и немного познакомиться с Битриксом вообще. На данный проект в среднем уходит 1 неделя. Далее 2-ой кейс — это снять реальный сайт и сделать его полную копию на Битриксе (в среднем 3-4 дня). Далее пишет элементарный компонент — один день (может меньше). После, если у нас есть легкие проекты, он работает как подмастерье у программиста, если нет делает кейс интернет-магазина, если он осилил его, то он уже может переходить на сайты визитки.

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

Скачать кейсы

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

Идеальный вариант. Пошаговое изучение всех курсов в линейке сертификационных курсов от Контент-менеджера через курсы Администратор до курса Разработчик Bitrix Framework. С одновременным изучением API и пользовательской документации.

Реальный вариант. Как правило, — это неделя на изучение, и потом работа над новым проектом в рамках веб-студии или на фрилансе. В этом случае необходимо знать интерфейс программы (глава Элементы управления курса Контент-менеджер) и данный курс. При работе впоследствии с проектом — постоянное обращение к документации для разработчиков, пользовательской документации и другим учебным курсам.

Сколько времени уйдет?

Опыт подготовки разработчиков от одного из партнеров

У нас стажёры (типовой опыт: 5 лет института, возможно, фриланс и знание РНР) с обучением по курсам, внутренней документации и консультациями разработчика, примерно через месяц понимают как и что устроено. Общий курс обучения — порядка 2-3 месяцев.

То есть через 1,5 месяца стажёр сдаёт тестовое задание (собранный на типовых компонентах сайт), а через 3 уже вливается в команду.

Более короткие сроки обучения приводят к тому, что разработчики пишут крайне неоптимальные вещи с точки зрения Bitrix Framework или испытывают трудности при решении типовых задач. Студия выросла с 5 до 20 человек. Всех обучали сами.

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

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

Оффлайновое обучение

Компания «1С-Битрикс» организовала очное обучение, которое проводит сеть авторизованных учебных центров и центров сертификации. Такое обучение в целом ряде случаев предпочтительнее, чем обучение с помощью онлайн курсов.

Примечание: Bitrix Framework активно развивается. Его нельзя изучить один раз и жить на этом знании все оставшееся время. Надо постоянно быть в курсе событий. Все новинки документации (новые уроки и изменения в старых) можно отслеживать с помощью страницы Что нового? или канала в Телеграмме)

Внимание! Приведенные в курсе примеры работы с API могут устаревать в силу постоянного развития системы. Отдел документации отслеживает это развитие, но задержки с внесением изменений в курсы могут быть.

Где брать информацию?

Все продукты компании «1С-Битрикс» сопровождаются полной документацией, которая расположена на сайте в разделе Документация.

Документация

Цитатник веб-разработчиков.

Степан Овчинников: Битрикс огромен. Перечень вопросов, которые человек задает сразу, на первом проекте, относительно мал. Но вот объем знаний, нужных на втором этапе погружения, когда делается нетривиальное, действительно очень велик. Его нельзя наработать быстро.

Документация для продукта «1С-Битрикс: Управление сайтом» включает в себя:

  • Онлайн документация для пользователей

    Нажмите на рисунок, чтобы увеличить

    Справочная документация представляет собой описание интерфейса системы управления сайтом, ее основных модулей и наиболее типичных операций. Также в ней содержится полное описание прикладного программного интерфейса.

    Рекомендуется таким группам пользователей, как Контент-менеджер и Администратор.

  • Онлайн документация для разработчиков

    Документация, предназначенная для технических специалистов со знанием PHP и HTML. В ней содержатся сведения о технологиях и основных принципах, заложенных в систему, описание классов и функций.

    Рекомендуется таким группам пользователей, как Администратор и Разработчик.

  • Онлайн документация для разработчиков по Rest_API

    Документация, предназначенная для технических специалистов со знанием PHP и HTML. В ней содержатся сведения о технологиях и основных принципах, заложенных в систему, описание классов и функций, относящихся к сервису Битрикс24.

    Рекомендуется таким группам пользователей, как Администратор и Разработчик.

При работе с Bitrix Framework очень большое значение имеет описание API. К сожалению составление описаний API нового функционала никогда не выходит одновременно с функционалом. В этом случае рекомендуем воспользоваться уроком Если нет описания API.

Оффлайновые файлы

Документация в файлах формата .chm

Содержит копию онлайн документации в формате справки Windows (.chm). Данная документация обновляется реже, чем источник на сайте. Удобно использовать при отсутствии интернета.

Если не отображается содержимое файла CHM

Для изучения материалов учебных курсов в оффлайне на индексной странице каждого курса размещаются [dw]ссылки на файлы[/dw][di][/di] формата [dw]EPUB[/dw][di] Чем открыть файл на
Android:
EPUB Reader
CoolReader
FBReader
Moon+ Reader
eBoox

iPhone:
FBReader
CoolReader
iBook
Bookmate

Windows:
Calibre
FBReader
Icecream Ebook Reader
Плагины для браузеров:
EpuBReader – для Firefox
Readium – для Google Chrome

iOS
Marvin for iOS
ShortBook
[/di]. Через некоторое время и файлы документации тоже будут предоставляться в этом формате.

Рекомендуется таким группам пользователей, как Контент-менеджер, Администратор и Разработчик.

Учебные курсы

Онлайн Курсы


Нажмите на рисунок, чтобы увеличить

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

Рекомендуется таким группам пользователей, как Контент-менеджер, Администратор и Разработчик.

FAQ

Частые вопросы


Нажмите на рисунок, чтобы увеличить

Подборка решений наиболее часто встречающихся проблем при работе с продуктом в удобном представлении.

Рекомендуется таким группам пользователей, как Администратор и Разработчик.

Видео

Учебные видеоролики

Видеоролики демонстрируют основные функциональные возможности продукта.

Внимание! Материалы роликов могут отставать от текущих версий продуктов.

Рекомендуется таким группам пользователей, как Контент-менеджер и Администратор.

Сообщество разработчиков

Цитатник веб-разработчиков.

Евгений Смолин: Ну а вам совет — не бойтесь Битрикса, Битрикс это всерьез и надолго. Начинайте работать с ним, появятся вопросы или не пожелаете нанимать разработчиков, то обращайтесь к форуму, в ТП. Почти всегда помощь оказывают, причем оказывают помощь такие монстры, что даже опытным разработчикам интересно смотреть на решения.

За время развития продукта сложилось свое сообщество разработчиков на Bitrix Framework. Это сообщество представлено на сайте компании «1C-Битрикс» и обменивается опытом работы и результатами работы на форуме компании, в группах социальной сети и на собственных сайтах.

Совет: Пока вы не овладеете в полной мере терминологией Bitrix Framework, рекомендуем при описании проблемы избегать терминологии, известной вам по другим CMS.

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

Внимание! Форум сайта не является службой техподдержки. Техподдержка работает согласно регламенту и в другом разделе сайта.

Рекомендуется познакомиться с опытом разработок сайтов на системе Bitrix Framework, который описывается в блогах разработчиков Bitrix Framework и в группах Социальной сети сайта компании 1С-Битрикс. Так же будет полезно заглядывать на ХаброБитрикс и официальный блог Битрикса на Хабре.

Для тех, кто переходит на Bitrix Framework с других платформ

Цитатник веб-разработчиков.

Алексей Коваленко: А в данной ситуации считаю, что инструмент должен выбираться исходя из проекта, а не прихотей разработчика.

Не сравнивать, а понять

Для программистов, переходящих на Bitrix Framework с других платформ и CMS возникают дополнительные сложности, вызванные «давлением» предыдущего опыта.

Чтобы научиться эффективно работать в Bitrix Framework, нужно не сравнивать то, что вы знаете по другим системам, а стараться понять как то или иное реализуется в этой системе. В плане обучения «сравнительный» подход не работает. Просто отвлекитесь от старых знаний и изучите новую систему используя только знания PHP и сайтостроения, а не сравнивая идеологии и технологии. Легче будет освоить. А сравнивать будете потом, когда освоите Bitrix Framework.

Зато у вас может возникнуть преимущество знания двух систем. В последнее время заказчики нередко ставят задачу миграции сайтов с других систем на Bitrix Framework. Настолько нередко, что партнеры компании «1C-Битрикс» даже разрабатывают специальные решения под эти задачи.

Прямое сравнение Bitrix Framework и других систем далеко не всегда корректно. Тем не менее, такие вопросы возникают и потому приведем некоторые мнения, высказанные партнерами компании «1C-Битрикс» и программистами, работающими на Bitrix Framework.

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

  • Пример «переезда» сайта с неизвестной CMS на «1С-Битрикс: Управление сайтом»
  • В социальной сети компании «1С-Битрикс» есть группа OpenSource.Конвертер, в которой можно познакомиться с опытом других разработчиков по миграции сайтов с других систем на Bitrix Framework.

Bitrix Framework и Drupal

Основной структурной единицей CMS Drupal служит узел (node). По сути дела, любая страница сайта на Drupal (за исключением служебных) — это либо список анонсов узлов, либо полное отображение одного узла. Вывод любой страницы может сопровождаться выводом дополнительных блоков, но, так или иначе, они являются вторичными по отношению к node.

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

Все инфоблоки равноправны в том смысле, что любой инфоблок (или даже несколько инфоблоков) может использоваться для вывода как в основной области страницы, так и в дополнительных областях. Таким образом, node в CMS Drupal является лишь частным случаем инфоблока — и, фактически, в этой системе имеется только один инфоблок, тогда как в Bitrix Framework их может быть неограниченное количество.

Bitrix Framework и Joomla

  • Шаблоны сайта в Bitrix Framework примерно соответствуют по концепции шаблонам сайта в Joomla.
  • Создание шаблона сайта для Bitrix Framework по готовой верстке заключается в выделении блоков и размещении вместо этих блоков компонентов. Далее эти компоненты настраиваются на источник данных и для них редактируются шаблоны вывода в соответствии с версткой сайта.
  • В шаблоне сайта для Bitrix Framework нет позиций под модули с номером, как в Joomla. И программист не может из административной части указать какой модуль в какую позицию ставить, просто изменив число. Размещение компонентов в Bitrix Framework реализовано по-другому.
  • Модули в Bitrix Framework — это сущность для объединения необходимых программистам функций «в одном флаконе» и разделения по редакциям. Аналог в Joomla — расширения.
  • Модуль в Joomla — это компонент в Bitrix Framework. Компонент извлекает информацию из различных источников системы и выводит её в виде фрагментов web-страниц. Шаблон в составе компонента отвечает за вывод данных на страницу.

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

  • В Bitrix Framework разделяются динамические и статические данные. Есть чисто динамические блоки, например, каталог товаров. Есть статические. Есть смешанные. Однако, для того, чтобы вывести динамическую информацию, например, каталог товаров, вы должны сделать для него «домик» — папку на диске, в которой будет находиться комплексный компонент «каталог», обрабатывающий обращения к динамической информации.

Глоссарий

Что есть что?

Каждая платформа имеет свои термины, используемые в работе. Чтобы без проблем ориентироваться в документации, учебных курсах, при общении с разработчиками желательно объяснять ваши трудности не «на пальцах», а на языке понятном специалистам. Ниже в помощь вам приведен глоссарий терминов системы и несколько общих терминов, активно используемых в системе.

  • Accept-Language
  • API
  • cron (крон)
  • DocumentRoot
  • HTML-безопасный вид
  • IP адрес
  • Абсолютный путь
  • Административная часть
  • Буферизация
  • Включаемые области
  • Время в Unix-формате
  • Группа пользователей
  • Дамп
  • Доменное имя (домен)
  • Индексная страница (файл)
  • Кастомизация
  • Компоненты
  • Локализация
  • Мета-тэг
  • Многоязычный интерфейс
  • Навигационная цепочка
  • Полный путь
  • Пользователь
  • Портал
  • Постоянное соединение (persistent)
  • Права в Unix системах
  • Пролог
  • Публичная часть
  • Путь к компоненту
  • Путь относительно корня
  • Почтовое событие
  • Почтовый шаблон
  • Раздел сайта
  • Режим Правка
  • Сабмит
  • Сайт
  • Связывание переменных
  • Сессия
  • Система обновлений
  • Тело страницы
  • Тип почтового события
  • Хост
  • Шаблон сайта
  • Шаблон страницы
  • Экземпляр программы
  • Эпилог
  • Язык
  • Языковой файл

Термин Описание
Административная часть Раздел системы, содержащий интерфейс для управления модулями системы, структурой, содержанием, посетителями и другими составляющими сайта. Страницы, в которых подключен административный пролог и эпилог. Эти страницы:

  • не принадлежат ни одному сайту;
  • принадлежат какому-либо модулю;
  • имеют строго определённый административный интерфейс;
  • отображаются в выбранном языке интерфейса;
  • дополнительно проверяются по правам, задаваемым в настройках соответствующего модуля.
Публичная часть Раздел системы, доступный для отображения посетителям сайта.На страницах раздела подключается пролог и эпилог одного из шаблонов сайта. Эти страницы:

  • принадлежат какому либо сайту;
  • имеют интерфейс текущего шаблона сайта;
  • отображаются на языке текущего сайта.
Режим Правка Режим, при котором текущая страница публичного раздела отображается в особом виде: выделены используемые компоненты, включаемые области, редактируемые области и т.д. Каждая такая область имеет набор кнопок для быстрого перехода к редактированию данного элемента страницы.
Сайт Совокупность параметров:

  • учетная запись в базе данных — создаётся в административном меню Сайты, включает в себя:
    • идентификатор — набор символов, идентифицирующий сайт;
    • доменное имя — одно или более доменное имя сайта;
    • папка сайта — путь к каталогу в котором будет храниться публичная часть сайта;
    • язык сайта;
    • формат даты и времени;
    • URL — протокол и доменное имя по умолчанию (например, http://www.site.ru);
    • DocumentRoot — если многосайтовость организована на разных доменах, то в данном параметре должен храниться путь в файловой системе сервера к корню сайта;
    • условия подключения шаблонов — каждый сайт может иметь один или более шаблонов для отображения скриптов своей публичной части, каждый такой шаблон может быть подключен по тому или иному условию;
  • публичная часть — совокупность страниц, лежащих в папке сайта и принадлежащих этому сайту;
  • настройки — каждый модуль может иметь ряд настроек, связанных с сайтом, например у модуля Информационные блоки эти настройки представляют из себя привязку информационного блока к тому или иному сайту, у модуля Техподдержка — привязку статуса, категории и т.п. к сайту.
Шаблон сайта Синонимы: дизайн сайта, скин сайта. Для показа одного сайта можно использовать несколько различных шаблонов.
Шаблон сайта — это набор файлов в каталоге /bitrix/templates/ID_шаблона/, где ID_шаблона — поле ID в [ds]форме редактирования[/ds][di]Управление шаблонами дизайна осуществляется в административном разделе на странице Шаблоны сайта (Настройки > Настройки продукта > Сайты > Шаблоны сайтов), где можно…

Подробнее …[/di] шаблона сайта. Структура каталога:

  • /components/ — каталог с компонентами, принадлежащими тому или иному модулю;
  • /lang/ — языковые файлы, принадлежащие как шаблону в целом, так и отдельным компонентам;
  • /images/ — каталог с изображениями шаблона;
  • /page_templates/ — каталог с шаблонами страниц и их описанием, хранящимся в файле .content.php;
  • /include_areas/ — каталог с файлами — содержимым включаемых областей;
  • header.php — пролог шаблона;
  • footer.php — эпилог шаблона;
  • styles.css — CSS стили, используемые на страницах сайта, когда используется шаблон;
  • template_styles.css — CSS стили, используемые в самом шаблоне;
  • .тип_меню.menu_template.php — шаблон вывода меню соответствующего типа;
  • chain_template.php — шаблон по умолчанию для вывода навигационной цепочки;
  • а также ряд других вспомогательных произвольных файлов и папок, входящих в данный шаблон.
Раздел сайта Каталог в файловой системе сервера. В Bitrix Framework структура сайта — это файловая структура сервера, поэтому страницы сайта — это файлы, а разделы сайта — соответственно каталоги.
Компонент Часть какого-либо модуля, логически завершенный код, хранящийся в одном файле. Принимает ряд параметров, выполняет ряд действий и выводит результат этих действий (например, в виде HTML кода). Использование компонента — предпочтительный способ вывода информации как в публичной, так и в административной частях. Компонент подключается методом IncludeComponent Рабочая версия компонентов — 2.0. Использование компонентов 1.0 не рекомендуется, но их ещё можно встретить на сайтах, построенных на ранних версиях системы.
Путь к компоненту Устаревшее. Используется в функции CMain::IncludeFile в качестве первого параметра и представляет из себя путь к основному файлу компонента 1.0.
Включаемые области Это специально выделенная область на странице сайта, которую можно редактировать отдельно от основного содержания страницы. Реализуется с помощью специального компонента.
Навигационная цепочка Это элемент дизайна, предназначенный для навигации по сайту. Выводится в визуальной части пролога и состоит из заголовков разделов сайта с соответствующими ссылками на них. Помимо заголовков разделов, добавляемых автоматически, вы также можете добавлять произвольные пункты в навигационную цепочку.
Индексная страница (файл) Это имя файла, который будет использован веб-сервером в случае, если запрашиваемый URL заканчивается на слэш и не содержит в себе имени файла. Порядок, в котором будут искаться индексные страницы для различных веб-серверов, задается по разному, например:

  • Apache — в файле httpd.conf, параметр DirectoryIndex;
  • IIS — в свойствах сайта, закладка Documents > Enable default content page;

К сожалению, в PHP значение данного параметра недоступно, поэтому для определения индексной страницы в коде необходимо пользоваться функцией GetDirIndex.

Пользователь Запись в базе данных с параметрами зарегистрированного пользователя, обязательные поля:

  • логин;
  • пароль;
  • E-Mail.

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

Регистрационные данные (логин и пароль) в дальнейшем используются для авторизации в системе. Пользователь привязывается к определенной группе и получает право на доступ к ресурсам портала в соответствии с правами данной группы.

Группа пользователей Совокупность пользователей, обладающих определенными правами на доступ и управление ресурсами (например, пользователи группы Модераторы обладают правом на чтение и редактирование сообщений форума). Управление группами пользователей осуществляется на странице Группы пользователей в административном разделе (Настройки > Пользователи > Группы пользователей).
Почтовое событие Это почтовое сообщение, имеющее свой тип и отправляемое по соответствующему почтовому шаблону. Почтовое событие инициализирует поля типа почтового события конкретными значениями. Порядок расположения этих полей в письме, а также текст письма, определяется почтовым шаблоном.

Для создания почтового события предназначен класс CEvent.
Почтовый шаблон Определяет текст почтового сообщения, а также порядок расположения полей (placeholder’ов), заданных в типе почтового события.

Почтовые шаблоны доступны в административном разделе на странице Почтовые шаблоны (Настройки > Настройки продукта > Почтовые события > Почтовые шаблоны).

Для манипуляции почтовыми шаблонами предназначен класс CEventMessage.

Тип почтового события Определяет набор специальных полей (placeholder’ов), которые могут быть использованы в почтовом шаблоне. В момент создания почтового события эти поля будут инициализированы конкретными значениями.

Типы почтовых событий доступны в административном разделе на странице Типы почтовых событий (Настройки > Настройки продукта > Почтовые события > Типы почтовых событий).

Для манипуляции типами почтовых событий предназначен класс CEventType.

Путь относительно корня Путь к файлу, начинающийся от каталога, указанного в параметре DocumentRoot в настройках веб-сервера, заданный по правилам формирования URL-адресов. Пример:/ru/about/index.php
Полный путь Включает в себя протокол, домен и путь относительно корня к странице (каталогу). Пример: http://www.bitrixsoft.ru/ru/about/index.php
Абсолютный путь Абсолютный путь к файлу включает в себя DocumentRoot и путь относительно корня.
DocumentRoot Путь к корню сайта в файловой системе сервера. Задается в настройках веб-сервера, например:

  • для Apache — в файле httpd.conf, параметр DocumentRoot;
  • для IIS — в свойствах сайта, закладка Home Directory > Local Path.
Система обновлений Технология SiteUpdate позволяет:

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

В процессе обновления выполняется модификация только ядра продукта (файлы папок /bitrix/modules/, /bitrix/tools/, /bitrix/admin/ и /bitrix/components/bitrix/). Обновление не затрагивает публичную часть портала, полностью исключая возможность потери данных.

Обновление системы осуществляется в несколько шагов:

  • система обновлений автоматически запрашивает лицензионный ключ продукта;
  • затем выполняется проверка наличия доступных обновлений;
  • далее пользователю предлагается выбрать обновления для загрузки;
  • после этого происходит загрузка выбранных обновлений.
Язык Это учетная запись в базе данных, доступная для редактирования в административном меню на странице Языки интерфейса, со следующими полями:

  • Идентификатор;
  • Название;
  • Формат даты;
  • Формат времени;
  • Кодировка;
  • и т. д..

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

В административной части язык определяет формат времени, даты, кодировку страниц (в публичной — данные параметры определяются настройками сайта).

Языковой файл Файл, хранящий переводы языковых фраз на тот или иной язык. Данный скрипт состоит из массива $MESS, ключи которого — идентификаторы языковых фраз, а значения — переводы на соответствующий язык. Для каждого языка существует свой набор языковых файлов, хранящихся как правило в каталогах /lang/.

Языковые файлы, как правило, используются в административных скриптах модулей и в компонентах.

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

  • административного раздела;
  • сообщений об ошибках;
  • визуальных компонентов;
  • соответствующих областей в шаблоне портала;
  • и т.д.

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

Локализация Подразумевает представление информации в переводе на соответствующих языке, кодировке и форматов представления данных (дата, время, денежные единицы, числа и т.д.).
Мета-тэг Мета-тэг это — элемент HTML, задающий информацию о странице: кодировку страницы, ключевые слова, автора, краткое описание. Как правило, содержимое мета-тэгов используется в служебных целях, например, роботами поисковых систем, индексирующих сайт. Мета-тэг задается внутри тэга <head>.
Доменное имя (домен) Одно из полей DNS таблицы (domain name service), содержащее в себе строго структурированное имя интернет сайта, заданное по определённым правилам. Основная задача DNS таблицы — это ассоциация доменных имен с IP адресами сайтов. Пример доменного имени: 1c-bitrix.ru
IP адрес Это «имя» компьютера в сети, заданное по правилам протоколов TCP/IP. IP адрес состоит из четырех октетов, часть из которых идентифицирует подсеть, в которой находится компьютер, а часть — непосредственно этот компьютер в рамках соответствующей подсети. Пример IP адреса: 198.63.210.79.
Хост В применении к функциям главного модуля, хост — это доменное имя или IP адрес для обращения к тому или иному сайту.
Сессия Под термином понимается сессия PHP. Сессия может открываться в момент захода на сайт и закрывается при закрытии окна браузера. Также новая сессия открывается при авторизации пользователя, если закончить сеанс авторизации (разлогиниться) — сессия закрывается. Синонимом термина сессия можно считать один «заход на сайт».
Время в Unix-формате Количество секунд, прошедшее с 1 января 1970 года, с точностью до микросекунды. На сегодняшний день, время в Unix-формате может фиксироваться только до 2038 года.
Права в Unix системах В Unix-подобных операционных системах поддерживаются три вида прав — чтение, запись и выполнение, которые присваиваются каждому файлу или директории. Права эти повторяются три раза: для владельца файла, для группы пользователей, и для всех остальных пользователей. Как правило, права указываются в числовом формате:

  • 4 — чтение;
  • 2 — запись;
  • 1 — выполнение.

Сумма этих чисел дает окончательный набор прав, например, 6 — это чтение и запись, но без выполнения, 7 — все права, 5 — чтение и выполнение.

Таким образом, например, право 764 будет означать: 7 — все права для владельца файла, 6 — чтение и запись для группы пользователей, к которой принадлежит владелец файла и 4 — чтение для всех остальных пользователей.

В PHP все права задаются в виде восьмеричных чисел, поэтому их надо задавать с обязательным указанием префикса — 0. Пример: 0755.

Пролог

В общем случае, под данным термином понимается верхняя левая часть страницы.

Для публичной части, пролог соответствующего шаблона сайта хранится в файле /bitrix/templates/ID шаблона сайта/header.php.

Для административной части — пролог хранится в файле /bitrix/modules/main/interface/prolog_main_admin.php.

В свою очередь пролог может быть разделен на служебную и визуальную части. В служебной части подключаются все необходимые классы, создаётся соединения с базой, создаются ряд служебных экземпляров объектов, таких как $USER, $APPLICATION и т.д. В визуальной части выводится верхняя левая часть страницы.

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

require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");

Если по тем или иным причинам необходимо разделить пролог на служебную (prolog_before.php) и визуальную (prolog_after.php) части, то используем следующий код:

require($_SERVER["DOCUMENT_ROOT"].
"/bitrix/modules/main/include/prolog_before.php");
...
require($_SERVER["DOCUMENT_ROOT"].
"/bitrix/modules/main/include/prolog_after.php");
Эпилог

В общем случае под данным термином понимается нижняя правая часть страницы.

Для публичной части, эпилог соответствующего шаблона сайта хранится в файле /bitrix/templates/ID шаблона сайта/footer.php.

Для административной части — эпилог хранится в файле /bitrix/modules/main/interface/epilog_main_admin.php.

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

Если в публичной части необходимо подключить неразделенный эпилог, то используем следующий код:

require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");

Если по тем или иным причинам необходимо разделить эпилог на визуальную (epilog_before.php) и служебную (epilog_after.php) части, то используем следующие коды:

require($_SERVER["DOCUMENT_ROOT"].
"/bitrix/modules/main/include/epilog_before.php");
...
require($_SERVER["DOCUMENT_ROOT"].
"/bitrix/modules/main/include/epilog_after.php");

Примечание: Обратите внимание, что в случае пролога файл *_before.php это служебная часть, а в случае эпилога *_before.php это визуальная часть. Такое отличие возникает потому, что пролог перед телом страницы, а эпилог — после.

Тело страницы Тело страницы это часть PHP/HTML кода, расположенного в скрипте между подключениями пролога и эпилога. Тело страницы не является частью шаблона сайта и представляет из себя индивидуальное содержимое публичной либо административной страницы.
Шаблон страницы Это файл, хранящийся в одном из каталогов:

  • /bitrix/templates/ID шаблона сайта/page_templates/
  • /bitrix/templates/.default/page_templates/

Данный файл представляет из себя заготовку публичной страницы. Как правило, используется при создании новой страницы в модуле Управление структурой. Для задания порядка сортировки шаблонов страниц используется файл .content.php находящийся в одном из вышеуказанных каталогов.

Сабмит Отправка данных HTML формы на сервер.
HTML-безопасный вид Как правило данный термин применяют к тексту, в котором произведены следующие замены:

Исходное Результат
< &lt;
> &gt;
« &quot;
& &amp;

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

Связывание переменных В применении к SQL-запросам для Oracle версии, под данным термином подразумевается связывание имен переменных (либо полей таблицы) с их значениями. Как правило, подобная технология используется для полей типа BLOB, CLOB, LONG и т.п. предназначенных для хранения больших объемов данных.
Дамп В применении к базе данных это — выгрузка содержимого и, возможно, структуры таблиц в файл в определённом формате, для их возможной дальнейшей загрузки обратно в данную, либо любую другую БД. Для каждой базы данных существуют свои утилиты, позволяющие сделать дамп, например для MySQL утилита mysqldump позволяет выгрузить в формате обычных SQL запросов, для Oracle утилита exp позволяет выгрузить в своем внутреннем формате.

В применении к переменным, дамп подразумевает отображение структуры и содержимого переменной в текстовом виде.
cron (крон) В Unix-подобных операционных системах утилита cron позволяет организовать запуск скриптов по четко указанному расписанию.
Буферизация Режим, при котором весь исходящий поток данных из PHP скрипта (например, HTML код) запоминается предварительно в памяти и не отдается браузеру пользователя. Буферизацию в PHP можно включить с помощью функции ob_start. В дальнейшем её можно отключить, например с использованием функции ob_end_flush, при этом все накопленные данные будут отосланы браузеру. Режим позволяет произвольно манипулировать исходящим потоком данных, на этом принципе основана технология отложенных функций.
Постоянное соединение (persistent) При создании соединения с базой, в памяти создаётся дескриптор данного соединения. Если соединение обычное, то после отработки скрипта этот дескриптор удаляется, если соединение постоянное, он остается и может быть использован другими процессами при необходимости. Достоинством постоянного соединения является то, что времени на его работу требуется меньше. Недостаток — количество открытых постоянных соединений ограничивается в настройках базы данных и при превышении этого лимита посетитель не сможет зайти на сайт пока не освободятся новые соединения.
Accept-Language Набор языков, установленных в браузере посетителя сайта. К примеру, для MS Internet Explorer их можно выставить в меню Сервис > Свойства обозревателя > Общие > Языки. Для Mozilla Firefox: Меню > Настройки > Содержимое > Языки.
Кастомизация Изменение логики работы компонента или шаблона компонента под частные задачи.
API (SDK) Каждый модуль системы содержит набор высокоуровневых функций для выборки данных в публичном разделе сайта и набор классов с низкоуровневыми методами для более специализированной работы с данными модуля. Подробная информация по API каждого модуля представлена в документации для разработчиков.
Экземпляр программы Копия какого-либо продукта «1C-Битрикс», включающая в себя исходный текст продукта и только одну копию структуры и таблиц базы данных, входящих в состав продукта, а также любую документацию по использованию продукта.
Портал Один набор файлов, хранящихся в каталоге /bitrix/modules/, и одна копия базы. Портал включает в себя один или более сайтов. Синонимом данного термина может служить: «экземпляр продукта», «одна инсталляция системы», «одна копия системы».

Золотые правила работы с Bitrix Framework

Цитатник веб-разработчиков.

Степан Овчинников: Неописуемы безобразия, которые можно увидеть в коде человека, знающего мало, а делающего много.

Перед тем как начать работать в Bitrix Framework, необходимо понять основные правила, следование которым поможет избежать многих и многих ошибок:

  • Что важно помнить при работе над сайтом:
    1. Нельзя править на «боевом» сайте. Необходимо вести разработку на копии сайта с использованием режима [ds]Установка для разработки[/ds][di]Начиная с версии 16.5.7 и старше, в продуктах «1С-Битрикс» можно пометить новую или существующую установку продукта специальным маркером Установка для разработки. Маркер позволяет проводить тестирование, не устанавливая продукт локально.

      Подробнее …[/di]

    2. Если в силу каких-то причин [dw]приходится править[/dw][di]При должном уровне квалификации и обоснованных доводах почему именно так нужно.[/di] на живом сайте, то нужно всегда иметь доступ к FTP или SSH, так как административная часть из-за ошибок в коде может оказаться недоступной.
    3. Всегда должен быть наготове свежий бекап.
    4. Для случая, когда доработки значительны, когда работает команда из нескольких человек крайне рекомендуется использовать [ds]систему контроля версий[/ds][di]Организовать сопровождение проекта с помощью системы контроля версий не сложно, если ограничиваться файлами. Для этого можно использовать, например, Mercurial — кроссплатформенную распределённую систему управления версиями, разработанную для эффективной работы с очень большими репозиториями кода.

      Подробнее …[/di].

  • Если вы хотите внести какие-то изменения в работе сайта, то:
    • Сначала формализуйте свои требования на листе бумаги, а не кидайтесь править код.
    • После этого заново просмотрите все случаи использования на сайте модифицируемого вами блока. Убедитесь, что всё логично. Очень часто делают небольшие, казалось бы, изменения, а потом оказывается, что страдает связанный функционал, о котором не подумали или забыли.
    • После того, как вы формализовали потребности в изменениях, посмотрите, какие сущности эти требования затрагивают.
    • Только после этого подумайте, какие средства использовать для достижения своих целей.
  • Способы внесения изменений и желательный порядок их применения:
    • сначала попытайтесь сделать это редактированием шаблона самого сайта и файлов CSS;
    • если предыдущее невозможно, то попытайтесь сделать это средствами редактирования страницы сайта;

      Примечание: К этому пункту нужно подходить крайне осторожно. Бездумное добавление кода может привести к тому, что компоненты будут обрамляться кодом, который должен быть в шаблоне компонента, а не на странице. Размещение php-кода на странице грозит проблемами. Если контент-менеджер откроет страницу через визуальный редактор, то легко можно «все сломать»:

      • визуальный редактор на этапе разбора и визуализации содержимого страницы может допустить ошибки,
      • контент-менеджер может случайно внести правки «несовместимые с жизнью» в php-код и даже не поймет этого.
    • при невозможности реализации задачи с помощью первых вариантов переходите к редактированию шаблонов компонента и файлов CSS компонента, либо изменяйте вывод данных с помощью файлов result_modifier.php и component_epilog.php
    • используйте обработчики событий, которые позволяют решать очень широкий спектр задач.
    • кастомизация компонента или разработка собственного компонента или модуля — последний из возможных вариантов получения нештатного функционала.
  • Не рекомендуется писать код HTML в код PHP для изменения представления данных. В компонентах 2.0 разделены логика и представление. Логика — это сам компонент, представление — это шаблон вывода компонента. Шаблон существенно проще, чем компонент в целом. Нет необходимости изменять логику компонента для изменения особенностей показа его данных. Для одной логики может быть несколько представлений, в том числе зависящих от шаблона текущего сайта. Представление (шаблон вывода) может быть написано на любом шаблонном языке, который можно подключить из PHP. Например, шаблоны могут быть на PHP, Smarty, XSL и т.д. Использование стороннего шаблонизатора должно быть четко обосновано, его использование должно давать какие-то преимущества перед штатными средствами.
  • Собственные компоненты и шаблоны — в собственном пространстве имен. Кастомизируя штатные компоненты и шаблоны, разрабатывая собственные, размещайте их в собственном пространстве имен. При обновлении системы все внесенные изменения в пространстве bitrix затираются.
  • При работе с компонентами не надо обращаться к базе напрямую. Концепция работы с продуктом предполагает работу с данными через функции API. Структура данных может меняться от версии к версии, а функции сохраняют обратную совместимость. Мы настоятельно не рекомендуем использовать прямые запросы к БД, т.к. это может нарушить целостность данных и привести к неработоспособности сайта. В силу вышесказанного структура таблиц не афишируется.
  • Запрещается править код ядра в силу нескольких причин:
    • при обновлении системы внесенные изменения затрутся;
    • при изменении ядра владелец лицензии теряет право на техническую поддержку;
    • при изменении ядра разработчиком сайта возможна некорректная работа системы, так как ядро — сложная система, требующая учета работы всех модулей.

    Ядро продукта — файлы, находящиеся в директории /bitrix/modules/ а так же файлы системных компонентов: /bitrix/components/bitrix/.

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

  • Если вы сопровождаете живой сайт и заказчик просит провести работы по доделке-расширению функционала, то сначала проверьте, а пользуются ли вообще этим функционалом. Проверить можно так:
    • Статистика посещаемости — Внутренняя статистика, Яндекс.Метрика, Google Analytics или Liveinternet
    • Артефакты внутри Bitrix Framework: результаты веб-форм, заказы, скачивания (в Яндекс.Метрике есть нужный отчёт и отлично работает) и т.д.
    • Карты кликов в Яндекс.Метрике.

    Если блок мёртвый, то лучше его вообще убрать или [dw]переформулировать ТЗ[/dw][di]В большинстве случаев это задача не разработки, а проектного отдела, проект менеджеров, руководства, в крайнем случае тим лида, если он контактирует с клиентами.
    Кроме того, зачастую заказчик хочет доделать функционал, вне зависимости от того, пользуются или нет им, он платит за это деньги. А экспертиза обычного разработчика может быть вовсе не достаточна для такой оценки. Возможно клиент планирует дальнейшие шаги, рекламу и т. п.[/di].

Внимание! Файлы, к которым нельзя обращаться напрямую (они не должны выполняться, будучи вызванными напрямую по адресу в браузере), должны содержать в начале следующий код проверки:

<?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>

К таким файлам относится все, что работает внутри продукта, например: шаблоны сайтов, шаблоны компонента, файлы .parameters.php и .description.php.

Junior, Как создать простой сайт

Самый первый и самый простой по сложности пример создания сайта на «1С-Битрикс: Управление сайтом». Освоив эту главу вы сможете назвать себя разработчиком начального уровня.

Чтобы уметь создавать сайты на начальном уровне необходимо освоить систему на уровне работы штатным функционалом. Практически без кодирования. Кодирование нужно только при создании шаблона и это кодирование — знание HTML.

К концу изучения главы вы должны уметь:

  1. Устанавливать систему
  2. Создавать простейший шаблон сайта и применять его к сайту, разделу, странице.
  3. Интегрировать компоненты в шаблон сайта.
  4. Создавать, настраивать и применять информационные блоки.
  5. Управлять метаданными и заголовками.
  6. Управлять штатными инструментами кеширования.
  7. Создавать страницы и разделы.
  8. Задавать права доступа к страницам и разделам.

Типовой порядок действий

Цитатник веб-разработчиков.

Sergey Leshchenko: Сайты разрабатываются не для того, чтобы их было удобно потом поддерживать веб-разработчикам.

Примерный алгоритм действий по созданию простого сайта следующий:

  • Получение верстки и ознакомление с техническим заданием на сайт;
  • Определение необходимого числа шаблонов и их структуры;
  • [ds]Установка дистрибутива[/ds][di]Описание предварительных шагов для установки продукта, шагов мастера установки, а также выбора и первоначальной настройки решений для быстрого развертывания своего проекта.

    Подробнее …[/di] «1C-Битрикс»;

  • Создание сайта в Административном разделе;
  • Создание шаблонов и применение их к сайту;
  • Создание и настройка необходимых элементов шаблона для SEO-продвижения сайта;
  • Создание структуры сайта;
  • Создание и настройка инфоблоков;
  • Создание шаблонов визуальных компонентов;
  • Тестирование.

Под простыми сайтами понимаются проекты, функционал которых можно реализовать штатными средствами Bitrix Framework с кастомизацией только шаблонов компонентов с целью изменения формы вывода данных. Кастомизация шаблона компонента — самый простой и несложный для освоения способ изменения вывода данных компонентов, не требующий серьезного программирования.

Сайт в понятии Bitrix Framework

Сайт — это совокупность:

  • Учетной записи в базе данных, которая создаётся в административном меню на странице Список сайтов (Настройки > Настройки продукта > Сайты > Список сайтов) и включает в себя следующие основные параметры:
    • идентификатор — набор символов, идентифицирующий сайт;
    • доменное имя — одно или более доменное имя сайта;
    • папка сайта — путь к каталогу, в котором будет храниться публичная часть сайта;
    • язык сайта;
    • Региональные настройки;
    • URL — протокол и доменное имя по умолчанию (например, http://www.site.ru)
    • DocumentRoot (Путь к корневой папке веб-сервера для этого сайта) — параметр заполняется при использовании многосайтовости системы.
    • условия подключения шаблонов: каждый сайт может иметь один или более шаблонов для отображения страниц публичной части, каждый такой шаблон может быть подключен по тому или иному условию.
  • Публичной части — совокупности скриптов (страниц) лежащих в папке сайта и принадлежащих этому сайту.
  • Настроек. Некоторые модули имеют ряд настроек связанных с сайтом, например, у модуля Информационные блоки эти настройки представляют из себя привязку информационного блока к тому или иному сайту, у модуля Техподдержка — привязку статуса, категории и т.п. к сайту.

В Bitrix Framework имеется возможность на базе одного экземпляра продукта создавать и поддерживать неограниченное количество сайтов. Особенностями системы многосайтовости являются:

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

Детально эта функция системы описана в курсе Многосайтовость

Структура

Структура сайта в рамках Bitrix Framework:

  • Шаблон — определяет представление сайта пользователям. Существуют шаблоны компонентов и шаблоны сайта.
  • Компоненты — задают вывод данных.
  • Страница — элемент структуры сайта.

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

Что такое страница

Структура

Страница представляет из себя PHP файл, состоящий из пролога, тела страницы (основной рабочей области) и эпилога:

  • header
  • workarea
  • footer

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

В общем случае все страницы сайта имеют следующую структуру:

Нажмите на рисунок, чтобы увеличить

1 Верхняя — header. Включает в себя, как правило, верхнюю и левую часть дизайна со статической информацией (логотипом, лозунгом и так далее), верхним горизонтальным меню и левым меню (если они есть в дизайне). Может включать в себя информационные динамические материалы.

2 Основная рабочая область — work area. Рабочая область страницы, в которой размещаются собственно информационные материалы сайта. В качестве Основной рабочей области может подключаться как физический файл, так и создаваемый системой на основе комплексных компонентов, динамический код.

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

3 Нижняя — footer. Включает в себя, как правило, статическую информацию (контактная информация, сведения об авторе и владельце сайта и так далее), нижнее горизонтальное меню и правое меню (если они есть в дизайне). Может включать в себя информационные материалы.

Примечание: Подробнее со структурой страницы можно познакомиться в уроке Шаблон дизайна.

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

Шаблоны и свойства

Шаблоны

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

Шаблоны страниц хранятся в каталогах:

  • /bitrix/templates/.default/page_templates/;
  • /bitrix/templates/ID_шаблона_сайта/page_templates/.

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

Свойства

Свойства раздела хранятся в файле .section.php соответствующего каталога (раздела сайта). Свойства страницы задаются, как правило, либо в теле страницы, либо между служебной частью и визуальной частью пролога.

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

Язык и языковые файлы

Язык — это учётная запись в базе данных, доступная для редактирования в административном меню на странице Настройки > Настройки продукта > Языковые параметры > Языки интерфейса, со следующими основными полями:

  • Идентификатор,
  • Название,
  • Региональные настройки.

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

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

Цитатник веб-разработчиков.

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

Языковые файлы

Языковой файл — PHP скрипт, хранящий переводы языковых фраз на тот или иной язык.

Данный скрипт состоит из массива $MESS, ключи которого — идентификаторы языковых фраз, а значения — переводы на соответствующий язык.

Пример языкового файла для русского языка

Пример языкового файла для английского языка

Для каждого языка существует свой набор языковых файлов, хранящихся в подкаталогах /lang/ структуры файлов системы или модуля. Языковые файлы как правило используются в административных скриптах модулей или в компонентах.

Выбор кодировки сайта

Цитатник веб-разработчиков.

Зайцев Артемий: Если есть возможность делать в UTF, надо делать в UTF.

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

UTF-8 (от англ. Unicode Transformation Format) — в настоящее время распространённая кодировка, реализующая представление Юникода, совместимое с 8-битным кодированием текста.

и

Windows-1251 (или cp1251) — набор символов и кодировка, являющаяся стандартной 8-битной кодировкой для всех русских версий Microsoft Windows.

UTF-8 более перспективна. Но у любой вещи есть недостатки. И решение об использовании какой-то кодировки только потому, что она перспективна, без учета многих других факторов, не представляется правильным. Выбор будет оптимальным только тогда, когда он полностью учитывает все нюансы конкретного проекта. Другое дело, что предусмотреть все нюансы — само по себе весьма не просто.

Мы считаем, что использование UTF-8 предпочтительнее, но решать что выбрать — это дело разработчика проекта. А для облегчения этого выбора используйте сравнительную таблицу особенностей обеих кодировок.

Свойство UTF-8 Windows 1251
Общего характера
Многоязычность Кодировка позволяет использовать разные языки как в публичной, так и в административной части сайта. Смена кодировки действующего крупного сайта с Windows-1251 на UTF-8 может вызвать серьёзные дополнительные трудовые и финансовые издержки.

Большое число символов. Возможность использования спецсимволов. Есть. Но надо учитывать возможности браузеров. Штатно нет. Есть возможность замены спецсимволов на «костыли», например, © на
&cорy; или × (знак умножения) на &timеs;. Однако это повышает требования к уровню подготовки контент-менеджера и создаёт проблемы при переносе данных из другой базы данных. Кроме того, в Bitrix Framework есть поля, которые не используют визуальный редактор, например, название страницы или название элемента инфоблока. Это также усложняет поддержку проекта силами низкоквалифицированных сотрудников.
Минимизация объема проекта. Проект на UTF-8 будет заведомо «тяжелее», в силу того что строки в этой кодировке занимают в два раза больше места, чем строки в однобайтной Windows-1251. Размер сайта и базы данных будет в 1,2 — 1,5 раз больше.
Поддержка большинством js-фреймворков Поддерживается без проблем. Сложности в реализации.
Импорт из 1С Сайты на UTF-8 работают без проблем при интеграции через SOAP с такими системами как, например, 1С.
Вебвизор Яндекс.Метрики Вебвизор корректно записывает действия посетителей. Возможны ошибки в записи.
Связанные с Bitrix Framework
Возможность сделать сайты в разной кодировке по системе многосайтовости. Невозможно. Все сайты на одном ядре должны быть в одной кодировке.
Поддержка на различных хостингах Работает на любых хостингах. С версии 20.100.0 Главного модуля (main) требуется удаление настройки PHP mbstring.func_overload. Эта опция более не требуется и не поддерживается платформой.

до версии 20.100.0

Работает на любых хостингах.
Размещение продуктов на виртуальной машине BitrixVM. По умолчанию. Требует дополнительных действий по настройке.
Разные мелочи
Взаимодействие с WordPress (блог-клиенты, trackback и ping’и) Есть Нет
Поддержка большинством редакторов Требуется редактор, который поддерживает кодировку UTF-8 без BOM. Нет проблем.

Список ссылок по теме:

  • Конвертация сайта из cp1251 в UTF-8 (блог)
  • Конвертация сайта из cp1251 в UTF-8 (учебный курс)

Техническое задание на сайт

Цитатник веб-разработчиков.

Антипов Андрей: Как правило, плохо составленное ТЗ приводит к расходам со стороны исполнителя, разочарованию заказчика и, в худшем случае, бесконечным доработкам (на основе логики «так это же очевидно»).

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

Составление технического задания — обязательный шаг для разработки качественного, удовлетворяющего пользователя сайта. Обычно клиенты обращаются за разработкой сайта, не имея ни постановки задачи, ни, тем более, технического задания. А лишь имея некое формальное описание того, чего бы им хотелось получить в итоге. Такое формальное описание называется «бриф».

Пример брифа.

Необходимо разработать сайт автосервиса с Интернет-магазином автозапчастей, с формами заказа запасных частей, заявки на ремонт или ТО, отзыва о сайте, с фото-галереей.

Бриф может быть таким как в примере, а может быть гораздо обширнее и походить на постановку задачи.

Чтобы начать разработку проекта, необходимо подготовить всю необходимую документацию:

  • Постановка задачи, составляется после анализа брифа от заказчика;
  • Техническое задание, составляется после того, как с постановкой задачи разобрались;
  • ER-диаграмма — инфологическая или даталогическая (т.к. в 1С-Битрикс для хранения информации используются инфоблоки, то эти модели как бы совмещены) модель. На этой диаграмме отображаются все ваши сущности (инфоблоки) и связи между ними.
  • Прототип будущего сайта. Для больших проектов рисуется подробный прототип в специализированных программах, например, Axure.

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

Как описать архитектуру будущего сайта: текст? схемы?

Иван Неслуховский: На самом первом этапе делаю майнд-карту всего сайта, на которой также размещаю сущности и основные атрибуты. Это даёт возможность, уже глядя на что-то, обсуждать с заказчиком ТЗ, заказчик видит, что вы вникаете в проблему. Плюс — скорость. Я пользуюсь бесплатной программой XMind.

Далее уже составляются схемы данных. Я предпочитаю делать модель предметной области и модель данных в нотации UML. Для этого существует множество средств. Бумага тоже подходит для черновика. Тип данных обозначаю в соответствии с типами данных свойств инфоблока, например:

NAME: S [1]
CODE: S [1]
PREVIEW_TEXT: S* - звёздочка у меня означает, что текст многострочный
DURATION_FROM: D
DURATION_TO: D
PRODUCT: E ----------------------------------------------------------------> здесь может выходить связь к другому блоку

Для небольших проектов достаточно «начертить» прототипы в графическом редакторе или на бумаге.

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

  • разработка дизайна;
  • интеграция дизайна в систему 1С-Битрикс;
  • создание структуры сайта;
  • разработка функционала;
  • тестирование;
  • внедрение и сопровождение.

Список ссылок по теме:

  • Обязательные вопросы, которые нужно задавать в первую очередь для составления ТЗ на разработку интернет-магазина (блог)
  • Образец постановки задачи
  • Образец Технического задания

Базовый шаблон

Цитатник веб-разработчиков.

Иван Левый: Самая лучшая верстка — своя верстка. Сколько ни заказывали у внешних подрядчиков дизайн с версткой, все равно приходилось после них править.

Веб-дизайн – это прежде всего разработка интерфейса, среды взаимодействия пользователя с информацией, а не просто «красивая картинка». Надо учитывать особенности веб-среды, такое понятие как удобство использования (usability), направленность на цели создания сайта. Важно учесть основные сценарии поведения пользователя, особенности целевой аудитории.

Когда дизайн готов, обычно применяется один из двух технологических процессов по интеграции дизайна в систему управления: либо разработчик сам верстает (т.е. переводит из графического эскиза в HTML) макет сайта, либо ему предоставляется уже готовая верстка, и он ее интегрирует в сайт. Вопросы создания верстки не входят в программу обучения Bitrix Framework, поэтому речь пойдет о готовом, сверстанном шаблоне.

  • Определение количества необходимых шаблонов
  • Редактирование шаблона
  • Структурное деление шаблона
  • Служебные директивы
  • Картинки и файлы стилей
  • Определение количества необходимых шаблонов

    Перед началом работы необходимо определить, сколько различных шаблонов сайта понадобится. Обычно при разработке сайта прорисовываются все различные страницы или основные элементы сайта.

    Bitrix Framework позволяет использовать неограниченное число шаблонов и назначать их по разным условиям. Рассмотрим простейший вариант, что на всех этих страницах простого сайта фактически меняется только контентная часть, а дизайн – не изменяется. Исключение составляет главная страница, у которой контентная область устроена по-другому (не содержит заголовка страницы) и разделена на две части. Это можно реализовать как дополнительными условиями в шаблоне сайта, так и созданием двух шаблонов. Рекомендуется использовать дополнительные условия, в этом случае потребуется всего один шаблон сайта.

    Редактирование шаблона

    Перейти к редактированию шаблона можно любым из способов:

    • Создав (открыв для редактирования) нужный шаблон (Настройки > Настройки продукта > Сайты > Шаблоны Сайтов);
    • Выбрав Шаблон в меню Пуск (Настройки > Настройки продукта > Сайты > Шаблоны Сайтов);
    • С помощью кнопки Шаблон сайта на Панели управления (Шаблон сайта > В Панели управления > Редактировать шаблон);
    • Прямое редактирование файлов header.php и footer.php в папке шаблона.

    Структурное деление шаблона

    Проанализируйте шаблон и определите, какая часть кода должна относиться к Прологу (файл header.php), какая к Эпилогу (файл footer.php), а какая часть — к Рабочей области страницы. Выбранные части кода должны быть размещены в соответствующих файлах, а рабочая область должна быть отмечена тегом #WORK_AREA# в шаблоне сайта.

    Нажмите на рисунок, чтобы увеличить

    Добавьте соответствующий этим частям код в указанные файлы. Либо, если редактирование происходит в редакторе, разделите их тегом #WORK_AREA#, удалив из шаблона контентную часть.

    Служебные директивы

    Необходимо заменить некоторые части верстки на служебные директивы Bitrix Framework для создания шаблона:

    • Заменить подключение стилей и, возможно, javascript файлов на директиву <?$APPLICATION->ShowHead()?>
    • Заменить прописанный явно заголовок страницы на <title><?$APPLICATION->ShowTitle()?></title>
    • Сразу после тэга <body> добавить <?$APPLICATION->ShowPanel();?>. Если этого не сделать, Панель управления не появится.
    • Перед всеми картинками добавить путь к ним <? SITE_TEMPLATE_PATH?>/images/
    • Заменить контент на специальный разделитель — #WORK_AREA#

    Картинки и файлы стилей

    Все изображения, относящиеся к шаблону размещаются в папке /bitrix/templates/ID шаблона сайта/images/.

    Описания стилей из представленной верстки переносятся в файл: /bitrix/templates/ID шаблона сайта/styles.css.

    Описания стилей шаблона переносятся в файл /bitrix/templates/ID шаблона сайта/template_styles.css.

    Список ссылок по теме:

    • Управление шаблоном дизайна сайта в курсе Разработчик Bitrix Framework
    • Верстка под битрикс и создание шаблона – особенности, проблемы, рекомендации от Алексея Валеева

    Что такое Шаблон сайта

    Файлы и композиция

    Шаблон дизайна — это внешний вид сайта, в котором определяется расположение различных элементов на сайте, художественный стиль и способ отображения страниц. Включает в себя программный html-код, графические элементы, таблицы стилей, дополнительные файлы для отображения контента. Может также включать в себя шаблоны компонентов, шаблоны готовых страниц и сниппеты.

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

    Шаблон сайта определяет:

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

    Файлы шаблона

    В шаблон сайта входят:

    • каталог /components – предназначен для шаблонов компонентов;
    • каталог /images – предназначен для картинок шаблона (которые не зависят от просматриваемой страницы), копируется из верстки сайта;
    • каталог /include_areas – содержит включаемые области шаблона;
    • каталог /lang – содержит файлы языковых сообщений;
    • каталог /page_templates – для шаблонов страниц и редактируемых областей;
    • каталог /snippets – содержит сниппеты – маленькие фрагменты html-кода для ускорения работы контент-менеджера по созданию часто встречающихся блоков кода;
    • каталог /themes – тема оформления шаблона;
    • файл header.php – часть шаблона ДО контента;
    • файл footer.php – часть шаблона ПОСЛЕ контента;
    • файл description.php – название и описание шаблона;
    • файл .styles.php – описания стилей для визуального редактора страниц;
    • файл template_styles.css – стили шаблона (стили применяемые в самом шаблоне дизайна сайта);
    • файл styles.css – стили для контента и включаемых областей. Эти стили можно применять в визуальном редакторе.

    Композиция шаблона

    Композицию шаблона сайта строят из трех основных частей:

    Header — верхняя часть дизайна, заголовок. Включает в себя, как правило, верхнюю и левую часть дизайна со статической информацией (логотипом, слоганом и так далее), верхним горизонтальным меню и левым меню (если они есть в дизайне). Может включать в себя информационные динамические материалы. Хранится в отдельном файле .../<идентификатор_шаблона>/header.php.

    Work area — рабочая область страницы, в которой размещаются собственно информационные материалы сайта. Рабочая область — это все создаваемые пользователями документы, хранящиеся в файлах <имя_документа>.php в соответствующих папках сайта. В шаблоне сайта рабочая область помечается разделителем #WORK_AREA#, который используется для указания границы между верхней и нижней частью дизайна. В этом месте будет выполняться подключение рабочей области страницы сайта. Сохранение шаблона без этого разделителя невозможно.

    Footer — нижняя часть дизайна со статической информацией (как правило: контактная информация, сведения об авторе и владельце сайта и так далее), нижним горизонтальным меню и правым меню (если они есть в дизайне). Может включать в себя информационные материалы. Хранится в отдельном файле .../<идентификатор_шаблона>/footer.php.

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

    Несколько типовых примеров композиций

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

    Статическая информация, которая не нуждается (либо редко нуждается) в замене, как правило, размещается в статических зонах Footer и Header. Заменить ее можно в кодах самих файлов, но делать это придется квалифицированному разработчику, либо разработчик должен организовать такую замену с помощью компонентов системы силами редакторов сайта.

    Помимо статической информации, в шаблоне и в рабочей области могут располагаться:

    • Визуальные компоненты
    • Включаемые области
    • Произвольный PHP-код

    Эти элементы сайта предназначены для вывода динамической информации.

    Хранение и подключение

    Где хранятся шаблоны

    Все используемые в системе шаблоны хранятся в отдельных папках каталога /bitrix/templates/ (например, /bitrix/templates/demo/ или /bitrix/templates/template1/), либо, начиная с версии 14.0.0, в /local/templates/. Также существует специальная папка .default, которая не является полноценным шаблоном сайта, а содержит шаблоны компонентов и файлы, общие для остальных шаблонов сайта.

    Для собственных, не штатных, шаблонов рекомендуется использовать папку /local/templates/.

    Внимание! Шаблон Битрикс24, используемый в дистрибутивах «1С-Битрикс: Корпоративный портал» является системным, то есть некастомизируемым шаблоном. Шаблон расположен в папке /bitrix/templates/bitrix24/.

    Подключение частей дизайна

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

    <?
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
    $APPLICATION->SetTitle("1C-Битрикс: Управление сайтом");
    ?>

    Тело документа. Содержательная часть.

    <?
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");
    ?>

      Сколько может быть шаблонов

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

    Настройка условий применения того или иного шаблона определяется отдельно для каждого сайта (в форме создания и редактирования сайта: Настройки > Настройки продукта > Сайты > Список сайтов):

    Стили шаблона сайта подключаются последними — это крайняя возможность переопределить, например, стили стандартных компонентов. Иначе потребуется масштабная кастомизация шаблонов компонентов. Если есть необходимость подключить свои стили последними, то это можно сделать с помощью функции CMain::AddHeadString.

    Список ссылок по теме:

    • Применение шаблона дизайна в курсе Контент-менеджер
    • Шаблоны дизайна в курсе Администратор. Базовый
    • Шаблон сайта в Документации для разработчиков.

    Создание и управление шаблоном

    Управление шаблонами дизайна осуществляется в административном разделе на странице Шаблоны сайта (Настройки > Настройки продукта > Сайты > Шаблоны сайтов), где можно:

    • Создать новый;
    • Отредактировать существующий шаблон;
    • Импортировать/Экспортировать шаблон;

    Выбор шаблона

    Создание шаблона

    Шаблон дизайна сайта может быть создан непосредственно в системе с помощью формы Новый шаблон, для перехода к которой служит кнопка Добавить шаблон, расположенная на контекстной панели.

    При создании нового шаблона через интерфейс задается:

    • его идентификатор;
    • название;
    • описание для показа в списке;
    • порядок следования в общем списке шаблонов;
    • тип шаблона;
    • код шаблона внешнего вида сайта;
    • таблицы стилей:
      • Закладка Стили сайта служит для описания таблиц каскадных стилей (CSS), используемых на страницах сайта. Описание стилей хранится в файле styles.css в папке шаблона сайта.
      • Закладка Стили шаблона служит для описания таблиц каскадных стилей (CSS), используемых в шаблоне. Описание стилей хранится в файле template_styles.css в папке шаблона сайта.
    • набор используемых включаемых компонентов и картинок.

    При сохранении шаблона автоматически создается поддиректория /bitrix/templates/<идентификатор_шаблона>.

    Все графические элементы, используемые в шаблоне, рекомендуется размещать в директории /bitrix/templates/<идентификатор_шаблона>/images/.

    Примечание: На время создания шаблона рекомендуется отключить кеширование.

    Для наглядного представления шаблона в списке может использоваться его скриншот. Скриншот размещается в папке соответствующего шаблона в файле с именем screen.gif (например, /bitrix/templates/books/screen.gif).

    Редактирование шаблона

    Чтобы просмотреть или поменять структуру и программный код шаблона, перейдите в режим редактирования, выбрав в меню действий пункт Изменить в списке шаблонов, либо используйте пункт [dw]Редактировать шаблон[/dw][di]Пункт меню "Редактировать шаблон"[/di] меню кнопки Шаблон сайта на административной панели в Публичном разделе.

    Ранее была возможность редактировать шаблон дизайна, как с использованием визуального редактора, так и работая с исходным кодом. С версии 14.0 редактирование шаблона с помощью визуального редактора невозможно.

    Внимание! В шаблоне дизайна сайта крайне не рекомендуется использовать комплексные компоненты.

    Экспорт/Импорт шаблона

    С помощью интерфейса системы используемый на сайте шаблон может быть выгружен в файл формата <имя_шаблона>.tar.gz. Для выгрузки шаблона служит пункт контекстного меню Скачать.

    Готовый шаблон можно импортировать в виде комплекта файлов с помощью менеджера файлов либо с помощью специального интерфейса системы. На странице со списком шаблонов имеется специальная кнопка контекстной панели Загрузить шаблон.

    При нажатии на кнопку открывается форма:

    • С помощью кнопки Обзор… укажите файл с шаблоном для загрузки.

      Примечание: Файлы шаблона должны быть в кодировке UTF-8.

    • При загрузке по умолчанию шаблон будет распакован и помещен в папку с именем, соответствующим имени загружаемого файла (/bitrix/templates/<идентификатор_шаблона>/). Например, если имя загружаемого файла template1.tar.gz, то шаблон будет автоматически помещен в папку .../template1/, а самому шаблону будет присвоен идентификатор (ID) template1.

    • Для того чтобы шаблону был присвоен другой идентификатор, а сам шаблон был размещён в папке с соответствующим именем, в поле Код шаблона указывается нужный код шаблона.
    • Также можно привязать загруженный шаблон как шаблон по умолчанию для выбранного сайта с помощью соответствующей опции.

    Разработка шаблона дизайна

    Процесс создания шаблона сайта включает два основных этапа:

    Разработка прототипа

    Цитатник веб-разработчиков.

    Роман Петров: Периодически возникает вопрос: продайте мне Битрикс с шаблоном… Ведь для Joomla шаблоны есть, а для Битрикс? Открываю небольшую тайну: для Битрикс все немного по другому.

    Справедливости ради следует отметить, что разработка простого шаблона для 1С-Битрикс на основе имеющейся верстки — достаточно несложное занятие. Сложность заключается в том, что мало кому требуется сделать простой шаблон. Везде нужно реализовать бизнес-логику заказчика. Ведь Битрикс — решение для бизнеса, а не для «поиграться».

    Прототип представляет собой сверстанный в html шаблон дизайн сайта. При верстке в шаблоне выделяются функциональные области, например:

    • заголовок страницы;
    • меню;
    • цепочка навигации;
    • форма авторизации;
    • форма поиска;
    • включаемые области и файлы;
    • рекламные области;
    • и т.д.

    Создание полнофункционального шаблона

    На втором этапе выполняется замена HTML элементов дизайна на соответствующие функциональные элементы: программный код и вызовы компонентов. В результате чего уже получается PHP-шаблон дизайна сайта.

    Примечание: При создании шаблона сайта возможно использование различных программных условий, влияющих на отображение тех или иных элементов шаблона для различных разделов сайта. В этом случае для раздела сайта нужно определить [ds]некоторое свойство[/ds][di]Управление свойствами страницы в Административном разделе, независимо от выбранного редактора, осуществляется в форме редактирования страницы…

    Подробнее …[/di], значение которого будет проверяться в шаблоне сайта:

    <?if ($APPLICATION->GetProperty(“SECT_PROP”)=="Y"):?>
    

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

    Рекомендации по созданию шаблона

    Основные моменты, которые нужно учитывать при создании шаблона:

    • При подготовке графического дизайна следует заранее разметить линию раздела дизайна на пролог (header.php) и эпилог (footer.php).
    • Следует выделить основные элементы дизайна, для последующей модификации таблицы стилей: шрифты, цвета заливки и т.п.
    • Разрабатывая дизайн меню различных уровней, желательно выделять повторяющиеся элементы — для упрощения создания шаблона меню и дальнейшего управления этими меню.
    • Для облегчения сопровождения различных языковых версий сайта по возможности следует использовать вместо графических элементов текстовые.
    • При нарезке графического дизайна и подготовке HTML шаблона, необходимо заранее предусмотреть место расположения основных компонентов системы управления сайтом. Выделить области меню, рекламные области, области размещения дополнительных форм. Рекомендуется верстать компоненты независимо от основного шаблона. Чтобы в будущем, при перемещении компонента в другое место или другой шаблон, не поехала верстка.
    • Для удобства отладки верстки, рекомендуется отключить [ds]сжатие и объединение css и js файлов[/ds][di]
      Одним из способов повышения производительности является
      использование штатной функции объединения и сжатия
      css и js файлов, которое включается в настройках
      главного модуля:

      Подробнее …[/di].

    • Размещать графические изображения, относящиеся к шаблону сайта, следует в папке /bitrix/templates/<имя_шаблона>/images.
    • Каскадные стили, используемые в шаблоне, рекомендуется разделять на две таблицы стилей, хранящиеся в двух разных файлах. Оба файла находятся в директории /bitrix/templates/<идентификатор_шаблона>/. Файл styles.css содержит стили для представления информационного содержания страницы на сайте. Файл template_styles.css содержит стили для отображения текстов в самом шаблоне дизайна.

      [dw]В крайнем случае[/dw][di]По «фен шую» это неправильно, так как ресурсы нужно подключать через API, чтобы для них работало объединение в один файл, подключение минифицированных версий, кэширование и т.д[/di] допускается подключение в <head> любое количество стилевых файлов, дополнительно к styles.css и template_styles.css, подключаемым через showhead(). Делается это обычными линками перед закрытием тэга </head>, а дополнительные стилевые файлы положите в любую папку. Эффект будет тот же самый, как если бы вы собрали все ваши дополнительные стили и дописали их в два файла шаблона сайта со стандартными наименованиями.

    Создание шаблона сайта рекомендуется выполнять на локальной демо-версии продукта. Готовый шаблон необходимо экспортировать средствами системы в виде комплекта файлов формата tar.gz на удаленный сервер и развернуть его.

    Примечание: Крайне не рекомендуется использование комплексных компонентов в шаблоне дизайна. Так как в этом случае правила переписывания адресов начинают работать для всего сайта. Это может отражаться на работе [ds]ЧПУ[/ds][di]Правила обработки адресов настраиваются отдельно для каждого сайта и каждое правило должно содержать уникальное в рамках сайта условие выполнения.

    Подробнее …[/di] других компонентов и страницы /404.php. Комплексные компоненты должны находиться в #WORKAREA.

    Как правило, для сайта разрабатывается несколько шаблонов: Для главной, для раздела, для детальной и так далее. Если различия между шаблонами незначительны, то лучше использовать один шаблон:

    • либо с внедрённой в него проверкой того, какая страница сейчас открыта,
    • либо с использованием включаемых областей.

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

    Опыт веб-разработчиков.

    Роман Петров: Мы, например, интегрируем готовую верстку в Битрикс и поэтому в большинстве случаев у нас стили задаются для всего сайта сразу, стили компонентов мы чаще всего удаляем, а сами шаблоны компонентов складываем в .default потому что это сильно экономит время при наличии нескольких шаблонов сайта.
    Мы складываем все шаблоны в .default потому, что если на сайте несколько шаблонов дизайна, очень тяжело вносить правки в каждый из них. Гораздо проще поменять в одном месте.

    Sergey Leshchenko: Значит, я храню в:
    .default — все шаблоны компонентов, все стили всех шаблонов сайтов, картинки/спрайты, js и прочее с преследованием цели минимизации обращений к веб-серверу, но без фанатизма.
    site_template_name/styles.css со стилями для визуального редактора, шаблоны общих компонентов специфичные только под данным шаблоном сайта.

    Евгений Малков: Все стили храню в папке шаблона сайта в одном файле styles.css. Картинки, используемые в css, в /images/ в корне сайта.
    Когда точно знаю, что будет 1 сайт то храню шаблоны компонент в .default, когда несколько сайтов — в шаблоне сайта.
    Иногда, при другом шаблоне для ряда страниц (например для главной), в styles.css этого шаблона подключаю стиль основного шаблона.
    От верстальщика приходит 1 файл стилей на все шаблоны и делить его, особенно для компонент, сложно.

    Включаемые области

    О работе с включаемыми областями смотрите курс Контент-менеджер

    Управление

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

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

    Содержимое включаемых областей хранится в отдельных PHP или HTML файлах. Области для страниц или разделов сохраняются с некоторым суффиксом. Например, в поставляемых файлах продукта в качестве обозначения включаемой области для страницы используется суффикс _inc (например, index_inc.php), а включаемая область для раздела сайта сохраняется в файле с именем sect и добавлением к нему суффикса (например, sect_inc.php).

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

    Подключение областей в шаблоне дизайна сайта выполняется с помощью компонента Вставка включаемой области.

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

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

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

    Если компонент [comp include_main_include]Включаемая область[/comp] расположить в шаблоне дизайна сайта, то информация [dw]из файла[/dw][di]Установка параметра доступна только пользователю с правами на редактирование php файлов: edit_php.[/di] будет выводиться на всем сайте.

    Размещение

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

    • Откройте для редактирования шаблон сайта или страницу в визуальном редакторе.
    • Добавьте компонент Вставка включаемой области (bitrix:main.include) в шаблон сайта (или в тело страницы) и настройте его параметры.

    Использование включаемых областей при интеграции в дизайн имеет ограничение. Оно связано с размером, отведенным под ячейку, в которой размещен компонент. Если вводимый текст, картинка, либо что-то еще по своим размерам больше, чем отведенное компоненту место, то дизайн «поедет», то есть исказится.

    Использование включаемых областей позволяет управлять не только текстом. Можно поместить в эту область картинку вместо текста (или компонент Случайное фото) и получить индивидуальный вид каждого раздела. При этом индивидуальность будет «динамическая», изменяемая.

    Создание и редактирование

    Создание включаемых областей может быть выполнено:

    • из административного раздела в Менеджере файлов (Контент > Структура сайта > Файлы и папки), создав файл с соответствующим именем;
    • из публичного раздела сайта в режиме правки. В тех местах, где предполагается вывод включаемых областей, будут показаны иконки для быстрого перехода к созданию этих областей.

      Примечание: Файл включаемой области будет создан и назван в соответствии указанным в настройках компонента суффиксом — для опции для раздела, или именем файла — для опции из файла.

      После выбора команды Добавить область будет запущен визуальный редактор для создания содержимого включаемой области. При выборе команды Добавить область как PHP станет возможным добавление области в режиме РНР кода

    Аналогично перейти к редактированию включаемых областей можно:

    • непосредственно [dw]из публичного раздела[/dw][di][/di] сайта в режиме правки;
    • либо из административного раздела, открыв для редактирования соответствующий файл в Менеджере файлов.

    Пример. Использование Включаемых областей

    Задача: Сайт разделен на несколько разделов. По замыслу у каждого раздела должна быть своя «шапка» в дизайне. Более в дизайне ничего не меняется. Как лучше реализовать смену «шапок» разделов?

    Решение: В шаблон подключается компонент «Включаемая область (для раздела)»:

    <div id="header">
    <?$APPLICATION->IncludeComponent("bitrix:main.include", ".default", array(
       "AREA_FILE_SHOW" => "sect",
       "AREA_FILE_SUFFIX" => "headerinc",
       "AREA_FILE_RECURSIVE" => "Y",
       "EDIT_TEMPLATE" => "sect_headerinc.php"
       ),
       false
    );?>
    </div>

    Код шапки каждого из разделов будет храниться в файле sect_headerinc.php. Параметр "AREA_FILE_RECURSIVE" => "Y" означает, что такая же «шапка» появится у всех подразделов данного раздела, если родительский sect_headerinc.php не будет специально перекрыт у кого-то из нижележащих разделов.

    Цепочка навигации

    Цепочка навигации — последовательный список ссылок на разделы и страницы сайта, который показывает уровень «погружения» текущей страницы в структуру сайта.

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

    Цепочка навигации помогает посетителю легко ориентироваться на сайте: быстро вернуться на главную страницу или подняться на один (или более) уровень вверх, т.е. перейти из подраздела в раздел.

    Описание построения простейшей цепочки навигации без кастомизации приведено в курсе [ds]Контент-менеджер[/ds][di]Сайты зачастую обладают сложной структурой и несколькими уровнями вложенности. Не потеряться на таком сайте поможет цепочка навигации.

    Подробнее …[/di].

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

    Управление навигационной цепочкой

    Управление пунктами

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

    Переход к форме настройки свойств раздела можно осуществить из:

    • [ds]публичного раздела[/ds][di]Сайты зачастую обладают сложной структурой и несколькими уровнями вложенности. Не потеряться на таком сайте поможет цепочка навигации .

      Подробнее …[/di]

    • [ds]административного раздела[/ds][di]Управление свойствами страницы в Административном разделе, независимо от выбранного редактора, осуществляется в форме редактирования страницы в таблице

      Подробнее …[/di].

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

    Примечание: Чтобы ссылка на какой-либо раздел не выводилась в навигационной цепочке сайта, нужно удалить название раздела из поля Заголовок и сохранить внесенные изменения.

    Отдельные компоненты могут также добавлять в навигационную цепочку заголовок текущей страницы сайта или, например, заголовок текущей новости или товара каталога. Так, например, комплексный компонент Новости (bitrix:news) последовательно добавляет в навигационную цепочку названия каталогов и групп новостей по мере погружения вглубь по уровням, если это установлено в его настройках.

    Аналогичным образом в навигационную цепочку добавляются названия форумов и тем форумов. В этом случае значение пункта навигационной цепочки для данной страницы определяется непосредственно в сущности.

    Управление показом пунктов

    Показ навигационной цепочки может быть отключен на определенных страницах или в определенном разделе сайта. Управление отображением навигационной цепочки также осуществляется с помощью свойств страницы (раздела). Для этого необходимо:

    • На странице настроек модуля Управление структурой, секция Настройки для сайтов, создать свойство для страниц Не показывать навигационную цепочку с кодом not_show_nav_chain.

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

    Управление шаблоном

    Цепочка навигации подключается в шаблоне дизайна сайта с помощью компонента [comp include_breadcrumb]Навигационная цепочка[/comp]. Для него может быть создано любое количество шаблонов, т.е. внешних видов. Все они хранятся в папке компонента /bitrix/components/bitrix/breadcrumb/templates/<название шаблона>/. Все созданные шаблоны будут отображаться в настройках компонента. Таким образом для каждого шаблона сайта может быть установлен свой шаблон оформления компонента цепочки навигации. Структура шаблона показа навигационной цепочки аналогична структуре шаблона показа меню.

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

    Примеры работы с навигационной цепочкой

  • Как добавить свой пункт в цепочку навигации?
  • На главной не отображается цепочка навигации
  • Повторение пунктов в цепочке навигации
  • Как сделать, чтобы в навигационной цепочке присутствовал только физический раздел?
  • Как добавить свой пункт в цепочку навигации?

    С помощью функции AddChainItem() :

    <?
    $APPLICATION->AddChainItem("Форум &quot;Отзывы&quot;", "/ru/forum/list.php?FID=3");
    ?>
    

    На главной не отображается цепочка навигации

    Цитата: В шаблоне сайта в цепочке навигации выводится название только инфоблоков. Заголовки страниц (допустим контакты) не выводит, заголовок «главная» тоже не отображается.
    Подскажите что не так?!

    Для главной страницы необходимо в свойствах страницы в поле NOT_SHOW_NAV_CHAIN установить значение N.

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

    Главная страница может также не отображаться по причине неправильно установленной опции Номер пункта, начиная с которого будет построена навигационная цепочка в параметрах компонента Навигационная цепочка: 0 (значение по умолчанию) означает, что построение навигационной цепочки начнется от корня сайта. В случае если заполнено поле Путь, для которого будет построена навигационная цепочка, то номер пункта считается в указанном пути.

    Повторение пунктов в цепочке навигации

    Цитата: Не подскажете, как так получается, что в каталоге в цепочке навигации повторяется дважды название каталога:

    Главная / Магазин / Видеонаблюдение / Видеонаблюдение

    Первая ссылка на каталог выглядит так:
    /catalog/video/

    а вторая:
    /catalog/video/section.php?SECTION_ID=0

    Первое название берется из свойств директории video (файл .section.php), а второе — компонентом, расположенном на странице (в данном случае адрес страницы section.php).

    Примечание: Например, у компонента Новости (bitrix:news) в его параметрах присутствуют соответствующие опции: Включать инфоблок в цепочку навигации и Включать раздел в цепочку навигации.

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

    Как сделать, чтобы в навигационной цепочке присутствовал только физический раздел?

    Используется комплексный компонент «Новости» для вывода раздела из инфоблока. В навигационной цепочке помимо физического раздела, появляется некликабельное название раздела самого инфоблока. В настройках ни раздел, ни инфоблок не включен в НЦ.

    Решение: Добавьте в шаблон строку:

    "ADD_SECTIONS_CHAIN" => $arParams["ADD_SECTIONS_CHAIN"],

    Меню сайта

    Меню — это элемент дизайна, являющийся основным средством навигации по сайту.

    Общие понятия о [ds]работе с меню[/ds][di]Разработка эффективного, то есть заметного, красивого, понятного и логичного меню — важнейшая задача контент-менеджера.

    Подробнее …[/di] даются в курсе Контент-менеджер. На основе меню строится карта сайта. Подробный пример создания карты сайта приведен в курсе [ds]Контент-менеджер[/ds][di]Карта сайта — это отдельная страница, на которой содержатся ссылки на все остальные разделы и страницы в иерархическом порядке. С неё возможен переход в любую точку сайта за минимальное число переходов.

    Подробнее …[/di].

    Типы меню

    Тип меню – принцип организации меню. По умолчанию в дистрибутиве используется два типа меню: Верхнее (как главное) и Левое (как меню разделов).

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

    В самом общем случае на сайте существует одно «основное» меню (1), соответствующее самому верхнему уровню иерархии и отображаемое во всех разделах сайта. Также в системе часто используется «второстепенное» меню (или меню второго уровня) (2 и 3), включающее ссылки на подразделы и документы текущего раздела.

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

    Примечание: Если необходимо, чтобы в нижележащем разделе просто не отображалось вышестоящее меню, создайте меню в нужном разделе без создания в нем пунктов меню.

    Меню разделов, как правило, создается для каждого раздела свое и транслируется на все страницы раздела. При необходимости в подразделах можно создать свое собственное меню и применить к нему собственный тип.

    Типы используемых на сайте меню задаются из административного раздела на странице настроек модуля Управление структурой.

    Например, пусть в системе используются два типа меню:

    • левое меню – тип left;
    • верхнее (основное) меню – тип top.

    Тип меню, заданный в настройках модуля Управления структурой, будет использован как префикс файла меню, а также для идентификации файлов с пунктами меню (например, .top.menu.php).

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

    Примечание: Типы меню могут быть заданы отдельно для каждого сайта.

    Типы меню задаются произвольно (только символами латинского алфавита). Однако для упрощения управления меню рекомендуется давать типам меню значимые имена. Например, top, left, bottom.

    Построение и показ меню

    В общем случае задача формирования меню включает:

    • выделение HTML элементов для построения меню;
    • создание шаблона меню (создание шаблона компонента Меню);
    • включение функции показа меню (вызов компонента Меню) в общем шаблоне («прологе» и «эпилоге»);
    • заполнение меню в соответствии со структурой сайта.

    Структура меню

    Любое меню на сайте строится на основе двух составляющих:

    • массива данных $aMenuLinks, определяющего состав меню, задает названия и ссылки для всех пунктов меню. Управление массивом данных осуществляется через административный интерфейс;
    • шаблона внешнего представления меню. Шаблон меню – это PHP код, определяющий внешний вид меню (шаблон компонента Меню). Шаблон меню обрабатывает массив данных, выдавая на выходе HTML-код.

    Массив данных меню

    Данные для каждого типа меню хранятся в отдельном файле, имя которого имеет следующий формат: .<тип меню>.menu.php. Например, для хранения данных меню типа left будет использоваться файл .left.menu.php, а для хранения данных меню типа top — файл .top.menu.php.

    Меню является иерархически наследуемым. Файлы меню размещаются в папках тех разделов сайта, где требуется показ соответствующих типов меню. Если для данного раздела не создан соответствующий файл меню, система производит поиск файла в каталоге уровнем выше.

    Например, т.к. основное меню (в демо-версии продукта, это меню типа top) должно выводиться во всех разделах, то файл данного меню помещается только в корневой каталог сайта.

    Соответственно меню второго уровня (в демо-версии продукта, это меню left) выводится отдельно для каждого раздела сайта. Поэтому в папке каждого раздела создается свой файл для данного типа меню.

    Ещё пример: посетитель находится в разделе /ru/company/about/. Для показа меню типа left файл меню ищется системой в следующей последовательности:

    1. /ru/company/about/.left.menu.php
    2. /ru/company/.left.menu.php
    3. /ru/.left.menu.php
    4. /.left.menu.php

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

    Система Bitrix Framework позволяет также создавать [dw]меню динамического типа[/dw][di]Для этого требуется в компоненте Меню включить опцию Подключать файлы с именами вида .тип_меню.menu_ext.php («USE_EXT» => «Y»), которая по умолчанию выключена.[/di]. Т.е. массив данных таких меню генерируется автоматически на основании некоторых данных, получаемых с помощью программного кода. Данный код должен храниться в папке соответствующего раздела сайта в файле с именем .<тип меню>.menu_ext.php.

    Основная задача подобных файлов — это манипуляция массивом $aMenuLinks. Данные файлы не редактируются визуально в модуле Управление структурой, поэтому они не смогут быть случайно отредактированы при визуальном редактировании меню. При создании этого файла используйте компонент Пункты меню (bitrix:menu.sections).

    Примечание: В абзаце выше речь идёт только о дополнении созданного меню названиями разделов инфоблоков. Например, для дополнения меню названиями форумов этот вариант не годится.

    Внимание! Если в качестве пунктов меню используются разделы каталога без ЧПУ, необходимо указывать переменные в значимых переменных запроса.

    Примером такого меню может служить левое меню раздела Каталог книг, представленное в старой демо-версии продукта. Здесь первые два пункта меню Авторы и Рецензии созданы обычным способом, а остальные (Бизнес-литература, Детская литература и т.д.) формируются динамически.

    Динамическое меню

    В данном случае в качестве пунктов меню используются названия групп каталога Книги, созданного на основе информационных блоков. Программный код, на основе которого генерируется меню, хранится в файле .left.menu_ext.php в папке /e-store/books/.

    В файлах .<тип меню>.menu.php могут использоваться следующие стандартные переменные:

    • $sMenuTemplate — абсолютный путь к шаблону меню (данная переменная используется крайне редко);
    • $aMenuLinks — массив, каждый элемент которого описывает очередной пункт меню.

      Структура данного массива:

      Array
      (
          [0] => пункт меню 1
              Array
                  (
                      [0] => заголовок пункта меню
                      [1] => ссылка на пункте меню
                      [2] => массив дополнительных ссылок для подсветки пункта меню:
                          Array
                              (
                                  [0] => ссылка 1
                                  [1] => ссылка 2
                                  ...
                               )
                      [3] => массив дополнительных переменных передаваемых в шаблон меню:
                          Array
                              (
                                  [имя переменной 1] => значение переменной 1
                                  [имя переменной 2] => значение переменной 2
                                  ...
                               )
                      [4] => условие, при котором пункт меню появляется 
                             это PHP выражение, которое должно вернуть "true"
                  )
          [1] => пункт меню 2
          [2] => пункт меню 3
          ...
      )

    Примеры файлов меню

    <?
    // пример файла .left.menu.php
    
    $aMenuLinks = Array(
      Array(
        "Каталог курсов", 
        "index.php",
        Array(), 
        Array(), 
        "" 
      ),
    
      Array(
        "Мои курсы",
        "mycourses.php",
        Array(), 
        Array(), 
        "$GLOBALS['USER']->IsAuthorized()" 
      ),
      
      Array(
        "Журнал обучения",
        "gradebook.php",
        Array(), 
        Array(), 
        "$GLOBALS['USER']->IsAuthorized()"  
      ),
    
      Array(
        "Анкета специалиста",
        "profile.php",
        Array(), 
        Array(), 
        "$GLOBALS['USER']->IsAuthorized()"  
      ),
    );
    ?>
    <?
    // пример файла .left.menu_ext.php
    
    if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    global $APPLICATION;
    
    $aMenuLinksExt = $APPLICATION->IncludeComponent(
    	"bitrix:menu.sections",
    	"",
    	Array(
    		"ID" => $_REQUEST["ELEMENT_ID"], 
    		"IBLOCK_TYPE" => "books", 
    		"IBLOCK_ID" => "30", 
    		"SECTION_URL" => "/e-store/books/index.php?SECTION_ID=#ID#", 
    		"CACHE_TIME" => "3600" 
    	)
    );
    
    $aMenuLinks = array_merge($aMenuLinks, $aMenuLinksExt);
    ?>

    Организация показа меню

    Показ меню на страницах сайта выполняется с помощью компонента Меню (bitrix:menu). Например, вызов верхнего меню на демо-сайте имеет следующий вид:

    <?$APPLICATION->IncludeComponent(
    "bitrix:menu",
    "horizontal_multilevel",
    Array(
    "ROOT_MENU_TYPE" => "top",
    "MAX_LEVEL" => "3",
    "CHILD_MENU_TYPE" => "left",
    "USE_EXT" => "Y"
    )
    );?>

    Данный код помещается в предусмотренные для вывода меню области шаблона сайта.

    Построение меню сайта

    Построение меню для показа происходит следующим образом:

    • в общий шаблон показа включается вызов вывода меню на экран;
    • при загрузке компонент проверяет наличие в текущем разделе сайта файла, содержащего массив значений для меню;
    • затем компонент вызывает шаблон построения для данного типа меню и выводит HTML меню на экран.

    Шаблоны меню

    Вывод данных в компоненте Меню реализован с помощью шаблонов. Шаблоны могут создаваться пользователями самостоятельно.

    Системные шаблоны

    В дистрибутиве по умолчанию включены [dw]несколько шаблонов[/dw][di][/di]. Описания некоторых из них:

    • Default (Вертикальное меню) – шаблон для вертикального меню. Самый простой шаблон. При выборе в параметрах компонента глубины вложения более 1 вы увидите список страниц сайта в общей иерархии. То есть индексная страница сайта (раздела) будет размещена на одном уровне с вложенной страницей (разделом). Это затрудняет осмысление структуры сайта посетителем. Поэтому шаблон рекомендуется для простых видов меню и главных меню верхнего уровня. Шаблон достаточно прост для кастомизации под конкретный дизайн.

    • Tree (Древовидное меню) – шаблон для вертикального меню. Реализует меню в виде древовидной структуры аналогично Проводнику Windows. Вложенные страницы показаны в виде страниц в папке (разделе), что существенно облегчает понимание пользователем структуры сайта. Это меню не всегда удобно при разветвленной структуре интернет-проекта, так как существенно растягивает по вертикали колонку, где оно расположено.

    • Vertical_multilevel (Вертикальное многоуровневое выпадающее меню) – шаблон для вертикального меню. Реализует меню с выпадающими пунктами меню нижнего уровня, что сохраняет легкость восприятия структуры сайта посетителем, характерное для древовидного меню, но при этом не растягивает дизайн при разветвленной структуре.

    • Grey_tabs (Серое меню в виде закладок) и Blue_tabs (Голубое меню в виде закладок) – шаблоны для горизонтального меню. Отличаются только внешним видом. Это самые простые шаблоны, аналогичные шаблону по умолчанию (default) для вертикального меню.

    • Horizontal_multilevel (Горизонтальное многоуровневое выпадающее меню) – шаблон для горизонтального меню. Аналогично Vertical_multilevel (Вертикальному многоуровневому выпадающему меню) и реализует меню с выпадающими пунктами меню нижнего уровня.

    Создание шаблонов меню

    Выделение HTML элементов для построения меню

    Создание шаблонов меню начинается с выделения необходимых HTML областей в шаблоне сайта:

    • неизменной верхней и нижней части шаблона;
    • повторяющихся элементов. Например, для горизонтального меню – это ячейки таблицы, а для вертикального – строки.

    Создание шаблона меню

    Все шаблоны меню имеют одинаковую структуру:

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

    В php шаблоне для вывода меню используется массив $arItem — копия массива пунктов меню, в котором каждый пункт в свою очередь представляет собой массив, использующий следующие параметры:

    • TEXT — заголовок пункта меню;
    • LINK — ссылка на пункте меню;
    • SELECTED — активен ли пункт меню в данный момент, возможны следующие значения:
      • true — пункт меню выбран;
      • false — пункт меню не выбран;
    • PERMISSION — право доступа на страницу, указанную в LINK для текущего пользователя. Возможны следующие значения:
      • D — доступ запрещён;
      • R — чтение (право просмотра содержимого файла);
      • U — документооборот (право на редактирование файла в режиме документооборота);
      • W — запись (право на прямое редактирование);
      • X — полный доступ (право на прямое редактирование файла и право на изменение прав доступа на данный файл);
    • ITEM_TYPE — флаг, указывающий на тип ссылки, указанной в LINK, возможны следующие значения:
      • D — каталог (LINK заканчивается на «/»);
      • P — страница;
      • U — страница с параметрами;
    • ITEM_INDEX — порядковый номер пункта меню;
    • PARAMS — ассоциативный массив параметров пунктов меню. Параметры задаются в расширенном режиме редактирования меню.

    Рассмотрим построение шаблона меню на примере Левого меню, представленного в демо-версии продукта (шаблон .default компонента Меню):

    <?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
       <?if (!empty($arResult)):?>
          <ul class="left-menu"> 
          <?foreach($arResult as $arItem):?>
              <?if($arItem["SELECTED"]):?>
                   <li><a href="<?=$arItem["LINK"]?>" class="selected">
                   <?=$arItem["TEXT"]?></a></li>
              <?else:?>
                   <li><a href="<?=$arItem["LINK"]?>">
                   <?=$arItem["TEXT"]?></a></li>
              <?endif?>
          <?endforeach?>
          </ul>
       <?endif?>

    Повторяющаяся часть меню, выделенная на предыдущем шаге, выносится в тело шаблона меню.

    При создании шаблона меню потребуется также создать дополнительные стили в таблице стилей (CSS). Например, для текстового меню: цвет пункта меню и цвет текущего (активного) пункта меню.

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

    Примечание: Все шаблоны меню хранятся в папке компонента: /bitrix/components/bitrix/menu/templates/.

    Быстрый доступ к редактированию шаблона каждого типа меню можно осуществить в режиме Правки с помощью пункта Редактировать шаблон компонента меню команд кнопки по управлению компонентом.

    Примечание: Шаблон меню, если он является системным, перед изменением необходимо скопировать в текущий шаблон сайта.

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

    Управление меню

    Управление меню выполняется как с помощью средств административного, так и публичного раздела. Про работу с меню из публичного раздела читайте в курсе Контент-менеджер.

    Создание меню

    Перейти к созданию меню раздела из административного раздела можно с помощью команды Добавить меню кнопки Добавить, расположенной на контекстной панели Менеджера файлов:

    Меню будет создано для раздела, папка которого открыта в данный момент в Менеджере файлов.

    Примечание: В результате данных операций создается файл данных меню с именем .<тип_ меню>.menu.php. Однако в Менеджере файлов имя файла данных автоматически представляется в виде ссылки Меню типа «<тип_меню>«.

    Редактирование меню

    Примечание: При редактировании меню выполняется изменение файла .<тип_ меню>.menu.php (например, .top.menu.php). Однако работа с данным файлом ведется через специальный интерфейс системы. Это позволяет исключить необходимость работы непосредственно с программным кодом и дает возможность редактировать пункты меню в визуальном режиме.

    Перейти к редактированию меню из административного раздела можно открыв на редактирование файл соответствующего меню в Менеджере файлов.

    В системе предусмотрено два режима редактирования меню (переключение между ними выполняется с помощью соответствующей кнопки, расположенной на контекстной панели страницы редактирования меню):

    • упрощенный режим редактирования;

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

    • расширенный режим редактирования.

      В этом режиме для управления доступны следующие данные:

      • тип редактируемого меню;
      • шаблон, на основе которого будет генерироваться меню (поле используется в случае, если создаваемое меню должно генерироваться на основе шаблона, отличного от используемого по умолчанию);
      • название пункта меню;
      • ссылка для перехода;
      • индекс сортировки;
      • набор дополнительных ссылок, которые соответствуют этому же пункту меню. В данном поле задается набор ссылок на страницы, при переходе на которые будет также подсвечиваться данный пункт меню. Например, чтобы при просмотре любой страницы раздела Каталог книг подсвечивался пункт меню Каталог книг, в данном поле нужно указать ссылку на папку, содержащую все страницы раздела (или перечислить необходимые страницы): /e-store/books/;
      • условия показа. Например, позволяет внести ограничения на показ данного пункта меню пользователям с определенными правами доступа;
      • дополнительные параметры – набор произвольных параметров, которые могут быть обработаны в шаблоне показа меню и представлены соответствующим образом. Например, если пункт меню является заголовком секции, это может быть указано в параметрах пункта так: название параметра — SEPARATOR, значение — Y. При разработке шаблона можно проверять значение этого параметра и при показе выделять данный пункт меню разделителем.

        Параметры хранятся в ассоциированном массиве $PARAMS в виде пар имя => значение. При построении меню по шаблону, в самом шаблоне может быть добавлена проверка параметра, например:

        if ($PARAMS["MY_PARAM"]=="Y")
        

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

    Примеры создания меню

    Описанные ниже примеры можно повторить в Виртуальной лаборатории, выбрав при установке шаблон «Демо-сайт для разработчиков».

    Предварительные операции

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

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

    В параметрах компонента, для примера, в опции Тип меню для первого уровня укажем Главное меню.

    Примечание: По умолчанию этот компонент уже настроен для вывода Меню раздела, поэтому операция по созданию меню и его настройке будет пропущена.

    Древовидное меню

    Древовидное меню – самое распространенное меню. Оно достаточно простое и вместе с этим информативное. Создается на базе статических и динамических элементов: разделов, страниц и инфоблоков.

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

    Одноуровневое древовидное меню

    Задача: создать древовидное меню.

    • Создайте дополнительный тип меню: Подменю (podmenu).
    • В параметрах компонента установите следующие параметры:
      • Шаблон компонента — tree (Встроенный шаблон);
      • Тип меню для первого уровня — Главное меню;
      • Тип меню для остальных уровней — Подменю;
      • Уровень вложенности меню — 2.

    • Перейдите в раздел test_menu и создайте в нем 2 страницы: novaya_stranitsa_1 и novaya_stranitsa_2. При их создании необходимо отметить опцию Добавить пункт меню и выбрать меню типа Подменю.

    Результат. Результатом этой работы будет меню такого вида:

    Файл .podmenu.menu.php будет иметь следующую структуру:

    <?
    $aMenuLinks = Array(
    	Array(
    		"Новая страница 1", 
    		"/test_menu/novaya_stranitsa_1.php", 
    		Array(), 
    		Array(), 
    		"" 
    	),
    	Array(
    		"Новая страница 2", 
    		"/test_menu/novaya_stranitsa_2.php", 
    		Array(), 
    		Array(), 
    		"" 
    	)
    );
    ?> 
    

    Многоуровневое древовидное меню

    Шаблоны компонента Меню поддерживают создание многоуровневого меню с глубиной вложения до 4-х уровней. Покажем это на примере. Выполнять работу на данном этапе удобнее в административной части.

    Задача: создать четырехуровневое меню.

    Решение. Решение осуществим на примере уже созданного выше меню с шаблоном tree.

    • Перейдите в административный раздел на страницу Управление структурой (Структура сайта > Файлы и папки)
    • В созданном ранее разделе /test_menu с помощью команды Добавить папку кнопки Добавить контекстной панели создайте новый каталог со следующими параметрами:
      • Имя папки — test_1;
      • Название раздела — test_1;
      • Тип меню — Подменю;
      • Название пункта — test_1.

      Примечание: Для полноты картины можно создать в папках по дополнительной странице (кроме индексной). При создании их не забывайте добавить их в тип меню – Подменю.

    • Перейдите во вновь созданный раздел и создайте по описанному выше алгоритму папку /test_2.

      Примечание: Если в последней по вложенности папке не создать файл меню, то в публичной части сайта она будет отображаться как страница, а не папка.

    • При необходимости добавления новых пунктов или редактирования существующих перейдите в нужный раздел (например, /test_menu) и на контекстной панели используйте команду Добавить меню кнопки Добавить. В открывшейся форме выберите тип меню равный Подменю, после чего отредактируйте нужные пункты меню.

      Примечание: При удалении страниц и разделов из административной части потребуется последующее редактирование пунктов меню вручную.

    Результат: результатом работы будет созданное древовидное меню в четыре уровня:

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

    Если требуется уровень вложенности более, чем по умолчанию

    Выпадающее меню

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

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

    Меню из информационных блоков

    Построение меню из динамических элементов — информационных блоков — позволяет снять с контент-менеджера часть нагрузки по поддержке сайта. Не нужно будет выполнять работы по актуализации меню в связи с появившимися новыми разделами и страницами. Для решения этой задачи необходимо использовать компонент Пункты меню (bitrix:menu.sections).

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

    • Выполните команду Редактировать параметры компонента из меню компонента Меню.
    • В разделе Дополнительные настройки установите флажок в поле Подключать файлы с именами вида .тип_меню.menu_ext.php.
    • Сохраните внесенные изменения.
    • Перейдите в административную часть сайта.
    • Перейдите в раздел, для которого планируется создавать меню с помощью инфоблоков.
    • Создайте пустой файл под именем .podmenu.menu_ext.php в директории, в меню которой должны подключаться пункты инфоблока (например, /test_menu).
    • Примечание: Первая часть имени файла должна совпадать с названием меню, для которого применяется данный компонент. То есть для верхнего меню первая часть должна называться .top, для любого другого меню первая часть должна называться .luboe_drugoe.

    • Откройте файл для редактирования в визуальном редакторе.
    • Добавьте в тело файла компонент Пункты меню (bitrix:menu.sections).
    • Настройте параметры компонента:

    • Выберите тип информационного блока и сам информационный блок (например, Каталог товаров).
    • Установите глубину вложений любую, более 1.
    • Сохраните внесенные изменения. Форма создания файла закроется, файл появится в общем списке файлов папки.
    • Откройте его вновь для редактирования, но уже в режиме Редактировать как PHP.
    • Допишите в файле код проверки включения кода из ядра:
      <? 
      if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
      global $APPLICATION; 
      $aMenuLinksExt = $APPLICATION->IncludeComponent(…
          
    • После вызова компонента допишите код подключения к меню:
      $aMenuLinks = array_merge($aMenuLinks, $aMenuLinksExt);
    • Конечный код указанного файла должен быть примерно таким:

      <?
      if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
      global $APPLICATION; 
      $aMenuLinksExt = $APPLICATION->IncludeComponent(
      	"bitrix:menu.sections",
      	"",
      	Array(
      		"ID" => $_REQUEST["ID"], 
      		"IBLOCK_TYPE" => "books", 
      		"IBLOCK_ID" => "5", 
      		"SECTION_URL" => "/catalog/phone/section.php?", 
      		"DEPTH_LEVEL" => "1", 
      		"CACHE_TYPE" => "A", 
      		"CACHE_TIME" => "3600" 
      	)
      );
      $aMenuLinks = array_merge($aMenuLinks, $aMenuLinksExt);
      ?>
          
    • Сохраните внесенные изменения.

    Результат: Перейдите в публичную часть в раздел, для которого создавался файл .тип_меню.menu_ext.php. [dw]Вы увидите[/dw][di]Если вы ранее не отключали кеш, то, возможно, нужно сбросить кеш, чтобы меню отобразилось.[/di] созданное новое меню:

    Красным цветом выделены пункты, созданные в рамках главного меню (массив $aMenuLinks, возвращающий пункты главного меню), синим цветом – полученные из инфоблоков (массив $aMenuLinksExt).

    Два меню рядом

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

    • Перейдите к редактированию шаблона сайта.
    • В режиме редактирования кода в левой колонке сайта создайте таблицу с одной строкой и двумя ячейками.
    • Переместите в левую ячейку компонент меню, использовавшийся в качестве верхнего меню, а в правую – компонент меню, использовавшийся в качестве левого меню.
    • Сохраните внесенные изменения.
    • Перейдите в публичную часть сайта.
    • Выполните команду Редактировать параметры компонента из меню компонента меню, стоящего в левой ячейке.
    • Задайте в качестве шаблона шаблон default.
    • В поле Уровень вложенности меню выставьте значение 1.
    • Сохраните внесенные изменения.
    • Создайте пункты в меню типа top (которое в левой ячейке).
    • Выполните команду Редактировать параметры компонента из меню компонента меню, стоящего справа.
    • Настройте его так, как мы настраивали в примере построения меню из инфоблоков.
    • Выполните команду Редактировать пункты меню для компонента меню, стоящего в правой ячейке.
    • Сохраните внесенные изменения.

    После перезагрузки вы увидите простое меню по умолчанию. При переходе во внутренние разделы справа от основного меню будут раскрываться меню активного раздела в зависимости от его настроек (статичное или динамическое, из инфоблоков).

    Графическое меню

    Иногда встает задача создания меню с графическими элементами в качестве пунктов меню. Эту задачу можно решить разными способами.

    Самый простой – за счет редактирования файла CSS. Этот способ имеет преимущество в том плане, что текст, используемый в компонентах меню для разных пунктов, будет редактироваться обычным способом. Изображение, выводимое css, будет фоновым. Так как данный способ не относится непосредственно к Bitrix Framework, то он рассмотрен не будет.

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

    Задача: создать меню, где пункты оформляются выводом картинок. Отображение картинок должно быть различным: для активного пункта меню и пассивного пункта меню. Если по каким-то причинам картинки меню не загружены на сайт (либо для пункта меню не заданы параметры), должна отображаться картинка по умолчанию.

    • Создайте по паре картинок на каждый из пунктов меню и картинку для пунктов без параметров или без картинки.
    • Создайте папку /images/menu в рамках структуры сайта.
    • Загрузите в эту папку созданные картинки.
    • Примечание: Для одного из пунктов меню картинки не загрузите, чтобы проверить работу кода.

    • Перейдите в административной части сайта в раздел с меню, для которого хотите назначить картинки.
    • Откройте меню для редактирования в Расширенном режиме.

    • В строке Параметры каждого пункта меню в колонке Название введите ACT.
    • В строке Параметры каждого пункта меню в колонке Значение укажите путь до картинки, соответствующей активному состоянию пункта меню, от корня сайта.
    • Примените внесенные изменения. После перезагрузки добавится еще одна строка Параметры.

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

    • Заполните для каждого пункта меню поля Название (NOACT) и Значение (укажите путь до картинки, соответствующей неактивному состоянию пункта меню, от корня сайта).
    • Сохраните внесенные изменения.

    Мы задали параметры для системы, по которым она будет определять какую картинку ей выводить. Теперь переходим к редактированию шаблона. Для простоты будем использовать шаблон default.

    • Перейдите в публичную часть сайта.
    • Выполните команду Редактировать параметры компонента из меню компонента Меню.
    • Назначьте для компонента шаблон default.
    • Перейдите к редактированию шаблона компонента, скопируйте его и откройте для редактирования.
    • Вам нужно заменить код вызова пункта меню со штатного на обращение к параметрам, заданным для данного меню. Делается это в этом участке кода шаблона:

        <?if($arItem["SELECTED"]):?>
      		<li><a href="<?=$arItem["LINK"]?>" class="selected"><?=$arItem["TEXT"]?></a></li>
      	<?else:?>
      		<li><a href="<?=$arItem["LINK"]?>"><?=$arItem["TEXT"]?></a></li>
      	<?endif?>
          
    • Замените первую ссылку в коде на строку:
      <a href="<?=$arItem["LINK"]?>">
      <img src="<?=(array_key_exists("ACT", $arItem["PARAMS"]) 
      && file_exists($_SERVER["DOCUMENT_ROOT"].$arItem["PARAMS"]["ACT"]) ? 
      $arItem["PARAMS"]["ACT"] : "/images/menu/default.png")?>" /></a> 
    • Замените вторую ссылку в коде на строку:
      <a href="<?=$arItem["LINK"]?>">
      <img src="<?=(array_key_exists("NOACT", $arItem["PARAMS"]) 
      && file_exists($_SERVER["DOCUMENT_ROOT"].$arItem["PARAMS"]["NOACT"]) ?
      $arItem["PARAMS"]["NOACT"] : "/images/menu/default.png")?>" /></a> 
    • Строки различаются между собой только выбором параметра из расширенных настроек: ACT или NOACT.

      Примечание: В коде использован путь /images/menu/default.png для указания картинки, выводимой, если отсутствует картинка для пункта меню или если для него не указаны параметры. Вам нужно использовать собственное имя этой картинки. И другой путь, если вы сохраняете картинки в другой папке.

    • Сохраните внесенные изменения.

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

    Примеры решения частных задач в меню

  • Подсветка ссылок
  • Как сделать, чтобы отдельный пункт меню открывался в новом окне?
  • Отображение пункта меню для определенных групп пользователей
  • Отображение пункта меню неавторизованным пользователям
  • Отображение пункта меню при нахождении в определенном разделе сайта
  • Отображение определенных пунктов только на главной странице и еще в одном внутреннем разделе
  • Осуществление вывода пункта меню, ссылающегося на определенный товар, связанный с товаром, открытым на данной странице
  • Всплывающие подсказки для пунктов меню
  • Как поставить картинки рядом с пунктами меню?
  • Разные картинки-пункты меню для разных языков
  • Отображение собственного, не такого как в шаблоне, меню для определенных разделов сайта
  • Как сделать меню tree в постоянно открытом состоянии?
  • Как сделать так, чтобы при открытии страницы через древовидное меню, само меню не сворачивалось, а показывало, на какой странице ты находишься?
  • Скрытие бокового меню по свойству страницы
  • Меню и кеш
  • Подсветка ссылок

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

    При посещении страницы, указанной в поле Доп.ссылки для подсветки, будет происходить подсветка настраиваемого пункта меню. Путь к страницам задается относительно корня сайта.

    Как сделать, чтобы отдельный пункт меню открывался в новом окне?

    Для этого нужно в расширенном режиме редактирования меню прописать у нужных пунктов следующие дополнительные параметры:

    Название: target 
    Значение: target="_blank"
    

    В шаблоне меню, после вывода элемента меню, необходимо заменить код:

    <a href="<?=$arItem["LINK"]?>"><?=$arItem["TEXT"]?></a>
    

    на следующий:

    <a href="<?=$arItem["LINK"]?>" <?=$arItem["PARAMS"]["target"]?>><?=$arItem["TEXT"]?></a>
    

    Отображение пункта меню для определенных групп пользователей

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

    Тип условия: Для групп пользователей
    Условие: требуемые_группы_пользователей
    

    Отображение пункта меню неавторизованным пользователям

    В расширенном режиме редактирования меню необходимо установить следующее условие:

    Тип условия: выражение PHP
    Условие: !$USER->IsAuthorized()
    

    Отображение пункта меню при нахождении в определенном разделе сайта

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

    Примечание: Условие Для папки и файла лучше всего применять для статических страниц и для динамических страниц с компонентами на ЧПУ. На динамических страницах без ЧПУ он не будет работать, так как производит проверку по части адреса, а в динамических страницах всегда будут присутствовать выбранные значения. Для динамических URL удобнее применять условие Параметр в URL.

    Отображение определенных пунктов только на главной странице и еще в одном внутреннем разделе

    В расширенном режиме в поле условие для нужных пунктов ввести выражение php:

    CSite::InDir('/about/') or CSite::InDir('/index.php')

    Осуществление вывода пункта меню, ссылающегося на определенный товар, связанный с товаром, открытым на данной странице

    Для этого можно использовать тип условия Параметр в URL. Пункт будет отображен на страницах с определенным параметром в URL. Параметр работает с URL, в которых есть символ ?. То есть, с динамическими страницами.

    Страницы, созданные на базе инфоблоков, имеют URL вида: http://сайт/раздел/index.php?SECTION_ID=***. Предположим, что на странице с SECTION_ID=123 должен быть отражен пункт меню, ведущий на страницу SECTION_ID=456.

    Создадим пункт меню, ведущий на страницу http://сайт/раздел/index.php?SECTION_ID=456. В поле Тип условия выберем Параметр в URL, в первом поле Условие укажем SECTION_ID, а во втором 123.

    Всплывающие подсказки для пунктов меню

    Для этого необходимо в расширенном режиме редактирования меню добавить дополнительный параметр A_TITLE, и записать в него содержание всплывающей подсказки.

    Название: A_TITLE
    Значение: текст_всплывающей_подсказки
    

    В шаблоне меню:

    <?if($arItem["SELECTED"]):?>
    		<li><a href="<?=$arItem["LINK"]?>" class="selected"><?=$arItem["TEXT"]?></a></li>
    	<?else:?>
    		<li><a href="<?=$arItem["LINK"]?>"><?=$arItem["TEXT"]?></a></li>
    <?endif?>
    

    нужно заменить первую ссылку в коде на строку:

    <a href="<?=$arItem["LINK"]?>" class="selected" title="<?=$arItem["PARAMS"]["A_TITLE"]?>"><?=$arItem["TEXT"]?></a>
    

    а вторую на:

    <a href="<?=$arItem["LINK"]?>" title="<?=$arItem["PARAMS"]["A_TITLE"]?>"><?=$arItem["TEXT"]?></a>
    

    Как поставить картинки рядом с пунктами меню?

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

    Название: IMG
    Значение: путь_к_картинке
    

    В шаблон меню, после вывода элемента меню, необходимо после строки (в зависимости от шаблона):

    <a href="<?=$arItem["LINK"]?>">
    

    добавить следующее:

    <img src="<?=$arItem["PARAMS"]["IMG"]?>" border="0" />
    

    Разные картинки-пункты меню для разных языков

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

    Решение:

    Укажите в шаблоне меню следующее:

    <body class="lang-<?=LANG?>"> 
    

    в CSS:

    .menu li.item1 { 
    background-image:url(title-en.gif); 
    } 
    .lang-ru .menu li.item1 { 
    background-image:url(title-ru.gif); 
    }
    

    Отображение собственного, не такого как в шаблоне, меню для определенных разделов сайта

    Организовать такое меню можно с помощью смены шаблона сайта (Настройки > Настройки продукта > Сайты > Список сайтов, редактирование сайта, секция Шаблон, условие для папки или файла). Причем, если шаблон не сложный, то можно прямо в коде шаблона поставить проверку и в одном случае выводить одно меню в другом — другое:

    if($APPLICATION->GetCurPage() == "/index.php") {
    вывод меню для главной страницы
    }
    else {
    вывод второго меню
    }
    

    Как сделать меню tree в постоянно открытом состоянии?

    Решение:

    Нужно взять стандартный шаблон меню tree, скопировать его в свой шаблон.
    После чего в файле template.php необходимо строчку:

    <li class="close">
    

    заменить на:

    <li>
    

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

    Как сделать так, чтобы при открытии страницы через древовидное меню, само меню не сворачивалось, а показывало, на какой странице ты находишься?

    Решение:

    Нужно взять стандартный шаблон меню tree, скопировать его в свой шаблон.
    После чего в файле template.php необходимо 14 строчку:

    <li class="close">
    

    заменить на:

    <li <?if (!$arItem["SELECTED"]):?>class="close"<?endif?>>
    

    Примечание: Предложенное решение актуально лишь до 2-го уровня вложенности меню.

    Следующий код, который необходимо вставить в файл шаблона меню, позволяет меню не сворачиваться, при уровне вложенности больше 2:

    ...
    <?if (!empty($arResult)):?>
    
    <?
    //анализ открытых узлов дерева
    $lastLevel = 0;
    $selected = false;
    
    foreach(array_reverse($arResult) as $arItem){
        if ($arItem["SELECTED"]) {
            $lastLevel = $arItem["DEPTH_LEVEL"];
            $selected = true;
        }
        if ($selected and $arItem["DEPTH_LEVEL"] < $lastLevel){
            $arResult[ $arItem["ITEM_INDEX"] ]["SELECTED"] = true;
            $lastLevel--;
        }
    }
    ?>
    
    <div class="menu-sitemap-tree">
    <ul>
    <?$previousLevel = 0;foreach($arResult as $arItem):?>
    ...
    

    Скрытие бокового меню по свойству страницы

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

    Решение:

    Если меню расположено в header’е, то функцию GetProperty использовать нельзя, потому что свойства страницы задаются после подключения header’a. Поэтому меню можно вывести «отложенно» следующим способом:

    • в header’е, где надо показать меню добавить следующий код:
    $APPLICATION->ShowProperty('menu');
    
    • в свойствах страницы, если надо запретить меню:
    $APPLICATION->SetPageProperty('hide_menu', 'Y');
    
    • в footer’е:
    if( 'Y' != $APPLICATION->GetPageProperty('hide_menu') ){
        ob_start();
        echo '<b>проверка отложенного меню!</b>';
        // ....здесь происходит вывод меню компонентом или другим способом.... //
        $APPLICATION->SetPageProperty('menu', ob_get_clean() );
    }
    

    Само меню «выводится» в футере, если свойство hide_menu не установлено в значение Y. Но оно не выводится на самом деле в футере, а отправляется в свойство menu, которое «выше» по коду можно показать «отложенно» через ShowProperty. Если меню запрещено, то в значении свойства menu будет пусто и ничего не покажется в шаблоне. Если не запрещено, то для этого примера — там, где прописано $APPLICATION->ShowProperty('menu'), будет выведена фраза проверка отложенного меню!.

    Меню и кеш

    Цитата: Забился весь диск под завязку. Стал разбираться, и оказалось, что папка bitrix/managed_cache/MYSQL/menu весит 16Гб! На сайте присутствуют 2 меню горизонтальное — навигация по страницам сайта и вертикальное — по разделам каталога.

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

    Рекламные области

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

    Показ рекламы выполняется в специально выделенных областях шаблона дизайна сайта – рекламных областях.

    Подключение баннера в рекламной области может выполняться с помощью компонента Баннер (bitrix:advertising.banner). Компонент выводит баннер заданного типа.

    Примечание: Данный компонент не учитывает таргетинг по ключевым словам. Если необходимо его учесть, то следует использовать функцию $APPLICATION->ShowBanner().

    Код подключения баннера в рекламной области с помощью PHP функции ShowBanner() имеет вид:

    <?
    //---Пример размещения рекламной области в левой части сайта.
    //---Аналогично производится выбор любого другого типа:
    //---TOP, BOTTOM, COUNTER,… (первый параметр функции)
    //---Могут быть использованы как предустановленные, так и задаваемые
    //---пользователем типы.
    //---В качестве двух дополнительных параметров может указываться
    //---HTML-код обрамляющий рекламную область сверху и снизу.
    $APPLICATION->ShowBanner("LEFT", '<div align="center">', '<br></div><br>');
    ?>

    Для каждой рекламной области определяется, реклама какого типа будет доступна для показа в данной области. На приведённой выше иллюстрации это рекламные баннеры типа LEFT и BOTTOM.

    Список ссылок по теме:

    • Описание модуля Реклама в курсе Администратор. Модули.

    Типы рекламы

    Тип рекламы — это условное название группы рекламных баннеров, обладающих некоторым общим признаком. Например:

    • место показа – все баннеры должны показываться в определенном месте страницы сайта;
    • тематика (например, рекламируется один товар);
    • рекламодатель (реклама определенной компании);
    • и т.д.

    Название типа рекламного блока задается администратором произвольно. Например, для верхней рекламы может быть установлен тип TOP, MAIN, BOTTOM и т.п.

    Важно! Рекомендуется, чтобы баннеры из одной группы имели одинаковый размер. Это позволит избежать деформации страницы при показе рекламы.

    Управление типами рекламы выполняется через административный интерфейс модуля Рекламы (Маркетинг > Баннерная реклама > Типы баннеров):

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

    Механизм управления показом с помощью ключевых слов

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

    С помощью ключевых слов можно:

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

    Механизм управления

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

    • ключевые слова баннера;
    • ключевые слова страниц сайта:
      • желательные (desired): если для страницы сайта заданы желательные ключевые слова, то для показа на данной странице будут доступны все баннеры, в наборе ключевых слов которых содержится хотя бы одно желательное ключевое слово страницы.
      • обязательные (required): если для страницы сайта заданы обязательные ключевые слова, то для показа на данной странице будут доступны все баннеры, в наборе ключевых слов которых содержатся все обязательные ключевые слова страницы.

    Если система не найдет баннеры, удовлетворяющие каким-либо из ключевых слов, то на странице будут показаны баннеры, для которых ключевые слова не заданы. Отбор и показ данных баннеров осуществляется при этом на основе стандартного алгоритма системы (разрешенных/запрещенных страниц контрактов и баннеров, групп пользователей, типов баннеров и других).

    Общий порядок действий по организации показа баннеров на нужных страницах такой:

    • Определяется, какая реклама будет доступна для показа на тех или иных страницах сайта.
    • В соответствии с поставленными задачами для страниц сайта задаются наборы специальных ключевых слов.
    • В настройках рекламных баннеров задается необходимый набор ключевых слов.

    Ключевые слова баннеров задаются в поле Ключевые слова на странице создания/редактирования баннера, вкладка Таргетинг (Маркетинг > Баннерная реклама > Баннеры).

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

    Желательные ключевые слова страницы могут быть заданы с помощью функции SetDesiredKeywords в коде страницы. Данная функция содержит следующие параметры:

    • keywords – одно или несколько желательных ключевых слов страницы;
    • TYPE_SID – тип рекламы, управление показом которой будет осуществляться с помощью указанных желательных ключевых слов. Если данный параметр оставить пустым, то ключевые слова будут заданы для всех типов рекламы.

    CAdvBanner::SetDesiredKeywords (array ("Partners", "Cooperation", "Company", "Contacts"), "RIGHT");

    Обратите внимание! По умолчанию, если в коде страницы не установлено каких-либо ключевых слов для рекламы, считается что функция SetDesiredKeywords использует в качестве параметра значение свойства страницы adv_desired_target_keywords. Если оно не задано, то в качестве параметра функции используется значение свойства keywords.

    Функция SetDesiredKeywords вызывается автоматически в момент сборки страницы, ее не нужно дополнительно вызывать в файле header.php, если нет необходимости переопределить ключевые слова для показа рекламы.

    Обязательные ключевые слова страницы задаются с помощью предустановленной функции системы SetRequiredKeywords. Данная функция содержит следующие параметры:

    • keywords – одно или несколько обязательных ключевых слов страницы;
    • TYPE_SID – тип рекламы, управление показом которой осуществляется с помощью указанных обязательных ключевых слов. Если данный параметр оставить пустым, то ключевые слова будут заданы для всех типов рекламы.

    CAdvBanner::SetRequiredKeywords (array("Partners", "Cooperation"), "RIGHT");

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

    Использование прав доступа

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

    показом пунктов меню

    При редактировании меню в расширенном режиме для каждого пункта может быть задано условие показа. Например:

    Смотри также Настройка пунктов меню в курсе Администратор. Базовый.

    шаблоном меню

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

    <?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
    
    <?if (!empty($arResult)):?>
    <div class="blue-tabs-menu">
           <ul>
    <?foreach($arResult as $arItem):?>
    
           <?if ($arItem["PERMISSION"] > "D"):?>
                  <li><a href="<?=$arItem["LINK"]?>"><nobr><?=$arItem["TEXT"]?></nobr></a></li>
           <?endif?>
    
    <?endforeach?>
    
           </ul>
    </div>
    <div class="menu-clear-left"></div>
    <?endif?>

    Важно! Условия, включающие проверку значения ключа массива $PERMISSION у переменной $arItem, используются только для меню сайта.

    шаблоном сайта

    Для каждого шаблона дизайна может быть настроено условие его применения к сайту. Данная настройка выполняется на странице управление параметрами сайта (Настройки системы > Сайты > Изменить). Например:

    Нажмите на рисунок, чтобы увеличить

    В приведенном примере условие определяет, что шаблон Версия для печати будет применяться, если в URL параметр print=Y.

    Наиболее гибким инструментом настройки условий показа является Условие PHP. Примеры php-условий для показа шаблона сайта:

    $USER->IsAuthorized() Проверяется, является ли текущий пользователь авторизованным в системе.
    $USER->IsAdmin() Проверяется, является ли текущий пользователь администратором.
    in_array(‘5’,$USER-> GetUserGroupArray()) Проверяется, относится ли текущий пользователь к указанной группе (в данном случае к группе с ID равным 5).

    Смотрите также Настройка шаблона сайта в курсе Администратор. Базовый.

    отдельными элементами

    Для управления элементами шаблона дизайна

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

    Управление отдельными элементами сайта

    Использование механизма проверки прав доступа позволяет организовать управление отдельными элементами сайта (страницами, разделами, рекламой, форумами и т.д.) различными пользователями. Смотри соответствующие разделы курсов Контент-менеджер и Администратор. Базовый.

    Разные языки сайта

    Система Bitrix Framework позволяет использовать один и тот же шаблон дизайна для нескольких сайтов на разных языках. Данная возможность реализуется с помощью механизма языковых сообщений:

    • в HTML верстке шаблона определяются области, в которых должен отображаться тот или иной текст;
    • затем в выделенные области вместо текстовых сообщений подставляется код, выполняющий подключение и показ соответствующих языковых сообщений на нужном языке.

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

    Перед изучением главы рекомендуется познакомится с [ds]Управлением языковыми сообщениями[/ds][di]Чтобы многоязычный интерфейс работал правильно, нужно где-то хранить весь текст, который используется в административном разделе. Он хранится в виде отдельных текстовых сообщений в языковых файлах соответствующих языков. Языковые файлы размещаются в папках тех модулей и шаблонов, для которых используются эти текстовые сообщения.

    Подробнее в курсе Администратор.Базовый[/di] курса Администратор. Базовый.

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

    Ссылки по теме:

    • Пользовательская документация по модулю «Перевод»

    Механизм реализации

    Языковое сообщение — группа фраз на разных языках, имеющая один смысл.

    • В папке шаблона сайта создается папка с именем /lang/:
      /bitrix/templates/< идентификатор шаблона >/lang/
    • В папке /lang/ создаются папки с идентификаторами используемых языков: /en/, /de/, /ru/ и т.д. Например:
      /bitrix/templates/<идентификатор шаблона>/lang/ru/
    • В созданных папках размещаются соответствующие файлы языковых сообщений. Файлы языковых сообщений характеризуются следующими параметрами:
      • Имя файла языковых сообщений соответствует имени файла, в котором выполняется его вызов. Например, если предполагается, что вызов файла с языковыми сообщениями будет выполняться в прологе шаблона сайта (файл header.php), то файл языковых сообщений должен быть сохранен с именем header.php;
      • Список сообщений в файле хранится в следующем виде:
        <?
        $MESS ['COMPANY_NAME'] = "Company Name";
        $MESS ['MAIN_PAGE'] = "Home page";
        $MESS ['PRINT'] = "Print version";
        $MESS ['AUTH_LOGIN'] = "Authorization";
        $MESS ['RATES_HEADER'] = "Currency rates";
        $MESS ['SEARCH'] = "Site search";
        $MESS ['SUBSCR'] = "Subscription";
        ?>

        где выражение в скобках это код фразы (например, 'COMPANY_NAME')

        Примечание: Система собирает все языковые файлы и формирует единый массив $MESS при формировании страницы. И если языковые файлы фразы в разных местах имеют одинаковый код, то во всех местах будет использоваться код из последнего подключенного языкового файла. Это приводит к выводу неверной фразы. Рекомендуется создавать максимально уникальные ключи в языковых файлах.

    • Затем в начало файла, для которого предусмотрено использование языковых сообщений (например, header.php), добавляется функция:
      <?
      Loc::loadMessages(__FILE__).
      ?>

      Функция выполняет подключение файла языковых сообщений для текущего языка.

    • Далее все текстовые сообщения заменяются на функции вызова соответствующих языковых сообщений:
      <?echo Loc::getMessage("SEARCH");?>

      В качестве параметра функции GetMessage() используется код подключаемого сообщения. Функция проверяет наличие в подключенном языковом файле сообщения с соответствующим кодом и отображает его пользователю.

    Загрузка и выгрузка локализации

    Загрузка и выгрузка в CSV-файл

    Для ручной правки файлов локализации используется функционал выгрузки и загрузки CSV-файла.

    Для этого необходимо:

    • Открыть страницу Просмотра файлов (Настройки > Локализация > Просмотр файлов) и выбрать [dw]Выгрузить сообщения в CSV[/dw][di][/di].
    • Откроется форма настройки выгрузки. Указать, если требуется, выгрузку только непереведенных фраз и необходимость конвертации в кодировку UTF-8, а также какие языки выгрузить. Для выгрузки языковых сообщений из конкретных папок или файлов требуется внести пути к ним в поле Выгрузить сообщения только для файлов и папок:

    • Нажать кнопку Экспортировать для выгрузки файла.

      Для скачивания — нажать Скачать файл экспорта. Название CSV-файла по умолчанию будет включать путь до папки, в которой находится папка/файл и выбранные языки.

      Например, если экспортировать папку /bitrix/activities/bitrix/absenceactivity с языками ru и en, то название файла будет: bitrix_activities_bitrix_ru_en.csv.

      Примечание: Второй способ экспорта конкретных папок или файлов — из списка. Отметьте их в списке и на панели действий выберите действие [dw]Экспортировать выделенные[/dw][di][/di].
      Функция экспорта языковых сообщений также доступна в форме просмотра конкретного файла.

    • Отредактировать необходимые строки и сохранить все изменения в том же CSV-файле.
    • Далее перейти к импорту нажатием кнопки [dw]Импортировать сообщения из CSV[/dw][di][/di] на странице просмотра файлов. Выбрать файл для загрузки, кодировку и способ добавления локализации в систему: добавлять только новые переводы, обновить только фразы найденные в csv-файле или добавлять новые переводы и обновить фразы, найденные в csv-файле. Нажать кнопку Импортировать:

    Локализация из CSV-файла будет добавлена в систему.

    Сбор переводов

    После создания локализации системы можно создать полный пакет локализации для каждого языка системы.

    Для этого необходимо:

    • Перейти на страницу Сбор переводов (Настройки > Локализация > Выгрузка и загрузка).

    • Выбрать необходимые опции в закладке Сбор переводов:
      • язык для сборки;
      • дату сборки в формате YYYYMMDD;
      • выбрать кодировку, в которую будут сконвертированы все файлы локализации (Необязательно. Если не выбрать, то файлы будут собраны в текущей кодировке сайта);
      • по желанию упаковать файлы в архив tar.gz;
    • Для запуска процесса сбора пакета локализации нажать кнопку Собрать локализацию.

    После того, как пакет будет собран, будет предоставлена ссылка, где хранится архив (если была выбрана опция упаковки в архив tar.gz) или файлы всего пакета.

    Чтобы импортировать пакет локализации в систему, необходимо:

    • Перейти на страницу Выгрузка и загрузка (Настройки > Локализация > Выгрузка и загрузка):

    • Выбрать необходимые опции в закладке Загрузка переводов:
      • файл с архивом tar.gz (ограничение — 32 МБ);
      • язык для сборки (необходимый язык интерфейса должен быть уже установлен в системе, либо можно его создать по ссылке Добавить);
      • выбрать кодировку, в которую были ранее сконвертированы все файлы локализации;
    • Для запуска процесса импорта пакета локализации в систему нажать кнопку Загрузить локализацию.

    Изменение фраз в компонентах и модулях

    Иногда при разработке дизайна проекта, необходимо изменить несколько слов или фраз в стандартных компонентах или (что хуже) модулях. Если шаблон компонента можно скопировать и кастомизировать, то с самими компонентами и тем более модулями все плохо. Сложно что-то изменить, не отказавшись от обновлений.

    Тем не менее, есть технология замены любых языковых фраз продукта. Решение не идеальное, но оно дает довольно большой простор для полета фантазии разработчика.
    Суть технологии в том, что языковые фразы продукта после подключения языкового файла заменяются на фразы, определенные разработчиком. Список замен должен находиться в файле /bitrix/php_interface/user_lang/<код языка>/lang.php, либо в файле /local/php_interface/user_lang/<код языка>/lang.php.

    В этом файле должны определяться элементы массива $MESS в виде $MESS['языковой файл']['код фразы'] = 'новая фраза', например:

    <?
    $MESS["/bitrix/components/bitrix/system.auth.form/templates/.default/lang/ru/template.php"]["AUTH_PROFILE"] = "Мой любимый профиль";
    $MESS["/bitrix/modules/main/lang/ru/public/top_panel.php"]['top_panel_tab_view'] = "Смотрим";
    $MESS["/bitrix/modules/main/lang/ru/interface/index.php"]['admin_index_sec'] = "Проактивка";
    ?>

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

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

    Настройка дополнительных элементов

    Настройка сообщений об ошибках

    Часто возникает необходимость выполнить настройку сообщений об ошибках, чтобы сообщение об ошибке было аккуратно показано в дизайне сайта.

    Чтобы произвести настройку внешнего вида сообщения об ошибке соединения с базой данных, следует отредактировать файл:
    /bitrix/php_interface/dbconn_error.php.

    Для того чтобы произвести настройку внешнего вида сообщения об ошибке в запросе к базе данных, следует отредактировать файл:
    /bitrix/php_interface/dbquery_error.php.

    Настройка файла, подключаемого при закрытии сайта

    Чтобы произвести настройку внешнего вида файла, подключаемого при [ds]закрытии публичной части[/ds][di]Закрытие публичной части в настройках Главного модуля…[/di] сайта, следует скопировать файл:
    /bitrix/modules/main/include/site_closed.php и поместите его в /bitrix/php_interface/<язык>/ или в /bitrix/php_interface/include/.

    Настройка внешнего вида навигации постраничного просмотра

    Постраничный показ информации организуется с использованием PHP функции NavPrint() – функции вывода ссылок для постраничной навигации. Для управления внешним видом постраничной навигации могут быть использованы следующие параметры:
    NavPrint($title, $show_always=false, $StyleText="text", $template_path)
    где:

    $title – название выводимых элементов;

    $show_always – если значение параметра false, то функция не будет выводить навигационные ссылки, если все записи умещаются на одну страницу. Если true, то ссылки для постраничной навигации будут выводиться всегда;

    $StyleText – CSS класс шрифта для вывода навигационных ссылок;

    $template_path – путь к шаблону показа навигационных ссылок.

    Favicon

    Одна иконка для административной и публичной части сайта

    По умолчанию favicon располагается в корневом каталоге сайта (/favicon.ico).

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

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

    Существует вариант, позволяющий задать свою иконку для конкретного шаблона сайта. Для этого:

    • файл с иконкой следует поместить в папку с шаблоном (/bitrix/templates/#имя_шаблона#/favicon.ico).
    • в файле header.php (/bitrix/templates/#имя_шаблона#/header.php) между тегами <head> </head> необходимо добавить строку:
      <link rel="shortcut icon" type="image/x-icon" href="<?=SITE_TEMPLATE_PATH?>/favicon.ico" />

      Тогда указанная иконка будет отображаться в публичной части сайта, а для административной будет использована иконка по умолчанию (/favicon.ico).

    Примечание: В ранних версиях продукта favicon был в виде специального PHP-скрипта. Такое решение использовалось для модуля статистики.

    Во всех актуальных версиях продукта такой вариант более не используется.

    Примеры работы и решения проблем

    Цитатник веб-разработчиков.

    Алексей Майдокин: Моё мнение — вёрстке и другим сложным штукам место в шаблонах, компонентах и тому подобном, короче там, куда простым смертным просто так не добраться.
    Сложная вёрстка в области контента — это как двигатель в салоне легковушки.

    Подробную информацию об условиях применения шаблонов можно посмотреть в следующих уроках:

    • Применение шаблона дизайна (курс Контент-менеджер)
    • Шаблоны дизайна (курс Администратор. Базовый)

  • Простые примеры применения шаблонов в зависимости от различных условий
  • Подключение favicon.ico для разных шаблонов
  • Отдельный шаблон для главной страницы сайта
  • Изменение шаблона сайта, в зависимости от временного параметра
  • Применение шаблона сразу по двум условиям
  • Применение шаблона только к файлам с определённым расширением
  • Изменение дизайна «шапки» сайта для разных разделов
  • Особенности работы с ajax
  • Простые примеры применения шаблонов в зависимости от различных условий

    Если свойство раздела phone равно Y

    $APPLICATION->GetDirProperty("phone")=="Y"

    Если текущий раздел равен /ru/catalog/phone/

    $APPLICATION->GetCurDir()=="/ru/catalog/phone/"

    Если текущий пользователь — администратор

    $USER->IsAdmin()

    Если нужно привязать шаблон к динамической странице (в примере — к детальной странице(карточке) товара.)

    preg_match("#/catalog/?SECTION_ID=d+&ELEMENT_ID=d+#i",$_SERVER['REQUEST_URI']);

    Подключение favicon.ico для разных шаблонов

    Для того чтобы в разных шаблонах сайтов были разные значки, надо в шаблоне в header.php добавить вызов значка из шаблона:

    <link rel="icon" type="image/x-icon"
    href="<?=SITE_TEMPLATE_PATH;?>/images/favicon.ico" />
    

    Отдельный шаблон для главной страницы сайта

    Цитата: По умолчанию ставлю шаблон, который рассчитан для внутренних страниц сайта. На главной странице будет свой шаблон. Подскажите, как составить условие для отображения нужного шаблона на главной странице сайта?

    Вдобавок к основному шаблону для всех страниц сайта необходимо подключить требуемый шаблон через условие Для папки или файла, в котором указать /index.php с учетом индекса сортировки применения шаблона.

    Изменение шаблона сайта, в зависимости от временного параметра

    Цитата: Как реализовать изменение шаблона сайта, в зависимости от временного параметра (Всего два шаблона, но они должны меняться каждый час)?

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

    Первый вариант

    По нечетным часам: ((date("H")+6)%2) == 1

    По четным часам: ((date("H")+6)%2) == 0

    где +6 — указывает временную зону.

    Второй вариант

    date("H")%2

    или

    !date("H")%2

    Применение шаблона сразу по двум условиям

    Цитата: Подскажите, как задать шаблон одновременно по 2 условиям (для группы пользователей и для папки и файла) в настройках БУС.

    Для этого необходимо использовать изменение текущего шаблона по условию Выражение PHP:

    in_array($groupID, $USER->GetUserGroupArray()) || strpos($APPLICATION->GetCurDir(), "/dir/")!==false || $APPLICATION->GetCurPage()=="/dir/file.php"
    

    Применение шаблона только к файлам с определённым расширением

    Цитата: Подскажите, пожалуйста, выражение PHP, чтобы шаблон действовал на все страницы, заканчивающиеся на .php, а .html не трогал.

    Решение: изменение шаблона по условию Выражение PHP:

    substr($APPLICATION->GetCurPage(true), -4) == ".php" 
    

    Аналогично для файлов с расширением html:

    substr($APPLICATION->GetCurPage(true), -5) == ".html"

    Изменение дизайна «шапки» сайта для разных разделов

    Задача: Сайт разделен на несколько разделов. По замыслу у каждого раздела должна быть своя «шапка» в дизайне. Более в дизайне ничего не меняется. Как лучше реализовать смену «шапок» разделов?

    Решение: В шаблон подключается компонент Включаемая область (для раздела):

    <div id="header">
    <?$APPLICATION->IncludeComponent("bitrix:main.include", ".default", array(
       "AREA_FILE_SHOW" => "sect",
       "AREA_FILE_SUFFIX" => "headerinc",
       "AREA_FILE_RECURSIVE" => "Y",
       "EDIT_TEMPLATE" => "sect_headerinc.php"
       ),
       false
    );?>
    </div> 
    

    Код шапки каждого из разделов будет храниться в файле sect_headerinc.php. Параметр "AREA_FILE_RECURSIVE" => "Y" означает, что такая же «шапка» появится у всех подразделов данного раздела, если родительский sect_headerinc.php не будет специально перекрыт у кого-то из нижележащих разделов.

    Особенности работы с ajax

    Использование режима ajax имеет свои особенности. Чтобы строка навигации в открываемой по ajax странице имела в цепочке навигации своё название, необходимо, чтобы в шаблоне обязательно присутствовал элемент с id="navigation". Это необязательно должен быть div, это может быть span, h1, p и так далее.

    Аналогично, для заголовка обязательно наличие элемента с id="pagetitle".

    Как вывести произвольный контент в шаблоне сайта и компонента

    Усовершенствованные методы буферизации в шаблоне позволяют более не использовать CBitrixComponentTemplate::EndViewTarget() ввиду того, что конец шаблона вызывает завершение буферизации автоматически.

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

    Можно использовать как в шаблоне сайта, так и в шаблоне компонента.

    Теперь есть поддержка стандартного кеширования в компонентах.

    • template.php:
      <?$this->SetViewTarget("sidebar");?>
      
      	<div class="element-filter">
      		<!--вывод фильтра -->
      	</div>
      
      <?$this->EndViewTarget();?>
      
      <div class="element-list">
      	<!--вывод списка -->
      </div>
    • header.php:
      <div id="sidebar">
      	<?$APPLICATION->ShowViewContent("sidebar")?>
      </div>

    Методы, доступные в шаблоне (через $this)

    • CBitrixComponentTemplate::SetViewTarget($view, $pos)
    • CBitrixComponentTemplate::EndViewTarget()

    Методы глобального объекта $APPLICATION

    • Cmain::AddViewContent($view, $content, $pos)
    • Cmain::ShowViewContent($view)

    где:

    • $view – идентификатор буферизируемой области;
    • $content – буферизируемый контент;
    • $pos – сортировка вывода контента.

    Примечание: одному идентификатору $view может соответствовать несколько буферов. Последовательность вывода контента определяется сортировкой $pos.

    Разработка шаблонов страниц

    Система Bitrix Framework позволяет создавать и использовать шаблоны-заготовки для рабочей и включаемых областей страницы сайта. Использование шаблонов-заготовок особенно полезно и эффективно в тех случаях, когда страницы сайта имеют довольно сложную структуру (верстку).

    Шаблоны страниц и редактируемых областей хранятся в папке /page_templates/, находящейся в каталоге соответствующего шаблона сайта или в папке /bitrix/templates/.default/, если предполагается использование одинаковых шаблонов страниц для всех шаблонов сайта.

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

    Детально о создании шаблонов страниц описано в курсе [ds]Администратор. Базовый[/ds][di]На проектах с большим содержанием статических страниц возникает задача оптимизации труда контент-менеджеров. Решить эту задачу можно с помощью создания шаблонов страниц. Есть несколько вариантов шаблонизации контента статики.

    Подробнее …[/di]

    Руководство по оформлению HTML/CSS кода

    Руководство — это правила для разработчиков компании 1С-Битрикс, которое является обязательным при разработке продуктов Bitrix Framework. Раздел описывает правила оформления и форматирования HTML и CSS кода. Его цель — повысить качество кода и облегчить совместную работу и поддержку инфраструктуры разработчиками.

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

    Общие правила оформления кода

    Отступы

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

    В PHPStorm настройте поля Показ невидимых символов, Табуляция вместо пробелов.

    Кодировка

    UTF-8 фактически стал стандартом, хотя клиенты Bitrix Framework иногда установливают продукт в кодировке ANSI.

    Перевод строки

    Строки должны заканчиваться только символом перевода на новую строку (LF) — стандарт для текстовых файлов в Unix-системах. Не используйте комбинацию символов возврата каретки/перевода строки (CRLF), как это принято в Windows.

    В PHPStorm настройте поле Перевод строки для новых файлов. В нижней статусной строке показывается, какой перевод строки используется для текущего файла (должен быть LF).

    Максимальная длина строки

    Длина строки не должна превышать 120 символов. Настройте редактор кода так, чтобы он отображал правую границу строк.

    В PHPStorm настройте поля Показ правой границы, Ширина правой границы.

    Регистр

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

    Пробелы в конце строки

    Убирайте пробелы в конце строки. Пробелы в конце строк не обязательны и усложняют сравнение файлов в системе контроля версий.

    В PHPStorm настройте поле Удаление пробелов перед сохранением файла.

    Комментарии

    Все комментарии должны быть на английском языке.

    Правила оформления HTML

    Семантика

    Использование семантики облегчает восприятие html-кода страницы и понимание логического и функционального назначения каждого тега. В HTML существует довольно много тегов, которые в своем названии несут четкую семантику: <a>, <p>, <i>, <menu>. Не стоит с помощью CSS таким тегам менять их базовый смысл. Идеально для верстальщика было бы удобно использовать теги с произвольными названиями.

    <users>
        <user>
            <avatar></avatar>
            <name></name>
        </user>
    </users>

    HTML5 предлагает механизм для создания произвольных тегов (Web Components). Но реализация этого механизма не самая простая (нужно писать JavaScript) и поддержка в браузерах неполная. Единственный нормальный способ писать семантический код — использовать атрибут class. Он позволяет описать смысл тега естественными словами, не требователен к уникальности (в отличие от атрибута id) и поддерживает возможность указать несколько классов к одном тегу.

    Основы верстки

    • Используйте теги div (для блочных элементов) и span (для строчных и строчно-блочных элементов).
    • Остальные теги используйте только согласно их базовому смыслу.
    • Теги <b>, <i>, <s>, <p>, с <h1> по <h6>, <ul>, <li> — используются только для форматирования текста. Как правило, эти стили находятся в шаблоне сайта и в стилях компонентов не присутствуют.
    • Для привязки CSS-свойств к тегу используйте только атрибут class.
    • Если нажатие на элемент приводит к открытию новой страницы, используйте тег <a>. В противном случае используйте <span> или <div>.
    • Отступы между логическими блоками никогда не делайте пробелами или тегом <br>.
    • Структуру html-тегов необходимо делать по сетке дизайна. «Колонки» и логические блоки одного уровня обязательно помещайте в контейнер.

      Плохо:

          <span class="task-detail-sidebar-info-link">сменить</span>
          <div class="task-detail-sidebar-info-title">Ответственный</div>
          <div class="task-detail-sidebar-info-user">
              <a href="#" class="task-detail-sidebar-info-user-photo"></a>
              <a href="#" class="task-detail-sidebar-info-user-name">Иван Петров</a>
              <div class="task-detail-sidebar-info-user-post">Бухгалтер</div>
          </div>

      Лучше:

          <div class="task-detail-sidebar-info">
              <div class="task-detail-sidebar-info-head">
                  <div class="task-detail-sidebar-info-link">сменить</div>
                  <div class="task-detail-sidebar-info-title">Ответственный</div>
              </div>
              <div class="task-detail-sidebar-info-user">
                  <a href="#" class="task-detail-sidebar-info-user-photo"></a>
                  <div class="task-detail-sidebar-info-user-title">
                      <a href="#" class="task-detail-sidebar-info-user-name">Иван Петров</a>
                      <div class="task-detail-sidebar-info-user-post">Бухгалтер</div>
                  </div>
              </div>
          </div>
    • Удаление логических блоков не должно рушить дизайн.
    • Всегда есть вероятность, что на реальном сайте количество текста будет больше, чем нарисовано в макете. Верстка должна быть адаптивной к появлению дополнительных строк и к длинным строкам без пробелов.
    • Текстовый элемент не может быть на одном уровне с тегом.

      Плохо:

          <div class="task-detail-group">Задача в проекте: <div class="task-detail-link">добавить</div></div>

      Хорошо:

          <div class="task-detail-group">
             <span class="task-detail-group-label">Группа:</span>
             <span class="task-detail-group-name">Название группы</span>
          </div>
    • Каждый компонент должен иметь родительский контейнер. Для этого элемента в CSS необходимо указать шрифт (font-family), а также размер текста (font-size), чтобы избежать конфликтов со стилями шаблона сайта.

    Особые случаи

    • Внимательно относитесь к элементам inline-block, перевод строки между ними будет отображен как пробел. Чтобы не писать теги в одну длинную строку, можно воспользоваться следующим приемом:
          <div class="inline-block-items"><?
              ?><span class="inline-block-item">Inline Item 1</span><?
              ?><span class="inline-block-item">Inline Item 2</span><?
              ?><span class="inline-block-item">Inline Item 3</span><?
              ?><span class="inline-block-item">Inline Item 4</span><?
          ?></div>

      Визуально теги разбиты и отделены отступами, а в результирующем HTML-коде будет одна строка. Другой прием с помощью HTML-комментариев:

          <div class="inline-block-items"><!--
              --><span class="inline-block-item">Inline Item 1</span><!--
              --><span class="inline-block-item">Inline Item 2</span><!--
              --><span class="inline-block-item">Inline Item 3</span><!--
              --><span class="inline-block-item">Inline Item 4</span><!--
          --></div>
    • Элементы с обтеканием (float)
      • Неприятной особенностью свойства float является то, что такой блок может «вывалиться» из блока-родителя. Это происходит из-за того, что float-элемент удаляется из потока и родитель не выделяет место под него. Решить эту проблему можно несколькими способами:
        • Поставить родителю overflow: hidden/auto.
        • Поставить родителю float.
        • Добавить в родителя элемент с clear. Чтобы не добавлять в HTML-код лишний элемент, можно задать его через :after.
      • Элементы с обтеканием обязательно должны находится в контейнерах, чтобы не влиять на соседние логические блоки.
    • Для повторяющихся элементов в строчку (пункты меню, список фотографий) отдавайте приоритет inline-block вместо float.

    Тип документа

    Создавайте новую верстку по стандарту HTML5. В старых шаблонах по возможности избавляйтесь от XHTML (слешей в конце тегов <br />, <img />).

    <!DOCTYPE html>

    Альтернативные атрибуты (alt, title)

    Очень часто разработчики забывают указывать альтернативные подписи к иконкам, кнопкам или другим графическим элементам. Наличие атрибутов title и alt в верстке поможет решить эту проблему.

    Разделение ответственности

    Держите структуру (разметка), оформление (стили) и поведение (скрипты) раздельно и постарайтесь свести взаимодействие между ними к минимуму. Убедитесь, что документы и шаблоны содержат только HTML, и что HTML служит только для задания структуры документа. Весь код, отвечающий за оформление, перенесите в файлы стилей, а код отвечающий за поведение — в скрипты.

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

    Формы

    • В верстке всегда должен присутствовать тег <form>.
    • Подписи полей должны быть слинкованы к соответствующим элементам формы.
    • Должно быть показано, как выводится ошибка и/или сообщение об успешном выполнении операции.
    • Переключение между элементами формы по клавише Tab.
    • Интерактивность: смена фокуса должна визуально подсказывать пользователю активный элемент.

    Картинки

    • Используйте SVG вместо растровых изображений.
    • В случае, если SVG использовать затруднительно, используйте формат PNG.
    • Объединяйте картинки в спрайты. Это ускорит отображение страницы.
    • Называйте файлы с указанием имени модуля и блока, к которому относится изображение. Для разделения слов используйте дефис (-).

      Плохо:

          .crm-lead-history {
              background-image: url(images/sprite.svg);
          }
      
          .crm-lead-log {
              background-image: url(images/arrow_left.svg);
          }

      Хорошо:

          .crm-lead-history {
              background-image: url(images/crm-lead-sprite.svg);
          }
      
          .crm-lead-log {
              background-image: url(images/crm-lead-arrow-left.svg);
          }

    Правила форматирования HTML

    Новая строка для каждого блочного или табличного элемента

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

    Плохо:

    <div class="menu">
    <div class="menu-item"><span class="menu-item-title">Home</span></div><div class="menu-item"><span class="menu-item-title">Search</span></div>
    </div><div class="breadcrumb"></div>

    Хорошо:

    <div class="menu">
        <div class="menu-item">
            <span class="menu-item-title">Home</span>
        </div>
        <div class="menu-item">
            <span class="menu-item-title">Search</span>
        </div>
    </div>
    <div class="breadcrumb"></div>

    Двойные кавычки в атрибутах

    Значения HTML-атрибутов всегда пишутся в двойных кавычках.

    Плохо:

    <div class='navigation'>
        <div class=menu></div>
    </div>

    Хорошо:

    <div class="navigation">
        <div class="menu"></div>
    </div>

    Множественные атрибуты

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

    Атрибуты в одну строку:

    <a class="task-detail-special-link" id="task-detail-special-link-id" href="" title="Special Link" data-id="13">
        <span class="task-detail-special-text">Link</span>
    </a>

    Атрибуты с разбивкой на две строки:

    <a class="task-detail-special-link" id="task-detail-special-link-id"
       href="" title="Special Link" data-id="13">
          <span class="task-detail-special-text">Link</span>
    </a>

    Сортировка атрибутов

    HTML-атрибуты должны быть перечислены в следующем порядке:

    1. class
    2. id
    3. атрибуты тега (src, href, title и другие.)
    4. data-*

    Правила оформления CSS

    Каскадные селекторы

    Любой блок на странице должен описываться селектором класса. Не используйте каскадные селекторы так как они нарушают принцип независимости компонентов. Это же правило относится к селекторам прямого потомка (>) и соседнего элемента (+).

    Исключения:

    • Селекторы состояний (:visited, :active, :hover).
    • Селекторы контекста. Существуют случаи, когда компонент используется на разных страницах или в разном окружении. В этом случае удобно использовать селекторы контекста. Важно, чтобы эти контекстные стили содержались в родительском компоненте.
    • Верстка, в которой нельзя изменить структуру HTML-тегов.

    Именование классов

    • Имена классов записываются на английском языке в нижнем регистре. Если не уверены в написании английского слова, проверьте его по словарю. PHPStorm имеет встроенную проверку орфографии.
    • Желательно, чтобы терминология в названии класса совпадала с названиями, которыми оперирует разработчик (названия полей в базе данных, названия PHP-классов).
    • Для разделения слов в именах используется дефис (-).
    • Создавайте имена CSS-селекторов максимально информативными и понятными. Это поможет упростить разработку и отладку кода. Не стесняйтесь использовать длинные названия классов.
    • Название класса должно отражать суть блока, а не его внешний вид.
    • Первым словом в названии класса должно идти название модуля. На втором месте — название блока. Название блока может состоять из нескольких слов. Такой формат, с одной стороны, сразу показывает принадлежность стилей, с другой — гарантирует их уникальность. Уникальность стилей обеспечивает независимость верстки компонентов.
          <div class="task-detail">
              <div class="task-detail-title"></div>
              <div class="task-detail-description"></div>
          </div>
          <div class="crm-lead-form">
              <div class="crm-lead-form-title"></div>
              <div class="crm-lead-form-description"></div>
          </div>
    • Вложенные элементы должны сохранять контекст именования блока. Блок не обязательно должен быть прямым родителем элемента.

      Оба варианта именования допустимы:

          <div class="disk-user">
              <div class="disk-user-info">
                  <div class="disk-user-avatar"></div>
                  <div class="disk-user-name"></div>
              </div>
          </div>
          <div class="disk-user">
              <div class="disk-user-info">
                  <div class="disk-user-info-avatar"></div>
                  <div class="disk-user-info-name"></div>
              </div>
          </div>
    • Не допускайте сокращений. Сокращения могут привести к ситуации, когда одинаковые вещи будут названы по-разному (btn/button).
    • Не используйте старый префикс bx-.
    • Если к элементу происходит обращение по классу через JavaScript, то такой класс должен иметь префикс js-.
    • Для верстки шаблона сайта допускается опускать префикс модуля.

    Inline-стили

    Избегайте инлайновых стилей, все стили должны задаваться в CSS-файле через назначение классов элементам. Инлайновые стили применимы, только если они являются неотъемлемой частью контента страницы (аватар пользователя, размер ползунков в фильтре и т. п.).

    Селектор тега и селектор на атрибут

    Избегайте использование имен классов с селектором тега и атрибута.

    Плохо:

    div.task-detail-title {
        margin: 20px;
    }
    
    input[name=user] {
        margin: 20px;
    }

    Хорошо:

    .task-detail-title {
        margin: 20px;
    }
    
    .task-detail-user-field {
        margin: 20px;
    }

    Сокращенная форма записи свойств

    CSS предлагает множество различных сокращенных форм записи (например, margin, padding, border и другие), которые рекомендуется использовать везде где это возможно, даже если задается только одно из значений. Использование сокращенной записи свойств полезно для большей эффективности и лучшего понимания кода.

    Плохо:

    .meeting-sidebar {
        margin-top: 5px;
        margin-bottom: 3px;
        border-width: 1px;
        border-style: solid;
        border-color: #fff;
    }

    Хорошо:

    .meeting-sidebar {
        margin: 5px 0 3px;
        border: 1px solid #fff;
    }

    Сокращенные формы для шрифта (font) и фона (background) допускается разделять на несколько свойств.

    Единицы измерения

    Используйте значения в пикселях. Размеры, указанные в px, абсолютные, четкие, понятные и не зависят ни от чего. Допустимо использовать проценты % везде, кроме размера текста (font-size).

    0 и единицы измерения

    Не указывайте единицы измерения для нулевых значений.

    Плохо:

    .timeman-task-list {
        margin: 0px 12px 0px 13px;
    }

    Хорошо:

    .timeman-task-list {
        margin: 0 12px 0 13px;
    }

    Кавычки в ссылках

    Не используйте кавычки с функцией url(), кроме случая, когда ссылка содержит пробел, либо формируется программно (обычно при использовании inline-стилей).

    Плохо:

    .im-user-status {
        background: url("images/im-sprite.png");
    }

    Хорошо:

    .im-user-status {
        background: url(images/im-sprite.png);
    }

    Цвет

    Для указания цвета элемента используйте шестнадцатеричную запись или rgba(), если требуется указать прозрачность. Не используйте rgb() и верхний регистр в значении свойства.

    Плохо:

    .disk-invitation-popup {
        background: #FFFFFF;
    }

    Хорошо:

    .disk-invitation-popup {
        background: #fff;
    }

    Хаки

    Избегайте хаков в CSS-коде. Если все таки требуется сделать уникальные свойства для конкретного браузера, воспользуйтесь специальными классами .bx-chrome, .bx-firefox, .bx-ie10.

    Плохо:

    *+ html .vote-answers {
        margin: 5px;
    }

    Хорошо:

    .bx-ie7 .vote-answers {
        margin: 8px;
    }

    Группировка правил

    Старайтесь, чтобы селекторы, описывающие определенный блок верстки, находились в одном месте и не были раскиданы по CSS-файлу.

    Псевдоселекторы (:before, :after)

    Псевдоселекторы позволяют не нагромождать HTML-код лишними элементами, имеющими декоративное назначение. Важно понимать, что этих элементов нет в DOM и обратиться к ним через JavaScript невозможно.

    Сброс стилей (CSS Reset)

    Не используйте CSS Reset в верстке. Компоненты являются независимыми блоками. На них не должны влиять CSS-правила, созданные для всей страницы. Это нарушает независимость блоков и затрудняет их повторное использование. Общий сброс стилей реализуется с помощью глобальных CSS-правил, которые в большинстве случаев пишутся к селекторам на тег, что нежелательно использовать в верстке. Кроме того, сброс стилей влияет на производительность рендеринга страницы и может конфликтовать с версткой клиентов.

    Браузерные префиксы

    Свойство без префикса всегда должно быть в конце.

    Плохо:

    .menu-item {
        -webkit-box-shadow:0 0 10px 20px #000;
        box-shadow:0 0 10px 20px #000;
        -moz-box-shadow:0 0 10px 20px #000;
    }

    Хорошо:

    .menu-item {
        -webkit-box-shadow:0 0 10px 20px #000;
        -moz-box-shadow:0 0 10px 20px #000;
        box-shadow:0 0 10px 20px #000;
    }

    Селекторы состояний (selected, active и hover)

    Классы-модификаторы должны именоваться согласно общим правилам:

    <div class="menu">
        <div class="menu-item"></div>
        <div class="menu-item menu-item-selected"></div>
        <div class="menu-item"></div>
    </div>

    Приоритеты селекторов и !important

    Избегайте использования модификатора приоритета !important.

    Шрифты, ссылки, цвета

    Для оформления текста всегда указывайте семейство шрифтов.

    Плохо:

    .disk-external-folder {
        font: 12px "Helvetica Neue";
    }

    Хорошо:

    .disk-external-folder {
        font: 12px "Helvetica Neue", Helvetica, Arial, sans-serif;
    }

    Правила форматирования CSS

    • При создании правила для нескольких селекторов помещайте каждый селектор на отдельной строке.
    • Перед открывающей скобкой ставьте один пробел.
    • Внутри блока объявлений помещайте каждое объявление на отдельной строке.
    • Добавляйте один уровень отступов перед каждым объявлением.
    • Ставьте пробел после двоеточия внутри объявления.
    • Всегда ставьте точку с запятой после последнего объявления в блоке.
    • Ставьте закрывающую скобку на одной вертикальной линии с первым символом в селекторе.
    • Ставьте пробел после каждой запятой в объявлениях со множественным значением.
    • Разделяйте правила пустой строкой.
    • Сортируйте свойства по принципу: свойства, сильно влияющие на элемент, в начале, а самые незначительно влияющие — в конце:
      • Display
      • Позиционирование (position/float)
      • Боксовая модель (width/height/margin/padding/border/box-sizing)
      • Цвета и типографика
      • Остальное

      Плохо:

          .crm-lead-form { margin: 34px; color: #000; }
          .crm-lead-title, .crm-invoice-title, .crm-company-title
          {
              position: relative;
              background: #000;
              height: 15px;
              padding: 10px;
              border: 1px solid red;
              margin: 12px 0 17px;
              display: block;
              color: #fff;
              width: 15px;
          }

      Хорошо:

          .crm-lead-form {
              margin: 34px;
              color: #000;
          }
      
          .crm-lead-title,
          .crm-invoice-title,
          .crm-company-title {
              display: block;
              position: relative;
              width: 15px;
              height: 15px;
              padding: 10px;
              border: 1px solid red;
              margin: 12px 0 17px;
              color: #fff;
              background: #000;
          }
    • Для значений с пробелами (font-family, url()) и для свойства content используйте двойные кавычки.
          .disk-user-profile {
              font-family: "Helvetica Neue Light", Helvetica, Arial, sans-serif;
          }
      
          .disk-user-profile:after {
              display: block;
              content: "";
          }
    • Исключения

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

              .crm-column-title { width: 10%; }
              .crm-column-author { width: 20%; }
              .crm-column-actions { width: 30%; }
      
              .menu-icon-create { background-position: 0 0; }
              .menu-icon-delete { background-position: -15px -35px; }
              .menu-icon-approve { background-position: -34px -35px; }

      Длинные значения свойств, разделяемые запятыми — как, например, набор градиентов или теней — могут быть помещены на отдельной строке каждое, чтобы повысить читабельность кода и сообщений в системе управления версиями. Формат записи может слегка различаться, один из вариантов приведён ниже.

              .disk-info-popup {
                  box-shadow:
                      1px 1px 1px #000,
                      2px 2px 1px 1px #ccc inset;
                  background-image:
                      linear-gradient(#fff, #ccc),
                      linear-gradient(#f3c, #4ec);
              }

    Тестирование верстки (чеклист)

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

    • Проверка верстки на соответствие макету (Pixel Perfect).
    • Браузеры:
      • Chrome
      • Firefox
      • IE Edge
      • Safari
    • Минимальное и максимальное разрешение экрана.
    • Переполнение блоков:
      • Длинный текст без пробелов
      • Длинный текст с пробелами
    • Удаление необязательных блоков, которые могут отсутствовать.
    • Retina-экран.
    • Картинки разных размеров и пропорций в допустимых пределах, которые будет допускать код.

    Особенности интеграции с Битрикс

    Шаблон сайта и компоненты

    В рамках продукта верстку можно разделить на два вида:

    • Верстка компонентов
    • Верстка шаблона сайта

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

    Шаблон сайта определяет внешний вид сайта. Включает в себя HTML и CSS для layout’а страницы (header, footer, колонки), оформление текста и другие элементы, общие для всех страниц сайта.

    Контекст

    Контексты продуктов и шаблонов сайтов

    Прежде чем приступать к работе, необходимо выяснить в каком продукте и в каком шаблоне (или шаблонах) сайта будет использоваться верстка. От контекста использования зависит следующее:

    • Стандарт HTML (HTML5, HTML4, XHTML)
    • Standard Mode и/или Quirks Mode
    • Гибкость верстки
    • Устойчивость к внешним стилям
    • Тестирование
    • Кросс-браузерность

    Существует следующие контексты использования верстки:

    • Корпоративный портал. Данный продукт поставляется с шаблоном Bitrix24. Компоненты должны хорошо отображаться в этом шаблоне.
    • Управление сайтом. Именно с помощью этого продукта клиенты создают новые сайты. Компоненты будут работать в абсолютно разных по дизайну и по верстке проектах. Как правило, партнер, интегрируя стандартный компонент на сайте, идет по пути наименьшего сопротивления. Сначала пробует изменить только CSS шаблона и если этого недостаточно — копирует шаблон и на его основе создает новый.
    • Публичные интерфейсы. Административная верхняя панель, диалоги создания и редактирования страницы — это все примеры публичных интерфейсов, которые должны хорошо отображаться в любом дизайне. Здесь важно, чтобы верстка была «пуленепробиваемой» — внешние стили шаблона не должны влиять на ее отображение.
    • Административный раздел. Представляет собой по сути отдельный сайт со своим шаблоном и интерфейсами. Однако часть этих интерфейсов могут отображаться в публичной части сайта.
    • Сайты готовых решений. Для данных сайтов создается отдельный набор шаблонов, использование которых предназначено только для соответствующего шаблона сайта.
    • Решение Интернет-магазин. Несмотря на то, что сайт интернет-магазина является готовым решением, его шаблоны используются в компонентах как стандартные (по умолчанию). Это значит, что такие шаблоны могут использоваться в любых других дизайнах.
    • Мобильное приложение. Данный продукт использует браузер на основе движка WebKit.

    Bootstrap

    Bootstrap в Bitrix Framework

    В Bitrix Framework для вёрстки шаблонов сайта и компонентов используется Bootstrap. При вёрстке шаблонов учитывайте что:

    1. В БУС подключается не собственный вариант Bootstrap, а оригинальная, не изменённая поставка фреймворка в 4-ой версии.
    2. Bootstrap подключается не в конкретном компоненте, а в целом на продукте из модуля main.
    3. В Bootstrap рекомендуют кастомизировать стили в отдельном файле, обычно он называется bootstrap-theme.css. В случае с Bitrix Framework он называется template_style.css — для основного шаблона и style.css в каждом отдельном компоненте. В этом случае выше перечисленные файлы подключатся после основной библиотеки.

    Если верстальщик не следует рекомендациям Bootstrap и нашей документации, то возможна ситуация, когда «плывёт» дизайн.

    Верстка для мобильных устройств

    Viewport

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

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    Метатег viewport содержит инструкции для браузера по корректировке размеров и масштабированию страницы по ширине экрана устройства. Если этого элемента нет, мобильные браузеры по умолчанию показывают страницу для экрана компьютера (ширина которого обычно составляет около 980 пикселей, но это значение может отличаться у разных устройств). Затем мобильные браузеры пытаются оптимизировать содержание, увеличивая шрифты и либо масштабируя содержание по размеру экрана, либо показывая только часть контента, которая помещается на экране.

    Retina-экран

    • Используйте изображения в формате SVG.
    • Для растровых изображений используйте двукратный размер и свойство background-size.

    Border в 1px

    Часто дизайнеры требуют сделать границу блока шириной в 1 физический пиксель. Если присвоить блоку свойство border: 1px solid #000, на Retin’е это будет выглядеть жирной полосой в 2 физических пикселя. Раньше эту проблему решали путем создания картинки, которой обтягивали бордер с помощью свойства border-image. А это лишнее обращение к серверу. Начиная с iOS 8 и Android 4.4, стандартные браузеры на базе WebKit поддерживают дробные свойства border: .5px solid #000. На данный момент поддержка этого способа на используемых девайсах равна 97% на iOS и 75% на Android.

    В рамках курса «BitrixMobile — создание кроссплатформенных мобильных приложений» подробно рассмотрены возможности библиотеки BXMobileApp, которая позволяет создавать сложные бизнес-приложения на BitrixMobile.

    Приёмы верстки

    В этом разделе собраны типовые подходы к решению частых задач верстки.

    Использование оптимальных изображений под разные браузеры и устройства

    WebKit поддерживает srcset атрибут изображений в img и image-set в стилях. Это позволяет вам, как разработчику, использовать картинки с высоким разрешением для пользователей использующих ретина-дисплей без ущерба для остальных пользователей.

    Обязательно задается изображение для браузеров не поддерживающих это свойство.

    .class {
      /* задаем фоновое изображение, если браузер не поддерживает image-set */
      background-image: url(image-set-not-supported.png);
      /* задаем фоновые изображения, для различных разрешений */
      background-image: -webkit-image-set(url(low-res-image.jpg) 1x, url(high-res-image.jpg) 2x);
      background-size: cover;
    }

    Не забудьте использовать свойство background-size для того что бы большие изображения корректно отображались.

    Атрибут srcset работает аналогичным образом.

    <img src="srcset-not-supported.png" srcset="low-res-image.jpg 1x, high-res-image.jpg 2x">

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

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

    Для повторения описанного примера, знакомящего с принципами внедрения шаблона дизайна в Bitrix Framework, рекомендуется использовать этот макет шаблона. Шаблон — тестовый, вопросы кроссбраузерности при его создании не рассматривались. Все работы выполнялись в Firefox.

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

    Примечание: Описанный пример использовался в опубликованных несколькими годами ранее книгах о создании сайта на «1С-Битрикс: Управление сайтом». Принципы работы за это время не изменились. Однако поменялись шаблоны в демодистрибутиве и, следовательно, описание их кастомизации.

    При создании примера подразумевалось, что пользователь знаком с PHP, HTML и CSS. Поэтому детальное описание причин удалений или добавления того или иного кода опускается. Считаем, что вы сами способны понять эти моменты. Перед началом работы настойчиво рекомендуется познакомиться с понятием о шаблоне дизайна сайта в рамках Bitrix Framework.

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

    В Примере не рассматриваются вопросы, связанные с кастомизацией ввода и обработки данных за счёт изменения логики работы системы и компонентов.

    Создание шаблона

    Создание шаблона – не самый сложный шаг в интеграции дизайна, но требует внимательности.

    Форма создания шаблона

    • Перейдите в раздел Настройки > Настройки продукта > Сайты > Шаблоны сайтов. В Рабочей области откроется форма Шаблоны сайтов.
    • Выполните команду Добавить шаблон на Контекстной панели. Откроется форма создания нового шаблона. В этой форме в поле:
      • ID — введите название шаблона. В нашем примере это будет test.
      • Название — введите название шаблона. (Может быть и на кириллице и на латинице), например, тоже test.
      • Описание — можно вставить ваш комментарий к шаблону.
      • Порядок — числовое значение определяет порядок отображения шаблона в общем списке шаблонов.

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

    Назначение шаблона для сайта

    Назначьте созданный шаблон для тестового сайта.

    Добавление кода тестового дизайна в шаблон

    Служебная часть

    Задание внешнего вида шаблона происходит в поле Внешний вид шаблона сайта.

    Вставьте в поле следующий служебный код для шаблона:

    • Верхняя часть:
      <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
      ?><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
      <html>
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=<?echo LANG_CHARSET;?>">
      <?$APPLICATION->ShowMeta("keywords");?>
      <?$APPLICATION->ShowMeta("description");?>
      <title><?$APPLICATION->ShowTitle()?></title>
      <?$APPLICATION->ShowHead()?>
      </head>
      <body>
      
    • Нижняя часть:
      </body>
      </html>
      
    • Для того, чтобы в шаблоне подключалась Административная панель, введите код административной панели после открывающего тега body:
      <?$APPLICATION->ShowPanel();?>
    • Примечание: При редактировании уже существующего шаблона можно использовать кнопку Предпросмотр в низу формы создания шаблона.

    Добавление кода шаблона

    Код шаблона добавляем в то же поле — Внешний вид шаблона сайта.

    • Откройте в браузере файл index.html из папки с ранее скачанным архивом шаблона. В окне браузера откроется тестовый дизайн:

    Теперь перенесём html-код дизайна в поле Внешний вид шаблона сайта.

    • Откройте исходный код страницы, выделите весь код расположенный между тегами <body> </body> (без самих тегов) и скопируйте его в буфер.
    • Вставьте код в поле Внешний вид шаблона сайта перед закрывающим тегом </body> (аккуратно, не удалите служебный код показа административной панели)
    • В коде <body> шаблона нет заданных дизайнером параметров. Вставьте в этот тег параметры из аналогичного тега файла index.html: BGCOLOR="#FFFFFF" TEXT="#000000" leftmargin="0" topmargin="0" marginwidth="0" marginheight="0".

    Задание разделителя

    Установите разделитель #WORK_AREA# нажав на [dw]кнопку с соответствующим названием[/dw][di][/di]. Он должен стоять строго перед меткой <!-- #Begin_Article -->.

    Примечание: Найти тот или иной нужный участок кода на большой странице бывает достаточно сложно, но в исходном коде страницы нашего тестового шаблона расставлены специальные метки для облегчения поиска. Их общий вид:

    <!-- #Begin******** -->
    <!-- #End******** -->

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

    <!-- #Begin_Article -->
    

    Теперь можно сохранять созданный шаблон. До сих пор это было невозможно, так как был не задан разделитель #WORK_AREA#.

    Добавление графики и стилей

    Для полного и правильного отображения дизайна нужно добавить графику и стили.

    Графика

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

    • Откройте папку, где расположен шаблон (/bitrix/templates/test).
    • Создайте в ней папку /images.

      Примечание: Поместить картинки можно и в корневой папке /images. Но размещение изображений по предлагаемому нами варианту имеет одно преимущество. При копировании (или экспорте) шаблона вам не придется заново импортировать картинки в новый шаблон. Копия создастся сразу с папкой с картинками.

    • Загрузите графические файлы из архива дизайна. Файлы размещены в папке Сайтizo

    Теперь нам осталось только изменить пути до картинок в кодах шаблона. В исходном дизайне пути до картинок прописаны как izo/. Поменяйте их на /bitrix/templates/test/images/.

    Примечание: Ручная замена утомительна. Произведите такую замену в текстовом редакторе с функцией автоматической замены (Bred2, AkelPad и подобных, но не в MS Word), либо в используемой вам среде разработки типа PHP storm.

    • Сохраните изменения.
    • Откройте сайт с помощью кнопки Предпросмотр.

    Вы увидите, что теперь все картинки, кроме картинок в колонках Блоги и Галерея, а так же картинки в теле статьи «О прикорме», отображаются. При этом не прорисованы границы, и на сайте отображен текст (перечисление разделов), видео и картинки не предусмотренные в дизайне.

    Картинки в колонках Блоги и Галерея, а так же картинка в теле статьи «О прикроме» не отображаются потому, что они не загружены на сайт. (Они были расположены не в папке izo.) На это можно не обращать внимание, так как все равно нам удалять участки кода с этими картинками и заменять их на компоненты «1С-Битрикс: Управление сайтом».

    К ненужному тексту, видео и картинкам мы еще вернемся позже. (Кстати, появившийся «самостоятельно» текст может быть совсем не таким, как на нашей иллюстрации – это зависит от того какому сайту с каким содержанием вы применили шаблон.)

    Стили

    • Откройте на редактирование шаблон сайта на закладке Стили шаблона.

      Вы увидите, что поле Файл стилей шаблона (template_styles.css) пустое, то есть не задано никаких стилей. Стили применяемые в проекте указаны в файле Сайтizocode.css.

    • Перенесите стили из файла в это поле.
    • Сохраните изменения.

    Результат:

    Интеграция дизайна в систему

    Шаблон сайта на четверть создан. «На четверть» — это потому что в имеющемся у нас на данном моменте шаблоне есть только html-код, но отсутствуют функции вызова программных компонентов. А их интеграция – это существенный объем работы.

    Статическая информация, которая не нуждается (либо редко нуждается) в замене, как правило, размещается в статических зонах Footer’а и Header’а. Заменить ее можно в кодах самих файлов, но делать это придется квалифицированному разработчику. Либо разработчик должен организовать эту замену с помощью компонентов системы силами контент-менеджеров сайта.

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

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

    Интегрировать код любого компонента в код шаблона в Bitrix Framework можно следующими способами:

    • Вставкой кода из [dw]пользовательской документации[/dw][di]Нажмите на рисунок, чтобы увеличить

      Пользовательская документация

      .[/di]

      ;

    • С помощью визуального редактора напрямую (невозможно с версии 14.0, так как отключено использование редактора при редактировании шаблонов сайта).

    Внедрение компонентов в дизайн можно, в принципе, производить в любом порядке. Мы начнем с компонента авторизации. Но сначала разберёмся с тем, что же такое Рабочая область.

    #WORK_AREA# – Рабочая область

    #WORK_AREA# — основная рабочая область. Рабочая область страницы, в которой размещаются собственно информационные материалы сайта. В качестве Основной рабочей области может подключаться как физический файл, так и создаваемый системой на основе комплексных компонентов, динамический код.

    Поясним суть Рабочей области #WORK_AREA#. При создании шаблона мы загружали готовую нарезку дизайна, в которой предусмотрен текст в центральной части, колонка в правой части (фотогалерея), левая колонка с анонсами блогов. На данный момент все они: текст, левая и правая колонки входят что-то в Header, что-то в Footer. Если при формировании шаблона левая и правая колонки могут то включаться в Footer или Header, то исключаться из него, но основной текст всегда будет в #WORK_AREA#.

    Если вы создавали сайт для тестов на установке демо-дистрибутива по способу описанному здесь, то на вашем сайте отобразятся именно те лишние элементы, которые мы называли ранее. Это происходит потому, что используется та же папка для сайта, что и в демо-версии. Соответственно, отображаются элементы уже размещённые на странице index.php.

    • Скопируйте в буфер обмена текст статьи, которая у нас сейчас видна на шаблоне. Только текст, без картинки!
    • С Публичной части откройте для редактирования индексную страницу сайта в визуальном редакторе. (Либо откройте для редактирования файл index.php в корне сайта)
    • Удалите из страницы все тексты и ярлыки компонентов. Кстати, этим действием вы удалили весь тот «непрошенный» текст, видео и картинки, которые появились у нас на сайте после вставки дизайна в шаблон.
    • В самом начале страницы введите текст: Тестовый проект. Главная страница.
    • В поле редактора вставьте текст из буфера обмена.
    • Сохраните внесенные изменения.

    Вы увидите, что статья у нас теперь отражена дважды: один раз как часть #WORK_AREA# (то, что мы только что добавили), второй раз как часть Footer’а (текст, который стоял раньше):

    Вот из Footer’а нам и надо текст убрать.

    • Удалите текст из кода шаблона сайта, вместе со строкой и ячейкой. Метки:
      <!-- #Begin_Article -->
      <!-- #End_Article -->
    • Сохраните внесенные изменения.
    • Теперь при просмотре сайта с публички текст будет отображаться только один раз и он выводится из Рабочей области #WORK_AREA#.

    Авторизация

    Переход на страницу авторизации в «1С-Битрикс: Управление сайтом» можно реализовать как с помощью html, так и с помощью компонента авторизации. Мы опишем оба способа.

    Авторизация с помощью HTML кода

    За авторизацию в html-коде тестового дизайна отвечает код:

    <img width="4" height="4" src="/bitrix/templates/test/images/punkt_top.gif" /> 
           <b><a href="#" class="text"><font size="1" color="#6e6e6e">Регистрация</font></a> 
    <img width="4" height="4" src="/bitrix/templates/test/images/punkt_top.gif" />
           <b><a href="#" class="text"><font size="1" color="#6e6e6e">Вход</font></a></b>

    Найти этот код довольно просто по меткам:

    <!-- #Begin_Auth -->
    <!-- #End_Auth -->

    Чтобы реализовать авторизацию в html:

    • Замените знак # во втором теге <a href="#" class="text"> на ссылку на /auth.
    • Замените в первой ссылке знак # на /auth.php?register=yes
    • Сохраните внесенные изменения.

    Авторизация встроена. Можете закончить сессию и вновь авторизоваться для проверки работы.

    Компонент авторизации

    Интеграция компонента авторизации не сложна, но требует внимания.

    • Удалите описанный выше html-код из общего кода шаблона. Метки:

      <!-- #Begin_Auth -->
      <!-- #End_Auth -->
    • Откройте страницу [comp include_system_auth_form]Компонента авторизации[/comp] в пользовательской документации.
    • Скопируйте в буфер код вызова компонента.
    • Вставьте код между указанными метками.
    • Настройте компонент:
      • У параметра REGISTER_URL укажите значение /auth/ (системный путь к странице регистрации в дистрибутиве по умолчанию).
      • У параметра PROFILE_URL укажите значение /personal/profile/ (ссылка на персональный раздел в дистрибутиве по умолчанию).
    • Сохраните внесенные изменения.

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

    Несоответствия в выводе данных

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

    • Скопируйте папку auth в папку bitrixtemplatestestcomponentsbitrixsystem.auth.form.
    • Откройте на редактирование шаблон сайта.
    • В коде компонента system.auth.form установите название шаблона: auth (в кавычках после названия компонента):
      <?$APPLICATION->IncludeComponent("bitrix:system.auth.form","auth",Array(
           "REGISTER_URL" => "/auth/",
      	  "FORGOT_PASSWORD_URL" => "",
           "PROFILE_URL" => "/personal/profile/",
           "SHOW_ERRORS" => "Y" 
           )
      );?>
    • Сохраните внесенные изменения.

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

    Какие расхождения мы видим?

    • Не соответствуют местами порядок ссылок. Сначала Войти, потом – Регистрация, вместо обратного, как в дизайне;
    • Само слово Войти не соответствует утвержденному Вход;
    • Не соответствуют шрифты и их форматирование в надписях;
    • Не соответствуют картинки.
    • всплывающее окно авторизации тоже «не вписывается» в дизайн.

    Кастомизация шаблона компонента

    Примечание: Если у вас включено кеширование компонентов, то возможна ситуация, когда после внесения изменений в шаблоны компонента или его файл стилей вы не заметите этих изменений. В этом случае надо просто обновить кеш компонента воспользовавшись кнопкой Обновить на Административной панели. А лучше отключить кеширование на период работы с дизайном совсем.

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

    Для замены слова Войти на Вход необходимо найти файл в системе, где задается эта надпись и поменять ее.

    • Перейдите в раздел Контент > Структура сайта > Файлы и папки /bitrix/templates/test/components/bitrix/system.auth.form/auth/lang/ru/. В этой папке есть файл: template.php.

      Примечание: Обратите внимание на определенную логику расположения информации в файлах компонента. Вот она: system.auth.form/auth/lang/ru/. Эта структура типична для всех компонентов системы: имя_компонента/применяемый_шаблон/языковая_папка/конкретный_язык. В папке конкретного языка расположены все текстовые значения, используемые в этом шаблоне. Так вы можете поменять тексты в любом компоненте.

    • В колонке действий для строки этого файла выберите Редактировать как PHP. Откроется форма редактирования файла:

      В самой первой строке мы видим слово «Войти», которое прописано для параметра AUTH_LOGIN_BUTTON. Именно это слово и выводится в форме авторизации.

    • Замените Войти на Вход.

      Примечание: При утверждении шаблона дизайна бывает очень трудно предусмотреть все возможные моменты, связанные с дизайном. Например, в нашем случае мы меняем слово «Войти» на «Вход», но в утвержденном дизайне ничего не говорится про то, как выглядит страница у авторизованного посетителя. А у авторизованного посетителя будет видно слово «Выйти». Логично, что мы заменим слово «Выйти» на слово «Выход», по аналогии со словом «Вход». Несмотря на то, что на это нет прямого указания в дизайне.

    • Сохраните внесенные изменения.

    Текст шаблона адаптирован к требованиям дизайна. Теперь займемся внешним видом.

    • Откройте для редактирования шаблон компонента. Нам нужно заменить картинки. Работа упрощается тем, что в тестовом дизайне использована одна картинка на все три позиции. Если бы картинки были разные, то нам бы пришлось повторять описываемую ниже операцию несколько раз, меняя названия картинок.

      Если посмотреть исходный html-код авторизации, то видно, что имя картинки в этом коде: punkt_top.gif. Эта картинка после загрузки на тестовый сайт расположена в папке /bitrix/templates/test/images. Значит, путь до картинок в рамках системы будет такой: /bitrix/templates/test/images/punkt_top.gif.

    • В диалоге с кодом шаблона /bitrix/templates/test/components/bitrix/system.auth.form/auth/template.php найдите ссылки на картинки, используемые в шаблоне. Так как в теги картинки включен php-код и найти их будет не просто, то приведем ссылки на картинки полностью.
      Первая и вторая:

      <img src="<?=$templateFolder?>/images/login.gif" width="10" height="11" border="0" alt="">

      Третья:

      <input type="image" src="<?=$templateFolder?>/images/login.gif" alt="<?=GetMessage("AUTH_LOGOUT_BUTTON")?>">
    • Замените в шаблоне пути до всех картинок с тех, что прописаны в коде, на указанный выше адрес картинки, то есть у вас должно получиться.
      Первая и вторая:

      <img src="<?=SITE_TEMPLATE_PATH?>/images/punkt_top.gif" border="0" alt="">

      Третья:

      <input type="image" src="<?=SITE_TEMPLATE_PATH?>/images/punkt_top.gif" alt="<?=GetMessage("AUTH_LOGOUT_BUTTON")?>">
    • Сохраните внесенные изменения.

    Завершите сессию и авторизуйтесь заново. Вы увидите, что картинки заменены. Теперь займемся остальным. Начнем с всплывающего окна авторизации. Что нас не устраивает в нем? Фон, отличающийся от фона сайта. Кому-то еще может показаться, что не соответствует размер окна или еще что. Эти вопросы – дело вкуса и мы оставляем на ваше усмотрение изменение этих параметров.

    • Откройте для редактирования шаблон компонента. Просматривая шаблон компонента, мы увидим, что за оформление всплывающего окна отвечает стиль login-form-window: код этого окна расположен в теге <div id="login-form-window"> </div>. В то время как на надписи Вход и Регистрация стиль не задан. Следовательно, нам надо задать стиль для этих надписей. А стиль login-form-window изменить так, чтобы он соответствовал дизайну.
    • Откройте для редактирования CSS файл шаблона компонента Авторизация.
    • Откройте исходные коды тестового сайта. Дизайнер задал фон таблиц в цвете #DEDEE2, а границы в цвете #bfbfbf. Например, ячейка:
      <td class="br" bgcolor="#DEDEE2" bordercolor="#bfbfbf">
      ...
      </td>

      Воспользуемся этими же цветами и мы.

    • Замените цвет в строке background:#F5F5ED на background: #DEDEE2.
    • Замените цвет в строке border:1px solid #000; на border:1px solid #bfbfbf;.
    • Сохраните внесенные изменения.

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

    Теперь нам нужно изменить вид надписей Вход и Регистрация. Сделать это можно разными способами. Но мы воспользуемся опять же файлом стилей. Пусть стиль, отвечающий за оформление этих надписей называется login-info. Соответственно, стиль активной ссылки будет #login-info а.

    • Откройте для редактирования шаблон компонента. Установим теги для стиля login-info. Установить эти теги нам придется два раза, так как html-код не должен пересекаться с php-кодом.
    • Установите тег <div id="login-info"> первый раз сразу за тегом </div>, закрывающим предыдущий стиль login-form-window.
    • Установите тег </div>, закрывающий использование стиля перед php-кодом <?else:?>.
    • Установите тег <div id="login-info"> второй раз сразу за тегом <?else:?>.
    • Установите тег </div>, закрывающий использование стиля перед завершающим php-кодом <?endif?>.
    • Сохраните внесенные изменения.

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

    • Откройте для редактирования файл CSS компонента.
    • Добавьте в файл стилей строки:
      	#login-info {color:#3B3B3B; font-size:70%; font-weight:bold;}
      	#login-info a {color: #666666;}
    • Сохраните внесенные изменения.

    Завершите сеанс и авторизуйтесь вновь. Вы увидите, что надписи изменили шрифт, размер и цвет. Теперь они почти полностью соответствуют утвержденному дизайну.

    Мы закончили встраивание модуля авторизации и теперь можем двигаться дальше и заняться меню сайта.

    Меню сайта

    Меню бывают разные. Бывают горизонтальные и вертикальные, левые и правые, верхние и нижние. Чаще всего встречаются левые вертикальные меню и верхние горизонтальные. В нашем дизайне встречаются три вида меню:

    • Левое вертикальное, классическое;
    • Нижнее горизонтальное;
    • Верхнее горизонтальное – частичное.

    Для выполнения работ вам нужно будет создать два типа меню, которых нет в дистрибутиве по умолчанию. Эти типы меню будут использоваться в примере. В примере их назвали podmenu и down.

    Работу начнем с левого вертикального меню.

    Левое меню

    Откройте для редактирования шаблон сайта. Первым делом нужно определить часть html-кода, отвечающего за один элемент меню. В нашем тестовом дизайне код, отвечающий за меню расположен между:

    <!-- #Begin_Left_Menu -->
    <!-- #End_Left_Menu -->

    Добавление компонента

    • Скопируйте код, имитирующий меню в отдельный текстовый файлик. Он нам понадобится.
    • Удалите из кода шаблона код меню.
    • Откройте страницу документации с описанием компонента [comp include_menu]Меню[/comp] (bitrix:menu).
    • Перенесите код вызова компонента в шаблон сайта между указанными метками.
    • Сохраните внесенные изменения.

    При просмотре с публичной части вы увидите, что изменилось оформление меню (стало таким же как и в демосайте) и пункты меню (компонент автоматически вывел пункты меню из демосайта). При необходимости настройте пункты меню.

    Меню совершенно не подходит по оформлению. Слева что получилось, справа — что должно быть:

    Кастомизация шаблона

    • Скопируйте шаблон компонента Меню в папку тестового шаблона. Откроется форма для редактирования шаблона
    • В шаблоне компонента само меню строится через применение списка с помощью тегов <ul> <li>элемент маркированного списка</li> </ul>. Мы от списка откажемся, у нас само меню будет строиться через таблицы с помощью тегов таблиц. Поэтому удалите теги <ul>, </ul>. Позже в шаблоне компонента мы заменим и теги <li>, </li>.
    • Удалите атрибут с указанием на стиль class="left-menu", так как мы его применять не будем.
    • Откройте текстовый файл, где вы сохраняли вырезанный html-код таблиц меню оригинального дизайна. Каждый пункт меню — это отдельная таблица. Посмотрим код оформления пункта меню Главная:
      <table width="99%" cellspacing="0" cellpadding="0" border="0"> 
       <tbody> 
          <tr><td width="25" height="25" background="/bitrix/templates/test/images/fot_tabl_centr.jpg">
                 <img src="/bitrix/templates/test/images/ukazatel_red.gif" width="25" height="25"  /></td> 
             <td width="1" valign="top" height="25" background="/bitrix/templates/test/images/fot_tabl_c.gif">
                 <img src="/bitrix/templates/test/images/fot_tabl_w.gif" width="2" height="2"  /></td> 
             <td height="25" background="/bitrix/templates/test/images/fot_tabl_centr.jpg">
                 <img src="/bitrix/templates/test/images/pix.gif" width="10" height="10"  /><b>
                 <a href="#" class="text" >
                 <font size="2" color="#666666">Главная</font></a></b></td> 
             <td width="1" valign="top" height="25" background="/bitrix/templates/test/images/fot_tabl_c.gif">
                 <img src="/bitrix/templates/test/images/fot_tabl_w.gif" width="2" height="2"  /></td> 
           </tr>
                         
           <tr><td height="1" background="/bitrix/templates/test/images/fot_tabl_c.gif">
                  <img src="/bitrix/templates/test/images/fot_tabl_w.gif" width="2" height="2"/></td> 
              <td width="1" height="1">
                  <img src="/bitrix/templates/test/images/fot_tabl_w.gif" width="2" height="2"/></td> 
              <td height="1" background="/bitrix/templates/test/images/fot_tabl_c.gif">
                  <img src="/bitrix/templates/test/images/fot_tabl_w.gif" width="2" height="2"  /></td> 
              <td width="1" height="1">
                  <img src="/bitrix/templates/test/images/fot_tabl_c.gif" width="2" height="2"/>
               </td> 
           </tr>
        </tbody>
      </table>

      HTML-код представляет собой таблицу с двумя строками. Вторая строка – декоративная. Сама ссылка должна формироваться в первой строке, если быть точным в третьей ячейке первой строки.
      В эту ячейку и надо нам отредактировать.

    • Удалите из третьей ячейки из строки <b><a href="#" class="text"><font size="2" color="#666666">Главная</font></a></b> теги <b></b>; а также часть кода <font size="2" color="#666666">Главная</font>. Мы удалили параметры форматирования текста, которые будут определяться теперь не шаблоном, а стилями.
    • Замените параметр class=»text» на class=»left-menu». Этим мы указали название стиля, который должен применяться для названий разделов в меню. Теперь нам нужно заменить # — символ адреса ссылки на php-код, объясняющий системе, где искать название раздела и ссылку на него.
    • Просматривая структуру шаблона можно увидеть, как отображаются в php-коде шаблона ссылки, на которые происходит переход при выборе пункта меню. Они располагаются между html-тегами <LI>, которые определяют отдельный элемент списка. Элементов списка – два, так как в шаблоне предусмотрено выделение активной ссылки. В исходном дизайне такое выделение не предусмотрено. Это значит, что мы должны объединить представление выбранной и простой ссылки в php-коде.

      То есть, вместо

      <?if($arItem["SELECTED"]):?>
      <li><a href="<?=$arItem["LINK"]?>" class="selected"><?=$arItem["TEXT"]?></a></li>
                                     <?else:?>
      <li><a href="<?=$arItem["LINK"]?>"><?=$arItem["TEXT"]?></a></li>
                                     <?endif?>

      php-код, вызывающий ссылку и текст должен стать таким:

      <?=$arItem["LINK"]?>"<?if($arItem["SELECTED"]):?><?else:?><?endif?>"><?=$arItem["TEXT"]?>
      

      В коде исходного дизайна вместо # поставьте указанный php-код.

    Теперь можно переходить к переносу html-кодов, определяющих внешний вид строки меню. PHP-код и html-код не должны пересекаться между собой, а должны быть включены друг в друга. Потому нам нужно сейчас очень внимательно подходить к размещению тегов в шаблоне. Прежде всего, разместим теги таблицы. Так как меню должно формироваться внутри таблицы, значит php-код, запускающий цикл, должен лежать внутри таблицы.

    • Вставьте открывающий тег <table width="99%" cellspacing="0" cellpadding="0" border="0"> перед php функцией foreach, задающей цикл.
    • Вставьте тег </table>, закрывающий таблицу сразу за функцией endforeach, завершающей php-цикл.

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

    Так как мы уже интегрировали код собственно php-цикла в html-код, то нам осталось только вставить полученную html-конструкция его между тегами начала и конца php-цикла.

    • Выделите в исходном html-коде дизайна меню теги строк таблицы:
      <tr> 
          <td width="25" height="25" background="/bitrix/templates/test/images/fot_tabl_centr.jpg"> 
             <img width="25" height="25" src="/bitrix/templates/test/images/ukazatel_red.gif" /></td> 
          <td width="1" valign="top" height="25" background="/bitrix/templates/test/images/fot_tabl_c.gif">
             <img width="2" height="2" src="/bitrix/templates/test/images/fot_tabl_w.gif" /></td>
          <td height="25" background="/bitrix/templates/test/images/fot_tabl_centr.jpg">
             <img width="10" height="10" src="/bitrix/templates/test/images/pix.gif" />
             <a class="left-menu" href="<?=$arItem["LINK"]?>"<?if($arItem["SELECTED"]):?>
             <?else:?> <?endif?>"> <?=$arItem["TEXT"]?> </a></td>    
          <td width="1" valign="top" height="25" background="/bitrix/templates/test/images/fot_tabl_c.gif">
             <img width="2" height="2" src="/bitrix/templates/test/images/fot_tabl_w.gif" /></td>
      </tr>
      <tr>
          <td height="1" background="/bitrix/templates/test/images/fot_tabl_c.gif">
             <img width="2" height="2" src="/bitrix/templates/test/images/fot_tabl_w.gif" /></td>
          <td width="1" height="1">
             <img width="2" height="2" src="/bitrix/templates/test/images/fot_tabl_w.gif" />
          </td>
          <td height="1" background="/bitrix/templates/test/images/fot_tabl_c.gif">
             <img width="2" height="2" src="/bitrix/templates/test/images/fot_tabl_w.gif" />
          </td>
          <td width="1" height="1">
             <img width="2" height="2" src="/bitrix/templates/test/images/fot_tabl_c.gif" />
          </td>
      </tr>
    • Замените этим кодом следующий код:
      <?if($arItem["SELECTED"]):?>
      <li><a href="<?=$arItem["LINK"]?>" class="selected"><?=$arItem["TEXT"]?></a></li>
      <?else:?>
      <li><a href="<?=$arItem["LINK"]?>"><?=$arItem["TEXT"]?></a></li>
      <?endif?>
    • Сохраните внесенные изменения.

    Задание стилей

    • Откройте для редактирования файл с CSS стилями шаблона. В этом файле видно для вывода текста используется стиль left-menu.
    • Переименуйте стиль ul.left-menu в просто .left-menu.
    • Вставьте в параметры стиля left-menu следующие строки:
      font-size:80%;
      color:#666666;
      font-weight:bold;
      text-decoration:none;
    • Так как у нас по дизайну не предусмотрены другие стили оформления (активной ссылки, посещенной ссылки, подсветки ссылки), то все остальные стили просто удалите.
    • Сохраните внесенные изменения.

    Результат работы:

    Нижнее горизонтальное меню

    Левое вертикальное меню у нас будет меняться в зависимости от открытого раздела. Но для упрощения перемещения между разделами заказчик пожелал сохранить возможность доступа к любому разделу с любой страницы сайта. С этой целью нужно иметь одно меню, где-нибудь в не очень мешающемся месте, в котором будут выводиться ссылки на все основные разделы сайта. Таким у нас будет нижнее горизонтальное меню.

    Добавление компонента

    • Откройте для редактирования шаблон тестового сайта.
    • Скопируйте в отдельный текстовый файл а затем удалите код таблицы, расположенной между метками:
      <!-- #Begin_down_Menu -->
      <!-- #End_down_Menu -->
    • Перейдите в режим визуального редактирования.
    • Добавьте на то место, где находилась эта таблица компонент Меню так, как мы это уже делали с левым меню.
    • Откройте для редактирования диалог настроек параметров компонента. В отличие от левого меню здесь нам нужно их настроить.
    • В поле Тип меню для первого уровня выберите down (Нижнее).

      Примечание: Такого типа меню в дистрибутиве по умолчанию нет. Мы предполагаем что вы его создали заранее.

    • Сохраните внесенные изменения.
    • Скопируйте в место между этими тегами код вызова компонента Меню, так же как и в случае с левым меню.
    • Параметру ROOT_MENU_TYPE установите значение down. Этим мы назначили новый тип меню для этого компонента.
    • Сохраните внесенные изменения.

    Создание меню

    Создайте нижнее меню. Оно должно повторять разделы левого.

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

    Кастомизация шаблона компонента

    • Скопируйте шаблон компонента по умолчанию в текущий шаблон сайта, как мы это делали с левым меню.
    • Откройте для редактирования шаблон компонента.
    • Из скопированного исходного кода вставьте в шаблон вместо кода <ul class="left-menu"> открывающий код таблицы:
      <table width="1024" height="10" cellspacing="0" cellpadding="0" border="0" class="text"> 
    • Установите закрывающий тег таблицы вместо кода </ul>
    • Замените class="text" на class="down-menu". (Пусть такое имя будет у стилей для нижнего меню).
    • Перенесите этот параметр class="down-menu" в строку
      <a href="<?=$arItem["LINK"]?>"><?=$arItem["TEXT"]?></a>

      после открывающего тега <a>/

    • Повторите последние действия для строки
      <a href="<?=$arItem["LINK"]?>" class="selected"><?=$arItem["TEXT"]?></a>
    • В параметре class="down-menu" этой строки добавьте букву а для стиля активной ссылки.
    • Задайте открывающий тег строки таблицы сразу за открывающим тегом таблицы и закрывающий тег строки перед закрывающим тегом таблицы.
    • Задайте открывающий тег ячейки с параметром align="center" на месте открывающего тега <li> отдельного элемента списка. И задайте закрывающий тег ячейки на место закрывающего тега отдельного элемента списка.
    • Удалите лишнюю пару тегов отдельного элемента списка
    • Добавьте код картинки из текстового файла с исходным кодом (<img src="/bitrix/templates/test/images/punkt_top.gif"/>) в код шаблона после тега <td align="center">.
    • Сохраните внесенные изменения.

    Кастомизация стилей

    • Откройте для редактирования стили шаблона компонента.
    • Отредактируйте файл стилей по аналогии с тем, как это делалось в левом меню.

    Результат работы:

    Верхнее частичное меню

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

    Механизм интеграции этого меню абсолютно идентичен механизму интеграции, описанному выше. За исключением пары моментов:необходимо использовать отдельный тип меню (part), используется другая картинка.

    • Разместите код вызова компонента меню между метками:
      <!-- #Begin_part_menu -->
      <!-- #End_part_menu -->
    • Скопируйте шаблон компонента и файл css из шаблона нижнего меню;
    • Замените в шаблоне название картинки (punkt_top на punkt_red.)
    • Сохраните внесенные изменения.
    • Создайте меню.

    Цепочка навигации

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

    Размещение компонента

    • Откройте для редактирования шаблон тестового сайта.
    • Найдите и удалите код, имитирующий цепочку навигации на шаблоне сайта, расположенную между
      <!-- #Begin_nav -->
      <!-- #End_nav -->
    • Перенесите на место между метками код вызова компонента [comp include_breadcrumb]Навигационная цепочка[/comp] (breadcrumb) из документации.

    Кастомизация шаблона

    Цепочка отображена, но ее вид не соответствует оформлению сайта. Повторим процесс «подгонки». Правда, оговоримся сразу, активную ссылку красным (как это у нас в дизайне) можно сделать только в том случае, если в шаблоне написать условие php: если ссылка последняя, то выделить цветом. Мы этим заниматься не будем, пусть это будет домашним заданием. Поэтому последняя ссылка останется у нас серой.

    Примечание: последнюю ссылку можно выделять с помощью CSS, используя псевдокласс: last-child.

    • Скопируйте шаблон Цепочки навигации.
    • Откройте для редактирования файл CSS.
    • Используя за основу файл CSS левого меню, отредактируйте отображение Цепочки навигации.

    Компонент Новые сообщения блогов

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

    Размещение и настройка компонента

    Размещение компонента

    • Откройте для редактирования шаблон тестового сайта.
    • Найдите и удалите код, имитирующий вывод новых сообщений блога, расположенный между:
      <!-- #Begin_blog -->
      <!-- #End_blog -->
    • Разместите между метками код вызова компонента[comp include_blog_new_posts]Новые сообщения[/comp].
    • Сохраните внесенные изменения.

    Настройка компонента

    Перед настройкой компонента необходимо проверить настройки (и, при необходимости изменить) групп блогов и блога администратора, на который мы будем настраивать компонент.

    • Перейдите в Панель управления на страницу Сервисы > Блоги > Группы блогов. Откроется форма Группы блогов.
    • Переопределите какую-нибудь группу блогов на ваш тестовый сайт.
    • Сохраните внесенные изменения.
    • Перейдите в Панель управления на страницу Сервисы > Блоги > Блоги. Откроется список блогов, имеющихся в дистрибутиве.
    • Привяжите один или несколько блогов к выбранной выше группе.

    Перейдя в публичку, вы увидите, что блоги появились:

    • Вызовите диалог настройки свойств компонента.
    • В поле Шаблон пути к странице с сообщением блога группы необходимо настроить путь до страницы с сообщениями. Если используется дистрибутив по умолчанию, то путь будет вида:/blog/group/#group_id#/post/#post_id#/
    • В исходном дизайне у нас предусмотрен вывод только трех последних сообщений. Потом всегда можно эти настройки «переиграть». В поле Количество результатов, выводимых на страницу поставьте значение 3.

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

    • Сохраните внесенные изменения.

    Кастомизация вывода данных

    В исходном варианте дизайна в качестве новых сообщений выводится только текст. Без авторства и даты, но с аватаром. Задумка красивая, но есть одно «Но». Эта идея с аватарами накладывает ограничения на их размер, что не совсем удобно. Это – мелочи, в общем-то, можно потерпеть мелкие аватары в самих блогах. Серьезнее другое: для реализации этого варианта нам придется модифицировать код самого компонента. Это выходит за рамки примера. Потому отойдем от дизайна и вместо аватара выведем картинку с блога с параметрами, заданными в исходном шаблоне.

    Значит нам нужно:

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

    Займемся этими изменениями.

    • Скопируйте шаблон компонента в текущий шаблон сайта и откройте его для редактирования.
    • Найдите и удалите из шаблона код:
      <div class="blog-author">
      	<?if($arParams["SEO_USER"] == "Y"):?>
      		<noindex>
      		<a class="blog-author-icon" href="<?=$arPost["urlToAuthor"]?>
                      " title="<?=GetMessage("BLOG_BLOG_M_TITLE_BLOG")?>" rel="nofollow"></a>
      		</noindex>
      	<?else:?>
      		<a class="blog-author-icon" href="<?=$arPost["urlToAuthor"]?>"
                      title="<?=GetMessage("BLOG_BLOG_M_TITLE_BLOG")?>"></a>
      	<?endif;?>
      	<?
      	if (COption::GetOptionString("blog", "allow_alias", "Y") == "Y" && (strlen($arPost["urlToBlog"]) > 0 
              || strlen($arPost["urlToAuthor"]) > 0) && array_key_exists("BLOG_USER_ALIAS", $arPost) 
              && strlen($arPost["BLOG_USER_ALIAS"]) > 0)
      		$arTmpUser = array(
      			"NAME" => "",
      			"LAST_NAME" => "",
      			"SECOND_NAME" => "",
      			"LOGIN" => "",
      			"NAME_LIST_FORMATTED" => $arPost["~BLOG_USER_ALIAS"],
      		);
      	elseif (strlen($arPost["urlToBlog"]) > 0 || strlen($arPost["urlToAuthor"]) > 0)
      		$arTmpUser = array(
      			"NAME" => $arPost["~AUTHOR_NAME"],
      			"LAST_NAME" => $arPost["~AUTHOR_LAST_NAME"],
      			"SECOND_NAME" => $arPost["~AUTHOR_SECOND_NAME"],
      			"LOGIN" => $arPost["~AUTHOR_LOGIN"],
      			"NAME_LIST_FORMATTED" => "",
      		);	
      	?>
      	<?
      	$GLOBALS["APPLICATION"]->IncludeComponent("bitrix:main.user.link",
      		'',
      		array(
      			"ID" => $arPost["AUTHOR_ID"],
      			"HTML_ID" => "blog_new_posts_".$arPost["AUTHOR_ID"],
      			"NAME" => $arTmpUser["NAME"],
      			"LAST_NAME" => $arTmpUser["LAST_NAME"],
      			"SECOND_NAME" => $arTmpUser["SECOND_NAME"],
      			"LOGIN" => $arTmpUser["LOGIN"],
      			"NAME_LIST_FORMATTED" => $arTmpUser["NAME_LIST_FORMATTED"],
      			"USE_THUMBNAIL_LIST" => "N",
      			"PROFILE_URL" => $arPost["urlToAuthor"],
      			"PROFILE_URL_LIST" => $arPost["urlToBlog"],							
      			"PATH_TO_SONET_MESSAGES_CHAT" => $arParams["~PATH_TO_MESSAGES_CHAT"],
      			"PATH_TO_VIDEO_CALL" => $arParams["~PATH_TO_VIDEO_CALL"],
      			"DATE_TIME_FORMAT" => $arParams["DATE_TIME_FORMAT"],
      			"SHOW_YEAR" => $arParams["SHOW_YEAR"],
      			"CACHE_TYPE" => $arParams["CACHE_TYPE"],
      			"CACHE_TIME" => $arParams["CACHE_TIME"],
      			"NAME_TEMPLATE" => $arParams["NAME_TEMPLATE"],
      			"SHOW_LOGIN" => $arParams["SHOW_LOGIN"],
      			"PATH_TO_CONPANY_DEPARTMENT" => $arParams["~PATH_TO_CONPANY_DEPARTMENT"],
      			"PATH_TO_SONET_USER_PROFILE" => ($arParams["USE_SOCNET"] == "Y" ? 
                                         $arParams["~PATH_TO_USER"] : $arParams["~PATH_TO_SONET_USER_PROFILE"]),
      			"INLINE" => "Y",
      			"SEO_USER" => $arParams["SEO_USER"],
      		),
      		false,
      		array("HIDE_ICONS" => "Y")
      	);
      	?>	
      	</div>

    Этим мы удалили картинку по умолчанию, имя владельца блога, выводимое через компонент.

    • Найдите и удалите в коде шаблона код:
      <div class="blog-mainpage-meta">
      		<a href="<?=$arPost["urlToPost"]?>
                      " title="<?=GetMessage("BLOG_BLOG_M_DATE")?>"><?=$arPost["DATE_PUBLISH_FORMATED"]?></a>
      		<?if(IntVal($arPost["VIEWS"]) > 0):?>
      			<span class="blog-vert-separator"></span> <a href="<?=$arPost["urlToPost"]?>"
                              title="<?=GetMessage("BLOG_BLOG_M_VIEWS")?>"><?=GetMessage("BLOG_BLOG_M_VIEWS")?>:
                               <?=$arPost["VIEWS"]?></a>
      		<?endif;?>
      		<?if(IntVal($arPost["NUM_COMMENTS"]) > 0):?>
      			<span class="blog-vert-separator"></span> 
                              <a href="<?=$arPost["urlToPost"]?>#comments" title="<?=GetMessage("BLOG_BLOG_M_NUM_COMMENTS")?>">
                              <?=GetMessage("BLOG_BLOG_M_NUM_COMMENTS")?>: <?=$arPost["NUM_COMMENTS"]?></a>
      		<?endif;?>
      		<?if ($arParams["SHOW_RATING"] == "Y"):?>
      		<span class="rating_vote_text">
      		<span class="blog-vert-separator"></span>
      		<?
      		$APPLICATION->IncludeComponent(
      			"bitrix:rating.vote", $arParams["RATING_TYPE"],
      			Array(
      				"ENTITY_TYPE_ID" => "BLOG_POST",
      				"ENTITY_ID" => $arPost["ID"],
      				"OWNER_ID" => $arPost["AUTHOR_ID"],
      				"USER_VOTE" => $arResult[0]["RATING"][$arPost["ID"]]["USER_VOTE"],
      				"USER_HAS_VOTED" => $arResult[0]["RATING"][$arPost["ID"]]["USER_HAS_VOTED"],
      				"TOTAL_VOTES" => $arResult[0]["RATING"][$arPost["ID"]]["TOTAL_VOTES"],
      				"TOTAL_POSITIVE_VOTES" => $arResult[0]["RATING"][$arPost["ID"]]["TOTAL_POSITIVE_VOTES"],
      				"TOTAL_NEGATIVE_VOTES" => $arResult[0]["RATING"][$arPost["ID"]]["TOTAL_NEGATIVE_VOTES"],
      				"TOTAL_VALUE" => $arResult[0]["RATING"][$arPost["ID"]]["TOTAL_VALUE"],
      				"PATH_TO_USER_PROFILE" => $arParams["~PATH_TO_USER"],
      			),
      			$component,
      			array("HIDE_ICONS" => "Y")
      		);?>
      		</span>
      		<?endif;?>
      	</div>

    Этим мы удалили вывод даты сообщения, комментариев и компонент рейтинга.

    • Удалите из шаблона код:
      <div class="blog-mainpage-title"><a href="<?=$arPost["urlToPost"]?>"><?echo $arPost["TITLE"]; ?></a></div>

    Этим мы удалили название сообщения (правда, одновременно со ссылкой на само сообщение и её нам ещё предстоит вернуть).

    • Удалим из кода проверку первого сообщения и линию, разделяющие сообщения. Эта линия нам не нужна, так как в исходном дизайне сообщения разделяются рамкой.
      if($arPost["FIRST"]!="Y")
      	{
      		?><div class="blog-line"></div><?
      	}
    • Удалите блоки </div> с классами blog-mainpage-item и blog-clear-float. Они нам не нужны, так как форматирование в исходном дизайне задаётся таблицей.

    Из шаблона компонента удалено всё не нужное, но теперь надо привести вид в соответствие с задумками дизайнера.

    • Вставьте код:
      <?
        if(strlen($arPost["IMG"]) > 0)
        echo $arPost["IMG"];
          ?>

      Перед вызовом текста сообщения (<?=$arPost["TEXT_FORMATED"]?>)

    • Сохраните внесенные изменения.
    • Откройте для редактирования диалог настройки параметров компонента.
    • Посмотрите в исходном шаблоне дизайна сайта параметры картинки предпросмотра и установите эти параметры в поля Ширина картинки предпросмотра и Высота картинки предпросмотра.

      Примечание: Учтите, что компонент blog.new_posts не может производить изменение пропорций изображений. При этом параметр высоты имеет преимущество перед шириной.

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

    • Добавьте из исходного кода дизайна код таблицы сообщения блога
      <table width="99%" border="0" cellspacing="3" cellpadding="5">
                   <tr> 
                      <td class="br" bgcolor="#DEDEE2" bordercolor="#bfbfbf">

      Перед блоком <div class="blog-mainpage-content">

    • Закройте ячейку, строку и саму таблицу после закрывающего тега </div>
    • Сохраните внесенные изменения.

    Теперь нам нужно добавить картинку перехода к сообщению и установить ссылку на сообщение на эту картинку. Ссылка на сообщение у нас в исходном шаблоне была в таком виде:

    <div class="blog-mainpage-title"><a href="<?=$arPost["urlToPost"]?>"><?echo $arPost["TITLE"]; ?></a></div>

    Если из этого кода удалить код блока и вызов названия, то получим только ссылку:

    <a href="<?=$arPost["urlToPost"]?>"></a>
    • Вставьте код ссылки в код шаблона после кода вызова текста сообщения (<?=$arPost["TEXT_FORMATED"]?>)
    • Скопируйте из исходного кода код картинки (<img width="20" height="20" border="0" align="right" src="/bitrix/templates/test/images/ukaz_inf.jpg" />) и вставьте его в код ссылки.
    • Сохраните внесенные изменения.

    Теперь переход к сообщению у нас происходит с картинки. Однако пока ещё отображение текста не соответствует нужному.

    Файл CSS

    В коде шаблона есть указание на расположение файлов стилей:

    <?
    if (!$this->__component->__parent || empty($this->__component->__parent->__name) || 
       $this->__component->__parent->__name != "bitrix:blog"):
    	$GLOBALS['APPLICATION']->SetAdditionalCSS('/bitrix/components/bitrix/blog/templates/.default/style.css');
    	$GLOBALS['APPLICATION']->SetAdditionalCSS('/bitrix/components/bitrix/blog/templates/.default/themes/blue/style.css');
    endif;
    ?>

    Можно отредактировать эти файлы, а можно добавить свой файл CSS в папке C:wwwbitrixtemplatestestcomponentsbitrixblog.new_poststemplate1, что мы и сделаем.

    • Добавьте в указанную папку файл style.css.
    • Пропишите в файле:
       .blog-mainpage-content
      {
              font-size:80%;
              text-decoration:none;
      }
      
    • Сохраните внесенные изменения.

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

    Примечание: Маловероятно, что пользователи буду грузить картинки в пропорциях нужных для правильного отображения картинки на главной странице сайта. Значит разработчик должен будет предусмотреть модификацию вывода компонента с помощью файла result_modifier.php с целью формирования картинки в нужном размере.

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

    Счетчик посещений

    Для любого проекта – посещаемость очень важна. Посещения страниц регистрируются счетчиками. Счетчики можно установить внешние, скажем, от Yandex’а. А можно поставить счетчик внутренний, системный.

    Примечание: Если быть точным, то системный счетчик устанавливать не надо. Он начинает работу сразу по инсталляции 1С-Битрикс: Управление сайтом на ваш компьютер и всю информацию по посещениям вы можете посмотреть в разделе Веб-аналитика Административного раздела (Аналитика > Сводная статистика). А вот вывести данные счетчика для просмотра посетителями сайта можно, если установить соответствующий компонент.

    • Откройте для редактирования шаблон тестового сайта.
    • Скопируйте код вызова компонента Таблица статистики из документации.
    • Разместите его сразу за меткой <!-- #End_blog -->
    • Сохраните внесенные изменения.

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

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

    Баннер

    Для установки баннера в 1С-Битрикс: Управление сайтом предусмотрен специальный компонент, который так и называется Баннер. Место для него в шаблоне тегами предусмотрено, хотя зрительно в «подвале» оно не видно. Ячейка для баннера расположена между ячейками с копирайтом автора дизайна и ячейкой с логотипом клуба.

    • Откройте для редактирования шаблон тестового сайта.
    • После метки <!-- #Begin_banner --> введите любой символ, например число 123. Это нужно для того, чтобы стал виден компонент. Ненастроенный компонент не выводит баннер и поэтому он не будет отображаться при просмотре сайта с публички.
    • Скопируйте из документации код вызова компонента [comp include_advertising_banner]Баннер[/comp] и разместите между метками:
      <!-- #Begin_banner -->
      <!-- #End_banner -->

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

    Включаемые области

    Правая колонка

    Тестовый шаблон рисовался в тот момент, когда в 1С-Битрикс: Управление сайтом не было показа фотогалереи во всплывающем окне. Предложенный дизайнером на тот момент вариант сегодня — не лучший вариант. Кроме того, использование правой колонки можно не ограничивать фотографиями. Ведь в разных разделах сайта может потребоваться вывод разных данных в этой колонке. Если такая потребность возникнет, то придётся создавать новый шаблон с новым компонентом (и соответственно впоследствии поддерживать его). Мы поступим по-другому. Создадим в этом месте Включаемую область и при необходимости будем наполнять её нужным контентом в нужном разделе.

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

    • Откройте для редактирования шаблон сайта.
    • Удалите коды, имитирующие фотогалерею в правой таблице, расположенные между
      <!-- #Begin_right_Column -->
      <!-- #End_right_Column -->
    • Добавьте в это место код вызова компонента [comp include_main_include]Включаемая область[/comp] из документации.
    • Сохраните внесенные изменения.
    • Скопируйте и откройте для редактирования шаблон области.
    • Из исходного кода дизайна возьмите код заголовка области между метками
      <!-- #Begin_right_Column -->
      <!-- #Begin_Foto -->
    • Сохраните внесенные изменения.

    Теперь правая колонка у нас выглядит так:

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

    На главной странице должна быть фотогалерея. Значит нужно разместить во включаемой области соответствующий компонент: Список фото (со слайдером).

    • Включаемая область — это файл с именем sect_inc.php, расположенный в соответствующей директории сайта. В нашем случае — в корне сайта. Откройте этот файл для редактирования.
    • Перенесите в этот файл код вызова компонента [comp include_photogallery_detail_list_ex]Список фото (со слайдером)[/comp] (photogallery.detail.list.ex)
    • Настройте параметры:
      • THUMBNAIL_SIZE — размер фотографии-анонса (значение нужно взять из исходного дизайна);
      • IBLOCK_TYPE — тип информационного блока, используемого в качестве фотогалереи
      • IBLOCK_ID — инфоблок, используемый в качестве фотогалереи
      • PAGE_ELEMENTS — число выводимых фото на странице, например — 5.
    • Сохраните внесенные изменения.

    Результат действий:

    Кастомизация шаблона

    • Скопируйте и откройте для редактирования шаблон компонента photogallery.detail.list.ex.
    • Для начала сделаем фоновую таблицу для всех фотографий. И в качестве основы возьмём таблицу, которую использовали при кастомизации компонента blog.new_posts. Перед этим нужно определить какая часть шаблона отвечает за вывод списка превью фотографий. Этот код отмечен комментарием /* Used to show 'More photos' in js*/ и выделен в блок <div> с классом photo-items-list photo-photo-list. Вот этот блок и нужно включить в таблицу.

      Поместите блок с указанным классом в ячейку таблицы:

      <table width="99%" border="0" cellspacing="3" cellpadding="5">
           <tr> 
               <td class="br" bgcolor="#DEDEE2" bordercolor="#bfbfbf">
               </td>
           </tr>
      </table>
    • Если мы остановимся на этом, то ссылка на открытие других фотографий у нас окажется вне таблицы, что не верно с точки зрения исходного дизайна. Надо включить вывод этой надписи в таблицу тоже. Найти часть кода, отвечающую за эту надпись, просто:
      • Откройте файл bitrixtemplatestestcomponentsbitrixphotogallery.detail.list.extest_1langru.
      • Через поиск найдите фразу Еще фотографии. Это значение установлено у параметра P_SLIDER_MORE_PHOTOS.
      • Через поиск найдите в шаблоне код с этим параметром. Этот код в блоке с классом photo-show-more расположен внутри PHP-условия. Значит нужно перенести закрывающий код таблицы после тега, закрывающего это PHP-условие.
    • Теперь нам нужно сделать темные рамочки у каждой выводимой превью. Перед этим нужно определить какая часть шаблона выводит непосредственно превью. Эту часть кода легко найти через поиск по img src. Вызов картинки обрамлён в тег a href с классом photo-item-inner. Заключите этот тег в ячейку таблицы, взятой из исходного кода дизайна:
      <table border="0" cellspacing="0" cellpadding="10">
         <tr> 
             <td bgcolor="#333333" align="center" valign="top">
             </td>
         </tr>
      </table>
    • Сохраните внесенные изменения.

    Осталось только привести к нужному виду фразу Ещё фотографии. Подгоним стили вывода текста и добавим стрелки, как на исходном дизайне.

    • Как писали выше фраза Ещё фотографии размещена в блоке с классом photo-show-more. Значит надо задать ему параметры, чтобы текст отображался в соответствии с дизайном. Воспользуйтесь вашими знаниями CSS.
    • Для размещения картинок стрелок посмотрите в исходном коде коды картинок и разместите их вокруг вызова сообщения <?= GetMessage("P_SLIDER_MORE_PHOTOS")?>
    • Сохраните внесенные изменения.

    Результат:

    Примечание: Можно поиграться настройками компонента, параметрами таблиц, чтобы получить более «красивое» положение и оформление.

    Work Area и разные шаблоны

    Bitrix Framework позволяет создавать и использовать несколько шаблонов для разных условий просмотра. Условия могут быть самыми разными: для разных групп пользователей, для разных разделов, даже для отдельных страниц. Это очень удобно в самых разных случаях. Например, при смене дизайна.

    Все шаблоны создаются за счет того или иного размещения в пределах шаблона сайта Рабочей области #WORK_AREA#, включения или исключения в/из нее элементов входящих на данный момент в Header или Footer.

    Создадим три шаблона:

    • С зоной #WORK_AREA# занимающей всю среднюю часть сайта, без левой и правой колонки (для блогов и форумов).
    • С левой колонкой в Header’е и без правой колонки (для статей).
    • С левой колонкой в Header’е и правой колонкой в Footer’е (для страниц, где нужно организовать предварительный показ картинок).

    Собственно третий у нас уже есть, он создался после удаления текста статьи из Footer’а. Логичнее всего будет далее последовательным удалением элементов создать оставшиеся два шаблона. Детально приводить удаляемый код в данном случае мы не будем, здесь вам надо положиться на собственные знания html. Для грамотного выполнения этой операции вам надо очень внимательно следить за тем, что вы модифицируете в шаблоне и почаще пользоваться кнопкой Предпросмотр перед сохранением результатов.

    Шаблоны можно создать двумя способами. Первый: простым копированием шаблона. Второй – созданием нового шаблона и добавлением в него кода из старого шаблона. Преимущество первого способа – в простоте выполнения операции, преимущество второго в том, что можно задать произвольное имя шаблона (в первом случае копия будет называться test_copy). Второй вариант несколько более сложный, так как требует дополнительных действий по настройке:

    • Откройте для редактирования шаблон тестового сайта.
    • Скопируйте в буфер обмена весь код шаблона тестового сайта.
    • Нажмите кнопку Добавить шаблон на Контекстной панели. Откроется форма создания нового шаблона.
    • Вставьте код шаблона в поле Внешний вид шаблона сайта.
    • В поле ID введите название test2.
    • В поле Название введите test_article.
    • В поле Описание введите: Шаблон для статей.
    • Скопируйте в шаблон стили сайта и стили шаблона.
    • Скопируйте в папку шаблона папку components из папки bitrixtemplatestest

    Такие названия и пояснения мы сделали для того, чтобы не путаться в шаблонах. Если вы будете создавать довольно много шаблонов, то логически осмысленные названия и описания вам не помешают.

    • Удалите код, включающий в себя правую колонку:

      Метки:

      <!-- #Begin_right_Column -->
      <!-- #End_right_Column -->
    • Сохраните внесенные изменения.

    Результат:

    Шаблон создан. Следующий шаблон мы создадим по первому варианту, копированием.

    • Выполните команду Настройки > Настройки продукта > Сайты > Шаблоны сайтов. Откроется список шаблонов.
    • В колонке команд в строке нашего второго шаблона выберите команду Копировать. Система выполнит копирование, в списке шаблонов появится шаблон под именем test_copy.
    • Откройте для редактирования этот шаблон
    • В поле Название смените название на test_blogs.
    • В поле Описание смените текст на Шаблон для раздела Блоги.
    • Удалите из него левую колонку. Метки:
      <!-- #Begin_left_Column -->
      <!-- #End_left_Column -->
    • Сохраните внесенные изменения.

    Результат:

    Число шаблонов, которые вы можете использовать на сайте – не ограничено, хоть для каждой отдельной страницы создавайте свой шаблон. Есть один нюанс. При создании шаблонов всегда учитывайте функционал удаляемых частей, и, при необходимости, изменяйте размещение компонентов. Например, при создании последнего шаблона вместе с левой колонкой мы удалили и компонент Таблица статистики. Если она вам нужна на страницах, использующих этот шаблон, Таблицу придется добавлять в #WORK_AREA#.

    Простейший пример внедрения дизайна с блочной вёрсткой (div)

    В данной главе представлен пример внедрения в Bitrix Framework шаблона дизайна с [dw]блочной вёрсткой.[/dw][di]
    Блочная верстка — это подход, при котором сайт строят на основе блоков,

    в качестве которых выступают теги div.

    Особенности тегов div:

    это блочный элемент, и если у него не задана ширина, то

    растягивается на всю ширину окна браузера;

    высота блока равна содержимому, если она не задана. Высота пустого

    блока 0 px, поэтому не отображается на странице;

    не имеет оформления. Нужно задать ему стили в CSS, чтобы увидеть его;

    не несет смысловой нагрузки, это просто способ структурировать сайт;

    может содержать любое количество вложенных элементов (можно

    вкладывать другие блоки div, заголовки, параграфы,

    изображения и многие другие элементы).
    [/di]

    С примером внедрения шаблона с табличной вёрсткой можно ознакомиться в [ds]соответствующей главе.[/ds][di]
    Описанный пример использовался в опубликованных несколькими годами ранее книгах о создании сайта на «1С-Битрикс: Управление сайтом». Принципы работы за это время не изменились. Однако поменялись шаблоны в демодистрибутиве и, следовательно, описание их кастомизации.

    Подробнее…[/di]

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

    Скачать тестовые материалы.

    Примечание: Шаблон — тестовый, вопросы кроссбраузерности при его создании не рассматривались. Все работы выполнялись в Google Chrome.

    Повторение примера рекомендуется на [ds]локальной установке[/ds][di]
    Ознакомление с продуктом на локальном компьютере:

    Для ознакомления с продуктом на локальном компьютере используйте установку «1С-Битрикс: Управление сайтом» или «Битрикс24 в коробке» на Виртуальную машину.

    Подробнее…[/di] с демоверсией сайта для разработчиков (например, на [ds]виртуальной машине[/ds][di]
    Запуск виртуальной машины BitrixVM:

    Загрузите подходящий вам дистрибутив настроенной виртуальной машины BitrixVM.

    Загруженный архив распакуйте в любую папку, например, С:BitrixVM, и запустите виртуальную машину с помощью подходящего ПО.

    Подробнее…[/di]).

    Подразумевается, что пользователь знаком с PHP, HTML и CSS, поэтому детальное описание причин удаления или добавления того или иного кода опускается. Считаем, что Вы сами способны понять эти моменты. Перед началом работы настойчиво рекомендуем ознакомиться с понятием [ds]шаблона дизайна сайта[/ds][di]Шаблон дизайна — это внешний вид сайта, в котором определяется расположение различных элементов на сайте, художественный стиль и способ отображения страниц. Включает в себя программный html-код, графические элементы, таблицы стилей, дополнительные файлы для отображения контента. Может также включать в себя шаблоны компонентов, шаблоны готовых страниц и сниппеты.

    Подробнее…[/di] в рамках Bitrix Framework.

    Для повторения примера можно [ds]создать специальный сайт,[/ds][di]
    Как создать новый сайт:

    Перейдите в Административный раздел на страницу Настройки > Настройки продукта > Сайты. В Рабочей области откроется Список сайтов.

    Выполните команду Добавить сайт на Контекстной панели. В Рабочей области откроется форма Добавление сайта.

    Подробнее…[/di] который будет использоваться в качестве базового для шаблона, либо применять шаблон к одному из сайтов в Вашей тестовой системе.

    В примере не рассматриваются вопросы, связанные с кастомизацией ввода и обработки данных за счёт изменения логики работы системы и компонентов.

    Подготовка к работе

    Итак, нам необходимо внедрить заранее подготовленный макет сайта с [dw]блочной вёрсткой.[/dw][di]
    Блочная верстка — это подход, при котором сайт строят на основе блоков,

    в качестве которых выступают теги div.

    Особенности тегов div:

    это блочный элемент, и если у него не задана ширина, то

    растягивается на всю ширину окна браузера;

    высота блока равна содержимому, если она не задана. Высота пустого

    блока 0 px, поэтому он не виден;

    не имеет оформления. Нужно задать ему стили в CSS, чтобы увидеть его;

    не несет смысловой нагрузки, это просто способ структурировать сайт;

    может содержать любое количество вложенных элементов (можно

    вкладывать другие блоки div, заголовки, параграфы,

    изображения и многие другие элементы).
    [/di]

    Скачать тестовые материалы.

    В архиве макета сайта [dw]содержатся:[/dw][di]

    [/di]

    • папки со стилями шаблона;
    • папка с js-файлами;
    • папка с картинками;
    • html-макет индексной страницы сайта (index.html);
    • файл с главными стилями шаблона (styles.css);
    • csv-файл с демоданными для импорта (demo.csv).

    Разархивируйте скачанный архив с тестовыми материалами (doc.zip).

    Откройте в браузере файл index.html из папки со скачанным архивом шаблона. В окне браузера откроется тестовый дизайн.

    Это дизайн главной страницы [ds]сайта-лендинга.[/ds][di]
    Целевая страница (англ. landing page, также «посадочная страница») — веб-страница, основной задачей которой является сбор контактных данных целевой аудитории. Используется для усиления эффективности рекламы, увеличения аудитории. Целевая страница обычно содержит информацию о товаре или услуге.

    Подробнее…[/di] Она состоит из основной заставки с названием сайта, навигации по странице (верхнее горизонтальное меню, кнопки РЫБЫ.НЕТ и Узнать больше), фотогалереи, блока новостей, контактной информации и копирайта.

    Пропишем требования, которым должен соответствовать сайт с внедрённым шаблоном.

    Техническое задание:

    • Основная заставка и навигация должны располагаться в шапке сайта (header).
    • Копирайт должен располагаться в подвале сайта (footer).
    • Новости сайта нужно выводить из инфоблока новостей с помощью компонентов. Для страницы детального вывода новости создать упрощенный шаблон.
    • Остальную информацию считать статической и разместить на индексной странице (тогда контент-менеджер сможет её изменять в публичном разделе, включив режим Правки).
    • В качестве дополнительного задания: создать нижнее меню.

    Разделение шаблона на header, footer и work_area

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

    1. header (шапка, хедер, пролог) — верхняя часть дизайна; хранится в файле header.php шаблона сайта (Контент > Структура сайта > Файлы и папки > bitrix > templates > [ваш шаблон] );
    2. WORK_AREA — рабочая область, содержащая основной контент; при создании страниц заполняется как раз эта область (например, Контент > Структура сайта > Файлы и папки > index.php );
    3. footer (подвал, футер, эпилог) — нижняя часть дизайна; хранится в файле footer.php шаблона сайта (Контент > Структура сайта > Файлы и папки > bitrix > templates > [ваш шаблон] ).

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

    Рассмотрим часть возможных вариантов разделения сайта на зоны, обозначенные цветами:

    • красный — header (шапка сайта)
    • зелёный — WORK_AREA (рабочая зона, т.е. основной контент)
    • синий — footer (подвал сайта)

    Описание приведённых вариантов:

    1
    • в шапке — основная заставка с названием сайта и навигация по странице (верхнее горизонтальное меню, кнопки РЫБЫ.НЕТ и Узнать больше)
    • в подвале — копирайт
    2
    • в шапке — горизонтальное верхнее меню и кнопка РЫБЫ.НЕТ
    • в подвале — блок с контактами и копирайт
    3
    • в шапке сайта нет видимой части кода (то есть в файле header.php есть только служебный код)
    • в подвале — блок новостей, контакты и копирайт

    В примере будем использовать разбивку 1.

    Шаблон сайта (создание папки шаблона)

    Создание шаблона – первый шаг в интеграции дизайна. Рассмотрим подробнее на примере, как это сделать.

    Добавление кода шаблона / создание папки шаблона

    • Откроем исходный код страницы index.html, выделим весь код и скопируем его в буфер обмена.
    • На сайте перейдём в раздел Настройки > Настройки продукта > Сайты > Шаблоны сайтов. Кликнем по кнопке Добавить шаблон, и в Рабочей области откроется форма Шаблоны сайтов.
    • Выполним команду Добавить шаблон на Контекстной панели. В открывшейся [dw]форме создания[/dw][di]

      [/di] нового шаблона укажем [dw]идентификатор шаблона[/dw][di]
      Можно использовать латинские буквы и цифры.
      [/di] и остальные параметры (описание понятно интуитивно).
    • Вставим скопированный код в поле Внешний вид шаблона сайта.
    • Теперь нужно определить шапку сайта (header) и подвал (footer) — это те области, которые будут неизменны на каждой странице сайта.

      В нашем примере шапка сайта будет содержать основную заставку с названием сайта и навигацию по странице (верхнее горизонтальное меню, кнопки РЫБЫ.НЕТ и Узнать больше) — т.е. заканчиваться на теге </header>. Подвал сайта будет содержать только копирайт, т.е. начинаться с блока <!-- Footer -->.

      Выделим и удалим весь код между шапкой (header) и подвалом сайта (footer).

      Таким образом, мы удалили весь код (контент сайта) между тегом </header> и блоком <!-- Footer -->. Вместо удаленного кода пропишем #WORK_AREA#:

    • Теперь нужно заполнить вкладки [dw]Стили сайта[/dw][di]
      Стили, используемые при оформлении контента страниц (стили сайта), хранятся

      в файле styles.css.


      [/di] и [dw]Стили шаблона.[/dw][di]
      Стили, используемые в шаблоне дизайна, хранятся в файле template_styles.css. Это — основной

      CSS-файл шаблона.


      [/di] Скопируйте туда содержимое файлов styles.css и creative.css (если же эти вкладки оставить пустыми, то придется дополнительно подключать файлы в шапке сайта).

    • Сохраним изменения.

    После выполнения вышеперечисленных действий будет создана папка этого шаблона, включающая отдельные файлы шапки сайта (header.php), подвала сайта (footer.php) и файлы с автоматически подключаемыми скриптами и стилями (Контент > Структура сайта > Файлы и папки > bitrix > templates):

    Нажмите на рисунок, чтобы увеличить

    Загрузка файлов шаблона

    [dw]Загрузите[/dw][di]
    Например, по протоколу FTP. Или же можно создать папки вручную и загрузить в них содержимое.
    [/di] в папку созданного шаблона следующие папки из архива (стили шаблона, картинки и js-файлы):

    • css
    • img
    • js
    • vendor

    Новый шаблон создан (точнее, создана папка этого шаблона со всем необходимым содержимым). Теперь нужно отредактировать шапку и подвал сайта (файлы header.php и footer.php), а также настроить их подключение. Об этом читайте [ds]в следующем уроке.[/ds][di]
    В папке шаблона откроем файл header.php в режиме редактирования как PHP.

    Первой строкой пропишем служебный код (защита от подключения файла напрямую без подключения ядра).

    Подробнее…[/di]

    Примечание: вместо папки bitrix можно использовать папку local (подробнее [ds]в уроке[/ds][di]
    Чтобы сделать жизнь разработчиков проектов удобнее, в рамках работ по новому ядру D7 с версии главного модуля 14.0.1 основные файлы проекта вынесены из папки /bitrix в папку /local. Это позволит изолировать изменяющиеся файлы проекта от папки продукта. По сути, в исключения достаточно будет добавить одну папку /bitrix.

    Подробнее…[/di]).

    Шаблон сайта (настройка и подключение)

    [ds]Ранее[/ds][di]
    Создание шаблона – первый шаг в интеграции дизайна.

    Примечание: Ранее можно было задавать внешний вид шаблона сайта с помощью визуального редактора. С версии 14.0 работа выполняется без визуального редактора.

    Подробнее…[/di] мы создали папку шаблона сайта. Однако для корректного отображения шаблона на сайте нужно еще адаптировать вёрстку, подключив все стили и js-файлы в шапке и подвале сайта, а также прописав новые пути до картинок и объектов.

    Необходимо выполнить три важных шага:

    1. Редактирование шапки сайта (header.php)
    2. Редактирование подвала сайта (footer.php)
    3. Подключение шапки и подвала (index.php), добавление контента

    1. Редактирование шапки сайта (header.php)

    • В папке шаблона откроем файл header.php в режиме редактирования как PHP.
    • Первой строкой пропишем служебный код (защита от подключения файла напрямую без подключения ядра):
      <? if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die(); ?>
      
    • Внутри тегов <head> добавим следующие строки:
      • Код отображения заголовка страницы:
        <title><? $APPLICATION->ShowTitle(); ?></title>
        
      • Код вывода в шаблоне сайта основных полей тега <head> (мета-теги Content-Type, robots, keywords, description; стили CSS; скрипты):
        <? $APPLICATION->ShowHead();  ?>
        
    • После открывающего тега <body> пропишем код для отображения административной панели <? $APPLICATION->ShowPanel(); ?> . В связи с особенностями данного html-макета заключим код отображения панели в теги <div> (панель будет отображаться внизу страницы), иначе административная панель «наедет» на меню:
      <div id="panel">
          <? $APPLICATION->ShowPanel(); ?> 
      </div>
      
    • Во все строках, где есть обращение к файлам, добавим в начале пути константу [dw]<?=SITE_TEMPLATE_PATH?>[/dw][di]
      Возвращает путь до текущего подключённого шаблона сайта без последнего слеша.
      [/di] и слеш /.

      Рассмотрим на примере блока <!-- Bootstrap core JavaScript -->:

        <!-- Bootstrap core JavaScript -->
        <script src="<?=SITE_TEMPLATE_PATH?>/vendor/jquery/jquery.min.js"></script>
        <script src="<?=SITE_TEMPLATE_PATH?>/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
      
    • Сохраним изменения.

    Пример отредактированного файла header.php с комментариями

    Примечание: css-стили в шапке сайта предпочтительнее подключить с помощью метода Asset::addCss() ядра D7.

    Пример файла header.php с комментариями

    2. Редактирование подвала сайта (footer.php)

    Подвал сайта почти [dw]не нуждается в изменениях[/dw][di]Проверьте обязательно наличие строки <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();?> в начале файла.[/di]. Необходимо только в строках, обращающихся к файлам, добавить в начале пути константу [dw]<?=SITE_TEMPLATE_PATH?>[/dw][di]
    Возвращает путь до текущего подключённого шаблона сайта без последнего слеша.
    [/di] со слешем / и сохранить изменения.

    Пример отредактированного файла footer.php

    3. Подключение шапки и подвала (index.php), добавление контента

    • Необходимо удостовериться в подключении на странице вывода файлов header.php и footer.php.

      Для этого в файле главной страницы сайта [dw]index.php[/dw][di] (Контент > Структура сайта > Файлы и папки > index.php)

      [/di] сверим следующие конструкции:

      • первая строка: <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");?>
      • последняя строка: <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>
    • Добавим [dw]статический[/dw][di]
      Т.е. для изменения контента страницы нужно будет вновь отредактировать вручную код страницы index.php.
      [/di] контент.

      Откроем файл с подготовленной вёрсткой (index.html) и скопируем код между шапкой (header) и подвалом сайта (footer) — тот, который мы в пункте 1 заменяли на #WORK_AREA#, то есть контент сайта.

      Настроим пути к картинкам. Пропишем в существующих путях уже знакомую константу <?=SITE_TEMPLATE_PATH?>/.

    Пример отредактированного файла index.php с комментариями

    Удобнее, разумеется, настроить вывод динамической информации и работать с ним. Подробнее об этом в [ds]следующем уроке.[/ds][di]
    В предыдущем уроке мы создали шаблон главной страницы сайта. Однако пока вся информация на сайте — статическая, и для внесения любых изменений придется править главную страницу index.php. Это не очень удобно, если информация на сайте должна постоянно обновляться. И при этом слишком громоздко.

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

    Подробнее…[/di]

    Готово! Теперь осталось только [ds]назначить[/ds][di]
    Откройте в Административном разделе страницу Настройки > Настройки продукта > Сайты > Список сайтов. С помощью меню действий откройте для редактирования нужный сайт.

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

    Подробнее…[/di] созданный шаблон для тестового сайта и посмотреть результат.

    Примечание:
    Всегда можно изменить шапку или подвал сайта, перенеся требуемую часть кода из индексной страницы в файл header.php или footer.php.

    Пример: перенос блока «Связаться с нами» в footer.php

    Список новостей: кастомизация шаблона компонента

    В [ds]предыдущем уроке[/ds][di]
    Создание шаблона – первый шаг в интеграции дизайна.

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

    Подробнее…[/di] мы создали шаблон главной страницы сайта. Однако пока вся информация на сайте — статическая, и для внесения любых изменений придется править главную страницу index.php. Это не очень удобно, если информация на сайте должна постоянно обновляться. И при этом слишком громоздко.

    Поэтому однородную информацию лучше заносить в [ds]инфоблоки,[/ds][di]
    Информационные блоки — модуль, позволяющий каталогизировать и управлять различными типами (блоками) однородной информации. С помощью информационных блоков может быть реализована публикация различных типов динамической информации: каталоги товаров, блоки новостей, справочники и т.д.

    Подробнее…[/di] а потом с помощью [ds]компонентов[/ds][di]
    Компонент — это логически завершённый код, предназначенный для извлечения информации из инфоблоков и других источников и преобразования её в HTML-код для отображения в виде фрагментов web-страниц. Состоит из собственно компонента (контроллер) и шаблона (представление). Компонент, с помощью API одного или нескольких модулей, манипулирует данными. Шаблон компонента выводит данные на страницу.

    Подробнее…[/di] выводить её на сайте. А чтобы информация отображалась именно так, как задумал дизайнер, необходимо [ds]кастомизировать[/ds][di]
    Кастомизация шаблона компонента, как правило, преследует две цели:

    1. Приведение формы вывода данных компонента в соответствие с дизайном сайта;

    2. Организация вывода данных компонента в виде, недоступном в стандартном варианте.

    Подробнее…[/di] шаблон компонента.

    Новости будем выводить с использованием двух простых компонентов:

    В этом уроке изменим дизайн компонента Список новостей. Рассмотрим пошагово:

    1. Копирование шаблона компонента

    Скопируйте шаблон компонента Список новостей. Это можно сделать двумя способами:

    В данном примере скопирован шаблон default.

    2. Кастомизация шаблона компонента

    • В исходном файле вёрстки index.html скопируйте блок, выводящий новости (в нашем случае блок Call to Action Section).
    • В папке скопированного шаблона компонента news.list откройте в режиме редактирования файл template.php.
    • После первой строки <? if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die(); ?> (защита от подключения файла напрямую без подключения ядра) вставьте скопированный ранее код вёрстки.
    • Теперь нужно разобраться в общей структуре кода вёрстки (какая часть кода за что отвечает):

      1 — стиль заголовка блока и сам заголовок;

      2 — стиль блока;

      3 — три элемента блока (т.е. три новости) со своими стилями.

      Код 1 и 2 оставим без изменений, а работать будем с 3:

    Примечание: Весь нижеприведённый код есть в штатном шаблоне компонента, который мы копировали в пункте 1.

    • Код трёх новостей заменим на конструкцию foreach, которая будет перебирать массив элементов и выводить найденные новости:
      <? foreach ($arResult["ITEMS"] as $arItem): ?>
      	<?
      	$this->AddEditAction($arItem['ID'], $arItem['EDIT_LINK'], CIBlock::GetArrayByID($arItem["IBLOCK_ID"], "ELEMENT_EDIT"));
      	$this->AddDeleteAction($arItem['ID'], $arItem['DELETE_LINK'], CIBlock::GetArrayByID($arItem["IBLOCK_ID"], "ELEMENT_DELETE"), array("CONFIRM" => GetMessage('CT_BNL_ELEMENT_DELETE_CONFIRM')));
      	?>
      
      	<!--  Код одной новости из исходного файла index.html  -->
      		<div class="col-lg-4 text-center">
      			<div class="card bg-secondary border border-dark">
      				<img class="card-img-top" src="img/2.jpg" alt="News image cap">
      				<div class="card-body">
      					<h5 class="card-title">News title</h5>
      					<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
      					<a href="#" class="btn btn-primary">Go somewhere</a>
      				</div>
      			</div>
      		</div>
      
      <? endforeach; ?>
      
    • Пропишем вызов пути картинки анонса src="<?= $arItem["PREVIEW_PICTURE"]["SRC"] ?>". Вместо статического названия элемента укажем <? echo $arItem["NAME"] ?> (будут подтягиваться названия элементов инфоблока):
      <img class="card-img-top" src="<?= $arItem["PREVIEW_PICTURE"]["SRC"] ?>"
      	alt="<? echo $arItem["NAME"] ?>">
      <div class="card-body ">
              <h5 class="card-title"><? echo $arItem["NAME"] ?></h5>
      
    • Выводить текст анонса новостей будем конструкцией:
      <? if ($arParams["DISPLAY_PREVIEW_TEXT"] != "N" && $arItem["PREVIEW_TEXT"]): ?>
      	<p class="card-text"><? echo $arItem["PREVIEW_TEXT"]; ?></p>
      <? endif; ?>
      
    • В коде, отвечающем за вывод кнопки перехода на страницу детального просмотра элемента, пропишем <? echo $arItem["DETAIL_PAGE_URL"] ?>:
      <a href="<? echo $arItem["DETAIL_PAGE_URL"] ?>" class="btn btn-primary">Подробнее</a>
      

    Итоговый код кастомизированного шаблона компонента news.list

    Детальный просмотр новости: шаблон страницы и компонента

    В [ds]предыдущем уроке[/ds][di]
    Однородную информацию лучше заносить в инфоблоки, а потом с помощью компонентов выводить её на сайте. А чтобы информация отображалась именно так, как задумал дизайнер, необходимо кастомизировать шаблон компонента.

    Новости будем выводить с использованием двух простых компонентов:

    Список новостей (news.list) — его разместим на главной странице сайта для вывода анонсов новостей;

    Новость детально (news.detail) — для вывода детальной новости нужно будет создать новую страницу со своим шаблоном.

    В этом уроке изменим дизайн компонента Список новостей. Рассмотрим пошагово:

    Подробнее…[/di] мы настроили вывод списка новостей, а в этом уроке займемся страницей детального просмотра новости:

    • создадим шаблон страницы;
    • разместим и кастомизируем компонент Новость детально [comp include_news_detail](news.detail)[/comp].

    1. Создание шаблона страниц для вывода детальной информации

    Шаблон главной страницы уже подготовлен. Теперь нужно подготовить шаблон страницы вывода детальной информации.

    Можно, конечно, создать шаблон по ранее описанному пути, но в этом случае можно сделать гораздо легче: [dw]копировать папку[/dw][di]

    [/di] шаблона, [dw]переименовать[/dw][di]

    [/di] и откорректировать файлы.

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

    В нашем примере сделаем шаблон для страниц с подробной информацией минималистичным, оставив только [dw]копирайт[/dw][di]

    [/di] в подвале сайта. Поэтому файл footer.php (в котором и содержится копирайт) оставим без изменений, отредактировав только файл header.php:

    <? if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die(); ?> <!-- Служебный код, необходим для защиты подключения этого файла без подключения ядра -->
    
    <!DOCTYPE html>
    <html lang="ru">
    
      <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
      <meta name="description" content="">
      <meta name="author" content="">
    
    	<title><? $APPLICATION->ShowTitle(); ?></title> <!-- Отображение заголовка страницы -->
    	<? $APPLICATION->ShowHead();  ?> <!--  Вывод в шаблоне сайта основных полей тега head (мета-теги Content-Type, robots, keywords, description; стили CSS; скрипты) -->
    
      <!-- Font Awesome Icons -->
      <link href="<?=SITE_TEMPLATE_PATH?>/vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
    
      <!-- Google Fonts -->
      <link href="https://fonts.googleapis.com/css?family=Merriweather+Sans:400,700&selection.subset=cyrillic" rel="stylesheet">
      <link href='https://fonts.googleapis.com/css?family=Merriweather:300,300i,400,400i,700,700i&display=swap&subset=cyrillic' rel='stylesheet' type='text/css'>
    
    </head>
    
    <body id="page-top">
    <div id="panel">
        <? $APPLICATION->ShowPanel(); ?> <!-- Отображение административной панели внизу страницы -->
    </div>
    

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

    2. Применение шаблона к странице

    Создадим страницу вывода детальной информации. По умолчанию к ней будет применен шаблон сайта.

    Изменим шаблон этой страницы (Настройки > Настройки продукта > Сайты > Список сайтов):

    [ds]Значение Сортировки[/ds][di]Обязательно проработайте порядок сортировки для шаблонов (колонка Сорт.). Указанные цифры определят порядок применения шаблонов.

    Подробнее …[/di] для этого шаблона должно быть меньше, чем значение для основного шаблона.

    3. Размещение и настройка компонента

    В результате при клике по кнопке [dw]Подробнее[/dw][di]

    [/di] в разделе Новости главной страницы сайта перейдем на страницу подробного описания новости, однако [dw]отображение новости[/dw][di]

    [/di] не совсем соответствует дизайну нашего сайта. Исправим это на следующем этапе.

    4. Кастомизация компонента

    • Скопируем шаблон компонента Новость детально по аналогии с [ds]уроком.[/ds][di]
      Это можно сделать двумя способами:

      1. В рамках файловой системы копированием папки /bitrix/components/bitrix/_нужный_компонент_/templates/ в папку /local/templates/шаблон_сайта/components/namespace/название_компонента/_название_шаблона.

      2. Средствами интерфейса системы, разместив на странице компонент и скопировав его с помощью команды Копировать шаблон компонента (при включённом режиме Правка).

      Подробнее…[/di]

    • В папке скопированного шаблона компонента news.detail откроем в режиме редактирования файл template.php.
    • Оформим детальный показ новости в том же стиле, что и показ анонса новостей. Для этого в файле исходной вёрстки страницы index.html скопируем [dw]часть кода,[/dw][di]

      [/di] отвечающую за стиль блока Новости.
    • После первой строки <? if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die(); ?> (защита от подключения файла напрямую без подключения ядра) вставим скопированный ранее код вёрстки. Не забудьте поставить закрывающий тег </section>.
    • В коде шаблона компонента, который скопировали, оставим только те части, которые отвечают за вывод картинки и текста.
    • Итоговый код кастомизированного шаблона компонента news.detail

    Теперь осталось в настройках компонента news.detail установить подготовленный нами [dw]шаблон[/dw][di]

    [/di] и проверить [dw]результат.[/dw][di]

    [/di]

    Меню: кастомизация шаблона компонента

    До этого момента навигация на нашем тестовом сайте-лендинге осуществлялась с помощью [ds]html-якорей,[/ds][di]
    Якорем называется закладка с уникальным именем на определенном месте веб-страницы, предназначенная для создания перехода к ней по ссылке. Якоря удобно применять в документах большого объема, чтобы можно было быстро переходить к нужному разделу.

    Для создания якоря следует вначале сделать закладку в соответствующем месте и дать ей имя при помощи атрибута name тега <a>. В качестве значения href для перехода к этому якорю используется имя закладки с символом решетки (#) впереди.

    Подробнее…[/di] что довольно удобно для переходов на «длинной» странице.

    Однако для более масштабных сайтов, имеющих несколько разделов, появляется необходимость создать [ds]меню[/ds][di]
    В общем случае задача формирования меню включает:

    выделение HTML элементов для построения меню;

    создание шаблона меню (создание шаблона компонента Меню);

    включение функции показа меню (вызов компонента Меню) в общем шаблоне («прологе» и «эпилоге»);

    заполнение меню в соответствии со структурой сайта.

    Подробнее…[/di] в качестве средства навигации по сайту.

    В этом уроке разберём создание [ds]статического [/ds][di]
    Как вы уже знаете, в «1С-Битрикс: Управление сайтом» информация делится на два вида — статическую и динамическую . Статическая вводится непосредственно на странице сайта, меняется редко и вручную. Динамическая размещается с помощью программного кода, меняется автоматически в зависимости от изменений в источнике данных — инфоблоке. Кроме того, эти два типа информации отличаются способами хранения, вывода и обработки.

    Подробнее…[/di] нижнего меню, выводящегося компонентом [comp include_133381]Меню[/comp] (понятия «нижнее», «верхнее» — условны, и влияют только на названия файлов и отображение при создании нового пункта; т.е. рассмотренное в данном уроке меню можно расположить в любой части сайта: и в шапке, и в подвале).

    Итак, для создания меню выполним следующие шаги:

    1. Кастомизация шаблона компонента Меню

    • Скопируйте шаблон компонента Меню. Это можно сделать двумя способами:

      • В рамках файловой системы копированием папки Контент > Структура сайта > Файлы и папки > bitrix > templates > [ваш шаблон] > components > bitrix > [название компонента] > [название шаблона компонента].
      • Средствами интерфейса системы, разместив на странице компонент и скопировав его с помощью команды [dw]Копировать шаблон компонента[/dw][di]

        [/di] (при включённом режиме Правка).
    • Задайте скопированному шаблону новое название (переименуйте папку шаблона). Например, bottom_menu.
    • В папке скопированного шаблона компонента Меню откройте в режиме редактирования файл template.php. Теперь нужно отредактировать этот шаблон в соответствии с исходным html-макетом сайта (т.е. согласно исходному файлу index.html):
      <?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
      
      <?if (!empty($arResult)):?>
      <nav class="navbar navbar-expand-lg navbar-light">
      	<ul class="navbar-nav mx-auto"> 
      
      <?
      foreach($arResult as $arItem):
      	if($arParams["MAX_LEVEL"] == 1 && $arItem["DEPTH_LEVEL"] > 1) 
      		continue;
      ?>
      	<?if($arItem["SELECTED"]):?>
      		<li class="nav-item"><a href="<?=$arItem["LINK"]?>" class="selected nav-link"><?=$arItem["TEXT"]?></a></li>
      	<?else:?>
      		<li class="nav-item"><a href="<?=$arItem["LINK"]?>" class="nav-link"><?=$arItem["TEXT"]?></a></li>
      	<?endif?>
      
      <?endforeach?>
      
      </ul>
      </nav>
      <?endif?>
      

      Добавлены служебные классы навигационной панели navbar, а также классы оформления:

      • navbar-expand-lg — горизонтальное расположение элементов на больших экранах (при уменьшении экрана элементы автоматически станут отображаться вертикальным списком);
      • navbar-light — светлая тема оформления;
      • mx-auto — выравнивание блока по центру по оси ОХ.

      Все используемые классы и стили можно посмотреть в документации используемой библиотеки шаблонов (в нашем примере — [ds]Bootstrap[/ds][di]
      Навигационная панель.

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

      Подробнее…[/di]).

    • Кроме того, у нас нет необходимости в специализированных стилях и картинках исходного шаблона компонента Меню. Поэтому можно удалить из папки нового шаблона файлы style.css, style.min.css и папку images.

    2. Размещение компонента Меню в подвале сайта.

    • Скопируем код вызова компонента. Это можно сделать следующими способами:
      • скопировать вызов из документации;
      • разместить на странице компонент Меню, и в [dw]совмещенном режиме просмотра[/dw][di]

        [/di] скопировать код вызова.
    • Добавим скопированный код вызова в файл footer.php (Контент > Структура сайта > Файлы и папки > bitrix > templates > [шаблон вашего сайта] ):

      В кавычках указывается название используемого шаблона компонента. В нашем случае bottom_menu.

    • В результате этого этапа меню будет выводиться в подвале сайта [dw]кастомизированным шаблоном.[/dw][di]

      [/di]

    Примечание: Подробнее о создании самого меню читайте в соответствующей [ds]главе.[/ds][di]
    В данном разделе приводятся основные принципы и правила создания и управления меню на сайте, в том числе описание файла данных и структуры шаблона меню. Также в разделе описывается возможность использования динамических меню.

    Подробнее…[/di]

    Управление служебными данными шаблона

    Кодировка страниц и формат отображения дат

    Bitrix Framework поддерживает кодировку UTF-8, в которой на одной странице могут сочетаться различные языки – от русского до иероглифов, что позволяет не заботиться о кодировке страниц. Рекомендуем вам разработку сайтов именно в кодировке UTF-8, что позволит вам избежать проблем с настройками однобайтных кодировок.

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

    Управление метаданными

    Основной целью использования метаданных является оптимизация сайта для поисковых систем. Поисковые системы используют метаданные для индексации документов сайта.

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

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

    Управление заголовком документа

    Одним из самых важных элементов сайта на сегодняшний день с позиции SEO-продвижения является заголовок окна браузера (тэг <title>). Практически повсеместно требуется, чтобы заголовок окна браузера отличался от заголовка страницы (тэг <H1>). В «1C-Битрикс» включена поддержка этого требования.

    Установка заголовка производится как с помощью свойств страницы, так и по настройкам компонентов, размещенных на странице. Установка дополнительного заголовка окна веб-браузера осуществляется с помощью зарезервированого в продукте свойства title.

    Управление кодировкой страниц

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

    Использование кодировок

    Одной из важных особенностей Bitrix Framework является поддержка произвольного количества языков. Система позволяет:

    • использовать многоязычный интерфейс в административном разделе;
    • создавать произвольное количество сайтов (в зависимости от лицензии) на различных языках в рамках одной системы.

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

    Для корректного отображения национальных символов используются соответствующие кодировки. При показе страницы браузер распознает используемую кодировку и на ее основе выполняет отображение символов.

    Ниже приводится список таблиц кодов, используемых для отображения символов русского, английского и немецкого языков:

    Язык Кодировка
    Russian (ru) windows-1251, koi8-r, iso-8859-5
    English (en) windows-1252, iso-8859-1, latin1
    German (de) windows-1252, iso-8859-1, latin1

    Полный список кодировок, используемых для различных языков, приводится в документации продукта.

    Примечание: Начиная с версии 7.0, в продукте (для баз данных MySql и Oracle) поддерживается универсальная кодировка UTF-8. С ее помощью содержимое сайта может быть одновременно представлено на разных языках.

    Если UTF-8 не используется, но в системе необходимо сочетание разных языков, то для каждого языка нужно определить кодовую таблицу, с использованием которой будут отображаться текстовые сообщения.

    Внимание! Кодировка страниц и кодировка таблиц базы данных должны совпадать.

    Настройка кодировок

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

    • Настройка кодировки, используемой в публичном разделе, выполняется для каждого сайта (Настройки > Настройки продукта > Сайты > Список сайтов):

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

      Параметры

    • Настройка кодировки для административного раздела сайта выполняется через форму управления параметрами языков, используемых в системе (Настройки > Настройки продукта > Языковые параметры > Языки интерфейса).

      Также при настройке параметров языка можно определить формат времени и даты.

      Добавление языка

      Указанный формат будет использоваться при отображении даты и времени в административном разделе сайта.

    Определение текущей кодировки

    Текущая кодировка, используемая в публичном разделе сайта, определяется с помощью php-константы LANG_CHARSET, подставленной в область заголовка шаблона сайта.

    При применении шаблона к сайту запрашивается значение параметра кодировка, заданное в настройках сайта. Константе LANG_CHARSET присваивается значение, равное значению параметра кодировка.

    Пример кода, с помощью которого выполняется установка кодировки страниц, приводится ниже:

    <head>
    …
    <meta http-equiv="Content-Type" content="text/html; charset=<?echo LANG_CHARSET?>">
    …
    <head> 

    Управление заголовком документа

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

    Управление

    Создание и изменение заголовка страницы, а также окна веб-браузера может быть выполнено как с помощью средств административного, так и публичного раздела.

    Примечание: Установка дополнительного заголовка окна веб-браузера осуществляется с помощью зарезервированного в продукте свойства title.

    Для удобного использования свойства следует задать его название (например, Дополнительный заголовок (заголовок окна веб-браузера)) в настройках модуля Управление структурой.

    Подробнее про установку нескольких заголовков смотрите на странице Примеры работы.

    Примечание: Некоторые компоненты могут самостоятельно устанавливать заголовок, учитывайте это при работе.

    Заголовок страницы

    Заголовок страницы может быть установлен следующими способами.

    • При редактировании документа с помощью встроенного редактора (в режиме «Текст», «PHP» или «HTML»). В этом случае заголовок страницы задается путем подстановки в код документа следующей функции:

      <?
      $APPLICATION->SetTitle("О компании");
      ?>

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

      <?
      $arIBlock = GetIBlock($ID, "news")
      $APPLICATION->SetTitle($arIBlock["NAME"]);
      …
      ?>

    Вывод заголовка документа выполняется с помощью размещения функции ShowTitle() в месте показа заголовка страницы:

    <H1><?$APPLICATION->ShowTitle()?></H1>

    Если при выводе заголовка страницы функция ShowTitle() использует параметр false, это означает, что для установки заголовка страницы не нужно проверять значение свойства title (например, Дополнительный заголовок (заголовок окна веб-браузера)).

    <H1><?$APPLICATION->ShowTitle(false)?></H1>

    Т.е. в качестве заголовка страницы будет использован заголовок, установленный функцией SetTitle().

    Примечание: Подробнее про работу функции ShowTitle(false) смотрите на странице Примеры работы.

    Заголовок окна веб-браузера

    Вывод заголовка окна веб-браузера выполняется с помощью кода ShowTitle(), размещенного в области <head> шаблона дизайна сайта:

    <head><title><?$APPLICATION->ShowTitle()?></title></head>

    Заголовок окна веб-браузера может быть установлен с использованием различных механизмов. По умолчанию заголовок устанавливается в свойстве страницы title (например, Дополнительный заголовок (заголовок окна веб-браузера)). Если значение данного свойства не указано, то заголовок окна браузера будет установлен равным текущему заголовку страницы.

    Примечание: Заголовок окна браузера может также устанавливаться с помощью функции SetPageProperty() или задаваться в публичной части сайта. Подробнее смотрите на странице Примеры работы.

    Внимание! Если на странице располагается несколько одинаковых функций или компонентов, которые могут устанавливать заголовок, то использоваться будет тот заголовок, который задается в самой последней (самой нижней на странице) функции/компоненте.

    Примеры работы

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

    В результате мы должны получить примерно следующий результат:

    Разные заголовки

    Выполним следующее:

    • Зададим заголовок страницы из интерфейса или с помощью функции $APPLICATION->SetTitle("Заголовок страницы"):
      <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
      $APPLICATION->SetTitle("Заголовок страницы"); ?>
      
      <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php"); ?>
      

    • Добавим дополнительный заголовок через интерфейс или с помощью функции $APPLICATION->SetPageProperty('title','Альтернативный заголовок'):
      <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
      $APPLICATION->SetTitle("Заголовок страницы"); ?>
      
      <? $APPLICATION->SetPageProperty('title','Альтернативный заголовок'); ?>
      
      <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php"); ?>
      

      В системе функция $APPLICATION->SetPageProperty() имеет приоритет над функцией $APPLICATION->SetTitle() , поэтому в заголовке браузера и страницы будет отображено именно ее содержимое:

    • Изменим шаблон страницы, если необходимо, так, чтобы вместо функции $APPLICATION->ShowTitle() заголовок страницы (не браузера) задавала $APPLICATION->ShowTitle(false).

      В таком случае будет игнорироваться значение свойства страницы SetPageProperty('title','Альтернативный заголовок') и в качестве заголовка страницы будет использован заголовок, установленный функцией SetTitle().

      Пример работы кода из предыдущего пункта, но с измененной функцией $APPLICATION->ShowTitle() на $APPLICATION->ShowTitle(false) в шаблоне сайта:

      Отдельно продемонстрируем разницу работы функции $APPLICATION->ShowTitle() с параметром false на следующем примере, без изменения кода шаблона:

      <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
      $APPLICATION->SetTitle("Заголовок страницы"); ?>
      <? $APPLICATION->SetPageProperty('title','Альтернативный заголовок'); ?>
      
      <? $APPLICATION->ShowTitle(); ?>
      <br>
      <? $APPLICATION->ShowTitle(false); ?>
      
      <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>
      

    • Дополнительный пример работы нескольких функций по установке заголовка:
      <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
      $APPLICATION->SetTitle("Заголовок страницы"); ?>
      
      <?
      $APPLICATION->SetPageProperty('title','Альтернативный заголовок');
      //......
      $APPLICATION->SetTitle("Заголовок страницы 2"); // заголовок установлен не будет т.к. у этой функции приоритет меньше 
      //.....
      $APPLICATION->SetPageProperty('title','Альтернативный заголовок 2'); // будет установлен именно этот заголовок т.к функция расположена ниже по коду
      ?>
      
      <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php"); ?>
      

    Управление стилями

    Таблицы стилей

    Таблицы стилей представляют собой совокупность правил, применяемых для оформления определенных элементов на страницах сайта. Технология каскадных стилей (CSS) позволяет хранить всю информацию о разметке страницы, используемых на ней шрифтах, цветах, стилях оформления меню и т.д. в определенном месте (одном или нескольких файлах).

    Использование CSS упрощает задачу оформления и верстки страниц сайта. Кроме того, при изменении дизайна сайта или его отдельных элементов нет необходимости в редактировании каждой страницы: достаточно просто изменить соответствующую таблицу стилей.

    Так, например, можно изменить оформление форума (шрифт, цвет элементов и др.) В приведенном примере стили форума задаются отдельно от стилей общего шаблона сайта.

    .forumborder {background-color:#CACBBD;}
    .forumhead
     {
          background-color:#EAEBE2;
     }
    .forumbody
     {
          background-color:#FBFBF9;
     }
    .forumbodytext
     {
          font-family: Arial, Helvetica, sans-serif; 
          font-size:smaller; 
          color:#000000;
     }
    .forumheadtext
     {
          font-family: Arial, Helvetica, sans-serif;
          font-size:smaller; 
          color:#000000;
     }
    .forumtitletext
     {
          font-family: Arial, Helvetica, sans-serif;
          font-size:smaller;
          color:#000000;
     }
    
    .postsep
     {
          background-color: #9C9A9C; 
          height: 1px
     }
    
    .forumquote
     {
          font-family: Arial, Helvetica, sans-serif; 
          font-size:8pt;
          color: #000000; 
          background-color: #FBFBF9; 
          border : 1px solid Black;
          padding-top: 2px; 
          padding-right: 2px; 
          padding-bottom: 2px; 
          padding-left: 2px; 
          text-indent: 2pt;
     }
    .forumcode
     {
          font-family: Arial, Helvetica, sans-serif;
          font-size:8pt; 
          color: #333333; 
          background-color: #FBFBF9; 
          border : 1px solid Black; 
          padding-top: 2px; 
          padding-right: 2px; 
          padding-bottom: 2px; 
          padding-left: 2px; 
          text-indent: 2pt;
     }
    …

    .forumborder {background-color:#96C0FA;}
    .forumhead {background-color:#A9CAF7;}
    .forumbody {background-color:#D7E6FB;}
    .forumbodytext {
    	font-family: Tahoma, Arial, Verdana, sans-serif;
    	font-size: 80%;
    	color:#042A69;
    }
    .forumheadtext {
    	font-family: Tahoma, Arial, Verdana, sans-serif;
    	font-size: 80%;
    	color:#011B46;
    …

    Таблицы стилей настраиваются отдельно для каждого шаблона сайта, используемого в системе, и размещаются в папках соответствующих шаблонов сайта. Таблицы стилей для представления внутреннего содержания страниц сайта хранятся в файлах вида styles.css папки соответствующего шаблона. Дополнительные таблицы стилей, например, стили форума, могут иметь имена вида: forum.css.

    Раздельное хранение стилей

    В продукте добавлен механизм, позволяющий осуществлять раздельное хранение стилей:

    • Стили, используемые в шаблоне дизайна, хранятся отдельно в файле template_styles.css. Это — основной CSS-файл шаблона.
    • стили, используемые при оформлении контента страниц (стили сайта), хранятся в файле styles.css. Стили из этого файла выводятся в выпадающем списке стилей при редактировании страниц в визуальном редакторе.

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

    К примеру: на сайте для всех заголовков определены соответствующие стили отображения, т.е. они используются как для оформления контента страниц, так и для оформления контента блоков, которые находятся вне #WORK_AREA#. Таким образом, мы должны были бы поместить стили этих заголовков как в файл template_styles.css так и в styles.css. Но так как оба этих файла подключаются к шаблону, то в данном случае все стили для оформления заголовков следует поместить только в файл styles.css, так как он тоже подключается на странице.

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

    К разнесению стилей по этим двум файлам следует подходить внимательно. К примеру, если необходимо сделать цвет фона сайта серым, а цвет фона в визуальном редакторе — красным, то в файле template_styles.css для тега body необходимо определить background-color:#ccc;, а в файле styles.css для этого же тега: background-color:#ff0000;.

    Файлы на странице сайта подключаются в таком порядке:

    1. styles.css
    2. template_styles.css

    В результате на сайте фон body станет серого цвета, т.к. стиль в последнем подключенном файле template_styles.css «перебьет» стиль, определенный в styles.css. А в визуальном редакторе фон станет красным, так как содержимое визуального редактора представляет собой iframe в который подключаются стили только из файла styles.css причем вставляются они непосредственно в область head с помощью тега <style>.

    Но если в файле styles.css к определению цвета добавить повышение приоритета !important, то стиль из этого файла «перебьет» стиль, определенный в template_styles.css, фон сайта станет также красного цвета, несмотря на то, что файл стилей шаблона подключается последним.

    Стили сайта

    Формирование таблицы стилей сайта (файл styles.css) выполняется на странице редактирования шаблона дизайна (Настройки > Настройки продукта > Сайты > Шаблоны сайтов) на закладке Стили сайта. Важным элементом при создании таблицы стилей страниц является создание названий стилей. Названия следует создавать только для тех стилей, которые планируется [ds]использовать при редактировании страниц[/ds][di]Достаточно часто в работе нам приходится изменить цвет, размер шрифта, выравнивание текста. Чуть реже мы добавляем специальные символы и разрывы страниц. Для всех этих операций мы будем использовать панель инструментов визуального редактора «1С-Битрикс: Управление сайтом»:

    Подробнее …[/di] в режиме визуального HTML-редактора (секция Описания стилей).

    Стили будут доступны в визуальном редакторе [dw]из выпадающего списка[/dw][di]][/di] под именами, определенными в данной форме. Заданные здесь названия будут храниться в файле идентификатор_шаблона>/.styles.php (файл с именами стилей).

    Создание таблицы стилей шаблона дизайна (файл template_styles.css) выполняется на закладке Стили шаблона формы редактирования шаблона сайта:

    Настройка таблицы стилей для шаблона сайта

    Ссылки по теме:

    • Работа со стилями в битрикс (статья)
    • Сжатие файлов css

    Механизм реализации

    Таблицы стилей подключаются к шаблону сайта в области пролога с помощью функции ShowCSS().

    <?
    $APPLICATION->ShowCSS(); 
    ?>

    Функция ShowCSS() выполняет подключение файла стилей из текущего шаблона сайта, а также всех дополнительных стилей определенных для данной страницы функцией SetAdditionalCSS().

    <?	
    $APPLICATION->SetAdditionalCSS("/bitrix/templates/demo/additional.css");
    ?>

    Дополнительные стили могут использоваться, например, для оформления форума, веб-форм, таблиц, некоторых типов меню и т.д.

    При использовании функции ShowCSS() без параметров подключение стилей будет выполнено в виде ссылки на CSS файл:

    <LINK href="/bitrix/templates/demo/styles.css" type="text/css" rel="STYLESHEET">

    При этом стили, подключаемые с использованием SetAdditionalCSS(), будут включены в код страницы с использованием PHP функции require() (т.е. будут полностью включены в итоговый код страницы).

    В случае использования функции ShowCSS() с параметром false файл стилей для текущего дизайна будет также включен в код страницы с использованием require():

    <?
    $APPLICATION->ShowCSS(false);
    ?>

    Работа со стилями в визуальном HTML-редакторе

    Для визуального редактора присутствует возможность к уже имеющимся стандартным стилям добавлять собственные и помещать их в соответствующую панель редактора. Чтобы стили отображались в меню, файл styles.css в шаблоне сайта должен существовать и быть непустым.

    Чтобы подключать сторонние стили в визуальном редакторе, например Bootstrap и Font Awesome, нужно прописать данную возможность в файле description.php, находящемся в папке шаблона сайта:

    "EDITOR_STYLES" => array (
    		'/bitrix/css/main/bootstrap.css',
    		'/bitrix/css/main/font-awesome.css',
    

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

    Для примера создадим простой стиль, добавляющий тексту рамку и меняющий его цвет на красный. Для начала добавим в файл styles.css описание стиля.

    .example {
    	border: 2px solid red; 
    	color: red;
    	padding: 20px; 
    }
    

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

    Чтобы созданный стиль отображался в меню, нужно добавить соответствующую строку в файл .styles.php.

    <?
    return array (
    "example" => array(
            "tag" => 'p', /*в какой тег будет помещен текст в данном стиле*/
            "title" => "Это тестовый стиль", /*название стиля*/
            "html" => '<span style="border: 2px solid red;color: #ff0000; padding: 4px;">Пример</span>')  /*оформление текста с помощью html*/
    );
    ?>
    

    Примечание: Поля "tag" и "title" являются обязательными для заполнения. Также поле "title" по умолчанию используется как название пункта в списке, соответствующему стилю, и всплывающая подсказка при наведении курсора на него. Однако при использовании поля "html" можно также указать название, которое и будет отображаться в выпадающем списке.

    Также можно разбивать стили по категориям с помощью поля "section". Пока что собственные стили можно помещать только в уже существующие категории.

    Теперь созданный нами стиль отобразится в меню стилей.

    Теперь созданный стиль можно применять из панели стилей.

    Пример таблицы стилей для шаблона сайта

    Стили для левого меню:

    .leftmenu, .leftmenuact {font-family: Arial, Helvetica, sans-serif;
     font-size:12px; font-weight: bold; text-decoration: none;}
    .leftmenu {color: #3F3F3F;}
    .leftmenuact  {color:#FF5235;}

    Стили для верхнего меню:

    .topmenu  {font-family: Arial, Helvetica, sans-serif; font-size:12px;
     font-weight: bold; color: #FFFFFF; text-decoration: none;}
    .topmenuact  {font-family: Arial, Helvetica, sans-serif; font-size:12px; 
     font-weight: bold; color: #FAC535; text-decoration: none;}

    Стили для таблиц:

    .tableborder  {background-color:#9C9A9C;}
    .tablehead  {background-color:#D8D9DA;}
    .tablebody  {background-color:#F8F8F8;}
    .tablebodytext, .tableheadtext {font-family: Arial, Helvetica, sans-serif;
     font-size:12px;}
    .tablebodytext {color:#000000;}
    .tableheadtext {color:#000066;}

    Управление значениями метаданных

    Как правило, основной целью использования метаданных является оптимизация сайта для поисковых систем. Поисковые системы используют метаданные для индексации документов сайта.

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

    Управление значениями метаданных через визуальный интерфейс

    Чтобы иметь возможность управлять значениями метаданных, предварительно необходимо создать соответствующие свойства в настройках модуля Управление структурой (Настройки > Настройки продукта > Настройки модулей >Управление структурой):

    Важно! Названия типов свойств, используемых для управления метаданными страниц, должны совпадать с названиями мета-тегов в языке HTML. Например, типы свойств ключевые слова и описание должны совпадать именами (name) соответствующих мета-тегов: keywords и description.

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

    Подробную информацию по настройке свойств страниц и разделов, а также по управлению значениями метаданных вы можете узнать в курсах Контент-менеджер и Администратор. Базовый.

    Обратите внимание! Значения свойств, заданные для папки, по умолчанию будут использоваться для всех страниц и подразделов соответствующего раздела сайта (если для них не заданы собственные значения этих свойств).

    Управление метаданными в коде

    Для вывода значений метаданных в коде страницы нужно воспользоваться функцией ShowMeta(), размещаемой в прологе шаблона дизайна сайта:

    <head>
    …
    <?$APPLICATION->ShowMeta("keywords")?>
    <?$APPLICATION->ShowMeta("description")?>
    …
    </head>

    Предположим, что для страницы заданы следующие значения свойств ключевые слова и описание:

    C помощью функции SetPageProperty() значения данных свойств будут применены к странице:

    <?
    $APPLICATION->SetPageProperty("keywords","веб,разработка,программирование");
    $APPLICATION->SetPageProperty("description","Система управления сайтом");
    ?>

    Примечание: Настройка свойств раздела может быть выполнена с помощью функции SetDirProperty() (например, в коде файла .section.php):

    <?
    …
    $APPLICATION->SetDirProperty("keywords","дизайн, веб, сайт");
    …
    ?>

    В коде файла за это отвечает массив $arDirProperties:

    $arDirProperties = array(
       "description" => "",
       "keywords" => "дизайн, веб, сайт",
       "title" => "",
    );

    Тогда в результате работы функции ShowMeta() в код страницы будет подставлен следующий HTML-код:

    <meta name="keywords" content="веб,разработка,программирование">
    <meta name="description" content="Система управления сайтом">

    Если для самой страницы значение свойства не задано, то будет взято значение свойства вышестоящего раздела (рекурсивно до корня сайта). Если значение свойства не определено, то значение соответствующего мета-тэга останется незаданным. Свойства страницы могут быть установлены динамически из кода компонентов, расположенных на странице. Например, для страниц показа информации каталога или новостей свойства страницы могут быть установлены в соответствии с определенными свойствами элементов инфоблоков:

     $APPLICATION->SetPageProperty("description",$arIBlockElement["PROPERTIES"][$META_DESCRIPTION]["VALUE"]);

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

    Ссылки по теме:

    • Свойства страниц и мета-теги

    Интеграция компонентов

    Интеграция компонентов

    Цитатник веб-разработчиков.

    Антон Долганин:

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

    • 50-70 часов на создание «можно торговать», дизайн подключен, все работает.
    • 30 часов — тюнинг, комментарии клиента, которые не учел в начале.
    • Хитрые SKU или сложное оформление накидывают еще 50 часов.

    Это именно боевые часы, сел и работаешь, а не офисные часы.

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

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

    Компоненты, которые должны выполнять функции в этих областях:

    В зависимости от того, где должен быть расположен компонент в файле header.php или footer.php, удаляется html код, отображающий данную функциональную область и вставляется код компонента.

    Если шаблон редактируется без визуального редактора системы (а рекомендуется именно такой вариант), то возможны 2 варианта вставки кода визуальных компонентов:

    • Из Пользовательской документации. В ней описан формат вызова всех компонентов.
    • Вставкой кода в визуальном редакторе на какой-нибудь тестовой странице. Полученный код копируется в нужное место шаблона.

      Примечание: Рекомендуется этот способ в силу того, что:

      • документация, хоть и редко, но может отставать от актуальной версии,
      • заказчиком может использоваться устаревшая версия продукта.

    Примеры размещения компонентов

    Компоненты версии 2.0 подключаются в коде страницы с помощью функции IncludeComponent(). В качестве параметров функции используется:

    • название компонента в форме <пространство_имен>:<название_компонента>. Причем название компонента рекомендуется строить иерархически, начиная с общего понятия и заканчивая конкретным назначением компонента. Например, catalog, catalog.element, catalog.section.list и т.п.
    • название шаблона. Шаблон компонента «по умолчанию» можно задавать пустой строкой, также можно явно определять .default
    • массив параметров компонента Array(...)

    Добавление включаемых областей

    Код для вставки компонента Включаемая область (bitrix:main.include), с настройкой на вывод из файла, выглядит так:

    <?$APPLICATION->IncludeComponent(
    	"bitrix:main.include",
    	"",
    	Array(
    	"AREA_FILE_SHOW" => "file",
    	"PATH" => $APPLICATION->GetTemplatePath("include_areas/company_name.php"),
    	"EDIT_TEMPLATE" => ""
    	)
    );?>

    В этом примере include_areas/company_name.php — файл с включаемой областью:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
      

    World Book

    Все книги мира

    В файле включаемой области не требуется подключать пролог и эпилог (файлы header.php и footer.php).
    Необходимо лишь проверить, что файл включаемой области подключен из системы, а не вызван напрямую.
    Это делает строка:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>

    Меню

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

    <?$APPLICATION->IncludeComponent(
    	"bitrix:menu",
    	"horizontal_multilevel",
    	Array(
    		"ROOT_MENU_TYPE" => "top", 
    		"MAX_LEVEL" => "3", 
    		"CHILD_MENU_TYPE" => "left", 
    		"USE_EXT" => "Y", 
    		"MENU_CACHE_TYPE" => "A",
    		"MENU_CACHE_TIME" => "3600",
    		"MENU_CACHE_USE_GROUPS" => "Y",
    		"MENU_CACHE_GET_VARS" => Array()
    	)
    );?>

    Это можно сделать как через визуальный редактор, так и через PHP-код шаблона.

    Список ссылок по теме:

    • Пользовательская документация с описанием компонентов.
    • Подключение компонентов в курсе Контент-менеджер.

    Что такое компонент

    Компоненты

    Цитатник веб-разработчиков.

    Роман Петров: Начиная работать с 1С-Битрикс, ребята говорили «здесь надо свой компонент», «свое свойство» и т.д. Сейчас познали ДАО стандартных компонентов и счастливы.

    Компоненты — это основной инструмент разработчика при работе с проектами, созданными на Bitrix Framework. От умения владеть этим инструментом во многом зависит профессионализм разработчика.

    Компонент — это логически завершённый код, предназначенный для извлечения информации из инфоблоков и других источников и преобразования её в HTML-код для отображения в виде фрагментов web-страниц. Состоит из собственно компонента (контроллер) и шаблона (представление). Компонент, с помощью API одного или нескольких модулей, манипулирует данными. Шаблон компонента выводит данные на страницу.

    Классическая схема работы компонента:

    Carrier Rider Mapper

    Компоненты в полной мере реализуют паттерн проектирования Carrier Rider Mapper.

    • Carrier. Носитель любой информации к которой могут иметь доступ несколько клиентов одновременно.
    • Rider (Reader либо Writer) — объекты, посредством которых Carrier предоставляет доступ к хранимой в нём информации. Клиенты считывают и записывают информацию хранимую в Carrier исключительно только посредством объектов типа Reader и Writer. Таким образом, Reader и Writer — интерфейсы доступа к информации.
    • Mapper (Scanner либо Formatter) — объекты обёртки над Reader либо Writer соответственно. Мапперы отвечают за преобразование форматов данных в удобные для клиентов форматы.

    Поток информации от носителя к клиенту (считывание): Carrier > Reader > Scanner > Client.

    Поток информации от клиента к носителю (запись): Carrier < Writer < Formatter < Client.

    Введение прослойки мапперов между Carrier-Rider и клиентами позволяет соединять один и тот же Carrier-Rider с разными типами клиентов посредством соответствующих (разных) мапперов.

    Использование

    Компоненты используются для:

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

    При размещении компонента на странице пользователь задаёт параметры, с которыми её программный модуль будет вызван на данной конкретной странице. Набор параметров (включая их типы) перечисляется в файле параметров компонента в виде специального хэш-массива.

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

    Компоненты могут быть простыми и комплексными.

    Особенности технологии

    • В компонентах разделена логика и визуальное представление. Логика — это сам компонент, представление — это шаблон вывода компонента. Для одной логики может быть создано несколько представлений, в том числе зависящих от шаблона текущего сайта. Визуальное представление (шаблон вывода) может быть написано на любом шаблонном языке, который можно подключить из PHP. Например, шаблоны могут быть на PHP, Smarty, XSL и т.д.
    • В компонентах нет необходимости изменять логику компонента для изменения особенностей его показа. Поэтому управлять внешним видом информации, выводимой компонентом, стало значительно легче. Шаблон вывода существенно проще, чем компонент в целом.
    • Компоненты централизованно хранятся в одной папке. Это обеспечивает большую целостность и понятность структуры сайта. Папка доступна для обращений, а значит компонент и его шаблоны, могут легко подключать свои дополнительные ресурсы.

    Примечание: Эволюция развития Bitrix Framework через компоненты обычного стандарта привела к действующим сейчас компонентам в стандарте 2.0. Обычный стандарт (компоненты версии 1.0) в данное время не поддерживается. Но в некоторых случаях возможно встретить устаревшие компоненты в проектах, работающих на старых версиях 1С-Битрикс: Управление сайтом. Если встретился такой анахронизм, обратитесь к документации.

    Простые и комплексные компоненты

    Цитатник веб-разработчиков.

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

    Компоненты делятся на простые (одностраничные) и комплексные (многостраничные). Простой компонент реализует вывод на одной физической странице, доступной под конкретным URL. Комплексный же компонент заменяет собой набор простых компонентов. Например, создание новостного раздела можно реализовать несколькими простыми компонентами, размещаемыми каждый на отдельной физической странице, а можно — одним комплексным, размещенным на одной физической странице.

    С точки зрения структуры и способов подключения простые и комплексные компоненты очень похожи. Но с точки зрения функционирования они сильно отличаются.

    Простые компоненты

    Простые (обычные, одностраничные) компоненты создают какую-либо область на одной странице. Их удобно использовать, когда на одной странице требуется разместить данные из различных модулей (блоги и инфоблоки, например) или данные из разных инфоблоков (новости и каталог товаров). Для создания полного раздела новостей или каталога товаров пользоваться ими довольно неудобно: приходится создавать большое число статических страниц и следить за тем, чтобы они были корректно связаны друг с другом.

    Комплексные компоненты

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

    Преимущество комплексных компонентов состоит в автоматической компоновке параметров одностраничных компонентов и отсутствии необходимости их связывать.

    Комплексные компоненты разрешают следующие проблемы:

    • Отпадает необходимость создания большого числа статических страниц для размещения всех требуемых подкомпонентов. Отпадает необходимость отдельно настраивать для каждого из подкомпонентов общие (пересекающиеся) свойства (например, тип инфоблока и инфоблок).
    • Происходит установление сложных взаимосвязей между подкомпонентами. Например, нет необходимости для страницы со списком сообщений темы форума настраивать, как эта страница может указать на страницу списка тем форума, а как на страницу профиля посетителя.
    • В компонент (даже с кастомизированными шаблонами вывода) можно добавить новую страницу. Например, если на форуме появится страница (подкомпонент) по выводу 10 посетителей с самым высоким рейтингом, то эта страница станет доступной и в публичной части.
    • Можно сменить шаблон вывода всего комплексного компонента одним действием, а не настраивать вывод каждого из подкомпонентов.

    Алгоритм работы комплексного компонента таков:

    1. на основании действий посетителя сайта (например, переход по пунктам меню) комплексный компонент определяет, какая страница должна быть показана пользователю, и подключает свой шаблон компонента для этой динамической страницы;
    2. шаблон страницы подключает обычные компоненты, автоматически настраивая необходимым образом их свойства;
    3. обычные компоненты выполняют свою работу: запрашивают данные у ядра системы, форматируют их и выводят посетителю, а также предоставляют пользователю различные элементы управления (ссылки, формы, кнопки и т.п.);
    4. пользователь с помощью каких-либо элементов управления, посылает новый запрос комплексному компоненту.

    Пример

    Рассмотрим упрощённый пример работы комплексного компонента новостей. Пусть у нас есть обычные компоненты: список новостей и детальной новости (последний принимает во входных параметрах код новости, которую нужно показать).

    Раздел новостей можно организовать, например, разместив на странице index.php компонент списка новостей, а на странице news.php — компонент детальной новости. При этом у компонента списка новостей нужно настроить входные параметры так, чтобы он мог формировать ссылки на страницу детальной новости (с кодом новости), а у компонента детальной новости нужно настроить входные параметры так, чтобы он мог формировать ссылку на страницу списка новостей.

    Чтобы задать ссылку на страницу детальной новости, нужно задать путь к этой странице, а так же название параметра, в котором будет передаваться код новости для показа. То же название параметра нужно задать и во входных параметрах компонента детальной новости, чтобы он знал, где брать код новости для показа. Даже в данном максимально упрощённом случае настройки не так просты. А если это набор из десятков компонентов форума?

    Более удобной альтернативой для сборщика сайта будет использование комплексного компонента новостей. Этот компонент, например, можно просто установить на страницу index.php. Согласованием ссылок и параметров будет заниматься сам комплексный компонент. От разработчика сайта никаких дополнительных действий не потребуется.

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

    Таким образом реализуется паттерн MVC:

    • на комплексный компонент новостей (controller) приходит HTTP запрос (действия пользователя);
    • комплексный компонент новостей (controller) проверяет, установлен ли через HTTP запрос код новости и подключает из своего шаблона страницу списка новостей или страницу детальной новости (view);
    • подключенная страница, в свою очередь, подключает соответствующий обычный компонент, устанавливая при этом его входные параметры соответствующим образом;
    • обычный компонент выполняет свою работу: запрашивает данные у ядра (model), форматирует их и выводит посетителю, а также отображает элементы управления (ссылки, формы, кнопки и т.п.);
    • пользователь с помощью элементов управления посылает новый HTTP запрос на комплексный компонент новостей (controller);
    • процедура повторяется по мере надобности.

    Список ссылок по теме:

    • Комплексный компонент на пальцах от Антона Долганина (блог)

    Структура компонента

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

    Структура

    Важно: файлы компонентов нельзя использовать по отдельности. Компонент — это единое целое, он обладает свойством неделимости.

    Структура файлов

    Папка компонента может содержать следующие подпапки и файлы:

    • подпапку /lang, в которой расположены файлы языковых сообщений (переводов) компонента. В ней также могут размещаться папки помощи /help.
    • подпапку /templates, в которой расположены шаблоны вывода (отображения) компонента. Эта подпапка может отсутствовать, если у компонента нет шаблонов вывода.
    • файл component.php, который содержит логику (код) компонента. Задача этого файла — сформировать из полученных параметров ($arParams) массив $arResult, который впоследствии попадет в шаблон компонента. Этот файл должен присутствовать в папке компонента, если только логика компонента не размещена в файле class.php.
    • файл .description.php, который содержит название, описание компонента и его положение в дереве логического размещения (для редактора). Этот файл должен всегда присутствовать в папке компонента. Его отсутствие не скажется на работе компонента, но размещение компонента через визуальный редактор станет невозможным.
    • файл .parameters.php, который содержит описание входных параметров компонента для редактора. Если у компонента есть входные параметры, то этот файл должен присутствовать в папке компонента.
    • файл class.php для поддержки ООП-компонентов. В этом файле так же может размещаться логика (код) компонента.
    • файл script.js, может подключаться из шаблона, а может из [dw]кода компонента[/dw][di]$APPLICATION->AddHeadScript($this->GetPath().’/script.js’);[/di].
    • любые другие папки и файлы с ресурсами, необходимыми компоненту, например, папка /images.

    Общая структура компонента

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?> 
    <? 
     //Проверяем и инициализируем входящие параметры компонента 
     if(вывод компонента находится в валидном кеше) 
     { 
     //Вывод данных из кеша
     } 
     else 
     { 
     //Запрос данных и формирование массива $arResult в соответствии со 
     //структурой,описанной в файле помощи компонента
     $this->IncludeComponentTemplate(); 
     //Кеширование вывода 
     } 
    ?>

    Папки локализаций

    Компоненты и их шаблоны поддерживают возможность вывода пользовательских сообщений на различных языках. Так, например, если компонент выводит содержимое инфоблока, это содержимое может понадобиться предварить строковой константой. Например, «Здесь вы видите содержимое инфоблока». Пользователь же может перейти, например, в англоязычную версию сайта — и в этом случае эта константа может быть для него автоматически выведена на английском языке.

    Для реализации этой функции при выводе строковых констант выводится не сама константа, а вызывается спецфункция, а в качестве аргумента ей передаётся идентификатор этой константы.

    Пример:

    В файле template.php без локализации:

    <?echo “Торговый каталог”?>

    В файле template.php с локализацией:

    <?echo GetMessage(“CATALOG”)?>

    В этом случае в файле /lang/ru/template.php пишем:

    <?$MESS[“CATALOG”] = “Торговый каталог”;?>

    В папке /lang для каждой языковой версии создаётся папка с названием языка (например, /ru, /en и т.п.), в которой и размещаются файлы языковых сообщений. Рекомендуется называть файл языковых сообщений для какого-либо файла компонента так же, как называется этот файл. В этих файлах находятся массивы, ключами для которых являются идентификаторы констант, а значениями — сами константы, переведенными на соответствующий язык.

    Рекомендуется располагать файлы в той же иерархии относительно папки /lang/код_языка/, в которой файл располагается относительно папки компонента. Например, языковой файл с английскими фразами для файла /install/uninstall.php рекомендуется располагать по пути /lang/en/install/uninstall.php. Подпапка /lang может отсутствовать, если в компоненте нет зависящих от языка фраз.

    Папки локализаций создаются отдельно для компонентов и для каждого из шаблонов.

    Как поменять стандартные надписи в интерфейсе.

    Скопировать шаблон компонента для того, чтобы его можно было кастомизировать (если он еще не кастомизирован).
    Затем либо:

    • С помощью модуля Перевод: Настройки > Локализация.
    • Либо же вручную редактируете языковые файлы /bitrix/templates/имя_шаблона_сайта/components/имя­_компонента/templates/имя_шаблона/lang/ru/template­.php.

    Подключение языкового файла (файла перевода) компонента

    Языковые файлы в компоненте и всех его стандартных файлах (component.php, .description.php, .parameters.php) подключаются автоматически. В других файлах компонента языковые файлы можно подключить командой:

    $this->IncludeComponentLang($relativePath = "", $lang = False)

    где:

    $relativePath — путь к файлу относительно папки компонента,

    $lang — язык. Если передается False, то используется текущий язык.

    Пример: $this->IncludeComponentLang("myfile.php");

    Подсказки в компонентах

    Bitrix Framework позволяет создать подсказки к параметрам компонента:

    Файл создаётся в языковой папке /lang. В файле — массив $MESS, в котором ключами являются параметры компонента c добавлением суффикса _TIP, значениями — подсказки. В качестве примера:

    $MESS["IBLOCK_TYPE_TIP"] = "Это подсказка для типа инфоблока";
    $MESS["IBLOCK_ID_TIP"] = "Это подсказка для ID инфоблока";
    $MESS["SORT_BY1_TIP"] = "Это подсказка для первой сортировки";

    Нет необходимости создавать отдельный lang-файл с подсказками. Их достаточно сохранить в lang-файле .parameters.php (как для компонента, так и для шаблона компонента). Формат подсказок прежний.

    Подсказки для параметров компонента IBLOCK_TYPE, IBLOCK_ID, SORT_BY1 и SORT_ORDER1.

    Ряд стандартных параметров (CACHE_TIME, AJAX_MODE и другие) имеет несколько подсказок. Для CACHE_TIME:

    • CACHE_TIME — время кеширования;
    • CACHE_TYPE — тип кеширования.

    Для страничной адресации:

    • DISPLAY_TOP_PAGER — показывать постраничку над списком;
    • DISPLAY_BOTTOM_PAGER — показывать постраничку под списком;
    • PAGER_TITLE — название элементов в постраничке;
    • PAGER_SHOW_ALWAYS — показывать постраничку всегда;
    • PAGER_TEMPLATE — имя шаблона постранички;
    • PAGER_DESC_NUMBERING — обратная адресация;
    • PAGER_DESC_NUMBERING_CACHE_TIME — время кеширования обратной адресации.

    Внимание! Нельзя делать подсказки про запас (создать, но оставить пустыми): перестанут показываться настройки компонента.

    Структура комплексного компонента

    Комплексный компонент служит для организации целого раздела сайта (форум, каталог и т.п.). Для вывода данных он подключает обычные компоненты. По сути, он является контроллером (менеджером) простых компонентов. Комплексный компонент определяет на основании HTTP запроса страницу, которую требуется вывести посетителю, и подключает шаблон этой страницы.

    Пример кода комплексного компонента:

    <?
    if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
        die();
    }
    
    $arDefaultUrlTemplates404 = [
        'list'    => 'index.php',
        'element' => '#ELEMENT_ID#.php',
    ];
    
    $arDefaultVariableAliases404 = [];
    $arDefaultVariableAliases    = [];
    $arComponentVariables        = ['IBLOCK_ID', 'ELEMENT_ID'];
    $SEF_FOLDER                  = '';
    $arUrlTemplates              = [];
    
    if ($arParams['SEF_MODE'] == 'Y') {
        
        $arVariables = [];
    
        $arUrlTemplates = CComponentEngine::MakeComponentUrlTemplates(
            $arDefaultUrlTemplates404,
            $arParams['SEF_URL_TEMPLATES']
        );
        
        $arVariableAliases = CComponentEngine::MakeComponentVariableAliases(
            $arDefaultVariableAliases404,
            $arParams['VARIABLE_ALIASES']
        );
    
        $componentPage = CComponentEngine::ParseComponentPath(
            $arParams['SEF_FOLDER'],
            $arUrlTemplates,
            $arVariables
        );
    
        if (strlen($componentPage) <= 0) {
            $componentPage = 'list';
        }
    
        CComponentEngine::InitComponentVariables(
            $componentPage,
            $arComponentVariables,
            $arVariableAliases,
            $arVariables);
    
        $SEF_FOLDER = $arParams['SEF_FOLDER'];
    } else {
        $arVariables = [];
    
        $arVariableAliases = CComponentEngine::MakeComponentVariableAliases(
            $arDefaultVariableAliases,
            $arParams['VARIABLE_ALIASES']
        );
        
        CComponentEngine::InitComponentVariables(
            false,
            $arComponentVariables,
            $arVariableAliases,
            $arVariables
        );
    
        $componentPage = '';
        
        if (intval($arVariables['ELEMENT_ID']) > 0) {
            $componentPage = 'element';
        } else {
            $componentPage = 'list';
        }
    
    }
    
    $arResult = [
        'FOLDER'        => $SEF_FOLDER,
        'URL_TEMPLATES' => $arUrlTemplates,
        'VARIABLES'     => $arVariables,
        'ALIASES'       => $arVariableAliases,
    ];
    
    $this->IncludeComponentTemplate($componentPage);
    
    ?>

    В начале кода определяются массивы:

    • $arDefaultUrlTemplates404 — для задания путей по умолчанию для работы в ЧПУ режиме. Каждый элемент массива является шаблоном пути и задается в виде:
      "код шаблона пути" => "шаблон пути"

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

      "element" => "#ELEMENT_ID#.php"

      реальный путь будет иметь вид 195.php или 7453.php. Шаблоны путей могут иметь параметры, например:

      "element" => "#IBLOCK_ID#/#ELEMENT_ID#.php?SECTION_ID=#SECTION_ID#"

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

    • $arDefaultVariableAliases404 — для задания псевдонимов по умолчанию переменных в режиме ЧПУ. Как правило, этот массив пуст (используются реальные имена переменных). В случае, если необходимо, чтобы в HTTP запросе (в адресе) переменная называлась по другому, можно задать псевдоним этой переменной, а при работе компонента восстанавливать значение переменной из псевдонима. Если для какого-либо шаблона пути нужно задать псевдоним для одной или более переменных, то в этом массиве должен появиться элемент вида:
      "код шаблона пути" => array(
          "название переменной 1" => "псевдоним переменной 1",
          "название переменной 2" => "псевдоним переменной 2",
          * * *
          )

      Например, если требуется, чтобы ссылка на страницу детальной информации об элементе инфоблока (например, карточки товара) имела вид:

      "/<мнемонический код инфоблока>/.php?SID=<код группы элементов>"

      то шаблон пути можно задать в виде:

      "element" => "#IBLOCK_ID#/#ELEMENT_ID#.php?SID=#SECTION_ID#"

      а в массиве $arDefaultVariableAliases404 задать псевдоним для переменной SECTION_ID в виде:

      "element" => array(
          "SECTION_ID" => "SID"
          )

      В этом случае ссылки (адреса) будут формироваться с параметром SID, а в компонентах будет установлена переменная SECTION_ID.

    • $arDefaultVariableAliases — для задания псевдонимов по умолчанию переменных в режиме не ЧПУ. Как правило, этот массив пуст, то есть используются реальные имена переменных. В случае, если необходимо, чтобы в HTTP запросе (в адресе) переменная называлась по другому, можно задать псевдоним этой переменной, а при работе компонента восстанавливать значение переменной из псевдонима. Если для какой-либо переменной нужно задать псевдоним, то в этом массиве должен появиться элемент вида:
      "название переменной" => "псевдоним переменной"

      Например, если название переменной в компоненте SECTION_ID, но требуется, чтобы в ссылках использовалась переменная SID, то псевдоним для SECTION_ID можно задать в виде

      "SECTION_ID" => "SID"

      В этом случае, ссылки (адреса) будут формироваться с параметром SID, а в компонентах будет установлена переменная SECTION_ID. Все эти массивы или их части могут быть переопределены с помощью входных параметров компонента (при вызове компонента). Например, во входном параметре SEF_URL_TEMPLATES в ЧПУ режиме может быть задан массив:

      "SEF_URL_TEMPLATES" => array(
          "element" => "#IBLOCK_CODE#/#ELEMENT_ID#.php?GID=#SECTION_ID#"
          )

      а во входном параметре VARIABLE_ALIASES может быть задан параметр:

      "VARIABLE_ALIASES" => array(
          "element" => array(
          "SECTION_ID" => "GID",
          ),
      )

      Тогда в адресах (ссылках) пути будут иметь вид типа /phone/3425.php?GID=28, а в компоненте из них будут восстанавливаться переменные IBLOCK_CODE = phone, ELEMENT_ID = 3425 и SECTION_ID = 28.

    • $arComponentVariables — для задания списка переменных, которые компонент может принимать в HTTP запросе и которые могут иметь псевдонимы. Каждый элемент массива является именем переменной.

    Входной параметр с предопределённым именем SEF_MODE может иметь два значения: Y и N. Если $arParams["SEF_MODE"] равен Y, значит компонент работает в режиме ЧПУ, иначе — нет.

    Входной параметр с предопределённым именем SEF_FOLDER имеет смысл в том случае, если компонент работает в режиме ЧПУ. В этом случае он содержит путь, по которому работает компонент. Путь может быть виртуальным (т.е. физически он может не существовать). Например, компонент из примера может лежать в файле /fld/n.php, при этом он работает в режиме ЧПУ и входной параметр SEF_FOLDER равен /company/news/. Тогда компонент будет откликаться на запросы по адресам /company/news/index.php, /company/news/25.php и т.п.

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

    • CComponentEngine::MakeComponentUrlTemplates
      CComponentEngine::MakeComponentUrlTemplates(
          $arDefaultUrlTemplates404, 
          $arParams['SEF_URL_TEMPLATES']
      );

      Метод объединяет массив по умолчанию шаблонов путей и шаблоны путей, которые были переданы во входных параметрах компонента в один массив. При этом, если в $arParams["SEF_URL_TEMPLATES"] определён шаблон какого-либо пути, то он переопределяет шаблон по умолчанию этого пути.

    • CComponentEngine::MakeComponentVariableAliases
      CComponentEngine::MakeComponentVariableAliases(
          $arDefaultVariableAliases404, 
          $arParams['VARIABLE_ALIASES']
      );

      Метод объединяет массив по умолчанию псевдонимов переменных и псевдонимы переменных, которые были переданы во входных параметрах компонента в один массив. При этом, если псевдоним некоторой переменной определён и в массиве по умолчанию, и во входных параметрах, то возвращается псевдоним из входных параметров.

    • CComponentEngine::ParseComponentPath
      CComponentEngine::ParseComponentPath(
          $arParams['SEF_FOLDER'],
          $arUrlTemplates,
          $arVariables
      );

      Метод на основании параметра $arParams["SEF_FOLDER"] и массива шаблонов путей (который вернул метод MakeComponentUrlTemplates) определяет, какому шаблону пути соответствует запрошенный адрес. Если шаблон был найден, возвращается его код, иначе возвращается пустая строка. Кроме того, в переменной $arVariables возвращается массив переменных компонента, который был восстановлен из шаблона пути без параметров. Например, если массив шаблонов путей (который получился из массива $arDefaultUrlTemplates404 после переопределения всех или части шаблонов через входные параметры компонента) имеет вид:

      $arUrlTemplates = array(
          "list" => "index.php",
          "element" => "#IBLOCK_ID#/#ELEMENT_ID#.php?SID=#SECTION_ID#"
      );

      Если входной параметр SEF_FOLDER равен /company/news/, а запрошенный адрес равен /company/news/15/7653.php?SID=28, то метод ParseComponentPath вернет строку «element» (код соответствующего шаблона), а массив $arVariables будет иметь вид:

      $arVariables = array(
          "IBLOCK_ID" => 15,
          "ELEMENT_ID" => 7653
      )
    • CComponentEngine::InitComponentVariables
      CComponentEngine::InitComponentVariables(
          $componentPage,
          $arComponentVariables,
          $arVariableAliases,
          $arVariables
      );

      где:

      • $componentPage — код шаблона, который вернул метод ParseComponentPath и которому соответствует запрошенный адрес;
      • $arComponentVariables — массив переменных, которые компонент может принимать в HTTP запросе и которые могут иметь псевдонимы;
      • $arVariableAliases — массив псевдонимов (который вернул метод MakeComponentVariableAliases).

      Метод восстанавливает переменные из $_REQUEST c учётом их возможных псевдонимов и возвращает их в переменной $arVariables. Например, если для кода, приведенного выше, в массиве $arVariableAliases есть запись вида

      "element" => array(
          "SECTION_ID" => "SID",
      )

      то метод InitComponentVariables в параметре $arVariables вернет массив вида

      $arVariables = array(
          "IBLOCK_ID" => 15,
          "ELEMENT_ID" => 7653,
          "SECTION_ID" => 28
      )

      Здесь методом InitComponentVariables был инициализирован третий элемент массива. Первые два были инициализированы методом ParseComponentPath в примере выше. В случае работы компонента не в режиме ЧПУ, в метод InitComponentVariables первым параметром передается значение False.

    В случае работы компонента в режиме ЧПУ на основании кода шаблона пути и переменных из HTTP запроса (а в случае не-ЧПУ адресов, только на основании переменных из HTTP запроса) компонент определяет, какая страница из шаблона компонента должна подключиться, и передает ее имя в вызов метода:

    $this->IncludeComponentTemplate($componentPage);

    На страницах шаблона комплексного компонента подключаются обычные компоненты и настраиваются их входные параметры на основании входных параметров комплексного компонента, некоторых вычисляемых значений и констант. Например, страница «element» шаблона компонента из примера (файл типа /templates/.default/element.php относительно папки компонента) может иметь вид типа:

    <?
    if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
        die();
    }
    
    $APPLICATION->IncludeComponent(
        'bitrix:news.detail',
        '',
        [
            'IBLOCK_ID'  => $arParams['IBLOCK_ID'],
            'ELEMENT_ID' => $arResult['VARIABLES']['ELEMENT_ID'],
            'SECTION_ID' => $arResult['VARIABLES']['SECTION_ID'],
            'CACHE_TIME' => $arParams['CACHE_TIME'],
        ],
        $component
    );
    ?>

    Последний параметр $component в подключении компонента — объект, представляющий текущий компонент. Он передается в вызов подключения компонента. Таким образом, подключаемый компонент будет знать, что он подключается из комплексного компонента. Соответственно, он сможет пользоваться ресурсами комплексного компонента, вызывать его методы и т.п.

    Размещение в системе и подключение компонента

    Размещение

    Компоненты в Bitrix Framework должны храниться только в определенных местах:

    • в папке /bitrix/components/bitrix (по умолчанию);
    • в папке /bitrix/components/собственное пространство имен.
    • в папке /local/components/ (рекомендуется для собственных компонентов сторонних разработчиков)

    Все компоненты находятся в папке /bitrix/components/. Системные компоненты находятся в папке /bitrix/components/bitrix/. Содержимое этой папки обновляется системой обновлений и не может изменяться пользователями.

    Внимание! Изменение содержимого папки системных компонентов /bitrix/components/bitrix/ может привести к непредсказуемым последствиям.

    Пользовательские компоненты могут находиться в любых других подпапках папки /bitrix/components/ или прямо в папке /bitrix/components/, но рекомендуется папка /local/components/.

    Именование

    Название подпапки директории /bitrix/components/ образует пространство имен (namespace) компонентов. Например, все системные компоненты расположены в пространстве имен bitrix. При создании пользовательских компонентов рекомендуется создать какое-либо пространство имен и размещать пользовательские компоненты в нем.

    Например, существует системный компонент news, который расположен в пространстве имен bitrix. Если необходим пользовательский компонент news, который имеет другую функциональность, не реализуемую с помощью шаблона системного компонента, то рекомендуется создать некоторое пространство имен (подпапку) в папке /bitrix/components/ (например, demo) и расположить пользовательский компонент news в этом пространстве имен. Таким образом будут доступны два компонента news, но лежащие в разных пространствах имен: bitrix:news и demo:news.

    Имена компонентов имеют вид идентификатор1.идентификатор2…. Например, catalog, catalog.list, catalog.section.element и т.п. Рекомендуется строить имена иерархически, начиная с общего понятия и заканчивая конкретным назначением компонента. Например, компонент, показывающий список товаров данной группы, может называться catalog.section.elements. Полное имя компонента — это имя компонента с указанием пространства имен. Полное имя имеет вид пространство_имен:имя_компонента. Например, bitrix:catalog.list. Если компонент лежит вне пространства имен, то пространство имен не указывается. Например, catalog.section.

    Подключение

    В самом общем виде подключение компонента осуществляется следующим образом:

    <?$APPLICATION->IncludeComponent(
       $componentName,         // имя компонента
       $componentTemplate,     // шаблон компонента, пустая строка если шаблон по умолчанию
       $arParams=array(),      // параметры
       $parentComponent=null,  // null или объект родительского компонента
       $arFunctionParams=array()
    );?>

    Внутри компонента (файл component.php) доступны следующие предопределенные переменные:

    $componentName – полное название компонента (например: bitrix:news.list).

    $componentTemplate – шаблон, с которым вызывается компонент.

    $arParams – входные параметры компонента (т.е. параметры с которыми вызывается компонент). Параметры доступны так же по их именам.

    $componentPath – путь к компоненту относительно корня сайта (пример: /bitrix/components/bitrix/news.list).

    $parentComponentName – название родительского компонента (пустое, если нет родителя).

    $parentComponentPath – путь к родительскому компоненту относительно корня сайта (пустой, если нет родителя).

    $parentComponentTemplate – шаблон родительского компонента (пустой, если нет родителя).

    $arResult — результат, чтение/изменение. Затрагивает одноименный член класса компонента.

    $this — естественно ссылка на текущий вызванный компонент (объект класса CBitrixComponent), можно использовать все методы класса.

    Кроме того, в компоненте объявлены глобальными переменные $APPLICATION, $USER, $DB.

    Примечание: Компонент получает все параметры вызова следующим образом:

    1. В ключах, начинающихся с ~, данные содержатся в исходном виде (т.е. без всякой обработки).
      Если это комплексный компонент или в шаблоне компонента вызывается другой и часть параметров передается ему, то необходимо передавать значение ключей с ~.
    2. В ключах без ~ данные приведены к безопасному виду с помощью метода htmlspecialcharsEx. Если ключ содержит массив, то будут обработаны строковые ключи массива (тоже с помощью htmlspecialcharsEx).

    Описание компонента

    В файле .description.php содержится описание компонента. Это описание применяется для работы с компонентом (например, в визуальном редакторе), а также при работе в режиме редактирования сайта. При работе самого компонента (при обращении к странице, на которой расположен компонент) описание не используется и файл .description.php не подключается.

    Файл .description.php должен находиться в папке компонента. Языковой файл подключается автоматически (должен лежать в папке /lang/<язык>/.description.php относительно папки компонента).

    Структура типичного файла .description.php такова:

    <?
    $arComponentDescription = array(
       "NAME" => GetMessage("COMP_NAME"),
       "DESCRIPTION" => GetMessage("COMP_DESCR"),
       "ICON" => "/images/icon.gif",
       "PATH" => array(
          "ID" => "content",
          "CHILD" => array(
             "ID" => "catalog",
             "NAME" => "Каталог товаров"
          )
       ),
       "AREA_BUTTONS" => array(
          array(
             'URL' => "javascript:alert('Это кнопка!!!');",
             'SRC' => '/images/button.jpg',
             'TITLE' => "Это кнопка!"
          ),
       ),
       "CACHE_PATH" => "Y",
       "COMPLEX" => "Y"
    );
    ?>

    Как видно, в файле определяется массив $arComponentDescription, который описывает компонент. Этот массив может иметь следующие ключи:

    • «NAME» — название компонента;
    • «DESCRIPTION» — описание компонента;
    • «ICON» — путь к пиктограмме компонента относительно папки компонента. Значок компонента используется в разных частях системы, например: в визуальном редакторе. (Параметр устарел, его можно не использовать. При создании собственных компонентов можно создавать .descripton.php без ICON, несмотря на то, что во многих старых компонентах ICON присутствует.)
    • «PATH» — расположение компонента в виртуальном дереве компонента в визуальном редакторе. Значением этого элемента должен быть массив, имеющий ключи:
      • «ID» — код ветки дерева. ID узла должен быть уникальным в пределах всего дерева компонент (включая стандартные). Если у узлов будут два одинаковых ID, то оба не будут открываться. Например, для компонента собственной разработки выбран узел ID = «news», а такой ID уже есть для стандартных компонентов.
      • «NAME» — название ветки дерева. Необходимо обязательно указать. NAME берется из первого попавшегося компонента в узле. Если его не оказалось либо нет нужной языковой константы — в качестве NAME используется ID.
      • «CHILD» — дочерняя или подчиненная ветка. В элементе с ключом «CHILD» может быть задана подчиненная ветка дерева с той же структурой, что и родительская ветка.

      Дерево ограничено тремя уровнями. Как правило, строится двухуровневое дерево и компоненты располагаются на втором уровне. Следующие служебные названия первого уровня зарезервированы и не могут быть использованы: «content» (контент), «service» (сервисы), «communication» (общение), «e-store» (магазин), «utility» (служебные).

      Если ключ «PATH» не задан, то компонент не будет присутствовать в визуальном редакторе;

    • «AREA_BUTTONS» — пользовательские кнопки, которые показываются для компонента в режиме редактирования сайта;
    • «CACHE_PATH» — если значение равно «Y«, отображается кнопка очистки кэша компонента в режиме редактирования сайта (предполагается, что кэш лежит по стандартному пути: /<код сайта>/<относительный путь к компоненту>). Если равно не пустой отличной от «Y» строке, отображается кнопка очистки кэша компонента в режиме редактирования сайта (кэш лежит по пути, равному значению с ключом «CACHE_PATH» — для нестандартных путей);
    • «COMPLEX» — элемент должен иметь значение «Y» для комплексного компонента, для простых компонентов не имеет значения.

    Параметры компонента

    В файле .parameters.php содержится описание входных параметров компонента. Данные файла нужны исключительно для создания формы ввода свойств компонента в среде Bitrix Framework (например, в визуальном редакторе). Это описание применяется для работы с компонентом, а также при работе в режиме редактирования сайта. При работе самого компонента (при обращении к странице, на которой расположен компонент) описание не используется и указанный файл не подключается. Для комплексного компонента в этом файле задаются параметры простых компонентов, входящих в состав комплексного. Также здесь будут задаваться и настройки ЧПУ.

    Файл .parameters.php должен находиться в папке компонента. Языковой файл подключается автоматически (должен лежать в папке /lang/<язык>/.parameters.php, относительно папки компонента).

    В файле определяется массив $arComponentParameters, который описывает входные параметры компонента. Если необходимо, производится выборка каких-либо дополнительных данных. Например, для формирования выпадающего списка типов информационных блоков (входной параметр IBLOCK_TYPE_ID) выбираются все активные типы.

    Структура типичного файла .parameters.php (на примере компонентов, работающих с модулем Информационные блоки):

    <?
    CModule::IncludeModule("iblock");
    
    $dbIBlockType = CIBlockType::GetList(
       array("sort" => "asc"),
       array("ACTIVE" => "Y")
    );
    while ($arIBlockType = $dbIBlockType->Fetch())
    {
       if ($arIBlockTypeLang = CIBlockType::GetByIDLang($arIBlockType["ID"], LANGUAGE_ID))
          $arIblockType[$arIBlockType["ID"]] = "[".$arIBlockType["ID"]."] ".$arIBlockTypeLang["NAME"];
    }
    
    $arComponentParameters = array(
       "GROUPS" => array(
          "SETTINGS" => array(
             "NAME" => GetMessage("SETTINGS_PHR")
          ),
          "PARAMS" => array(
             "NAME" => GetMessage("PARAMS_PHR")
          ),
       ),
       "PARAMETERS" => array(
          "IBLOCK_TYPE_ID" => array(
             "PARENT" => "SETTINGS",
             "NAME" => GetMessage("INFOBLOCK_TYPE_PHR"),
             "TYPE" => "LIST",
             "ADDITIONAL_VALUES" => "Y",
             "VALUES" => $arIblockType,
             "REFRESH" => "Y"
          ),
          "BASKET_PAGE_TEMPLATE" => array(
             "PARENT" => "PARAMS",
             "NAME" => GetMessage("BASKET_LINK_PHR"),
             "TYPE" => "STRING",
             "MULTIPLE" => "N",
             "DEFAULT" => "/personal/basket.php",
             "COLS" => 25
          ),
          "SET_TITLE" => array(),
          "CACHE_TIME" => array(),
          "VARIABLE_ALIASES" => array(
             "IBLOCK_ID" => array(
                "NAME" => GetMessage("CATALOG_ID_VARIABLE_PHR"),
             ),
             "SECTION_ID" => array(
                "NAME" => GetMessage("SECTION_ID_VARIABLE_PHR"),
             ),
          ),
          "SEF_MODE" => array(
             "list" => array(
                "NAME" => GetMessage("CATALOG_LIST_PATH_TEMPLATE_PHR"),
                "DEFAULT" => "index.php",
                "VARIABLES" => array()
             ),
             "section1" => array(
                "NAME" => GetMessage("SECTION_LIST_PATH_TEMPLATE_PHR"),
                "DEFAULT" => "#IBLOCK_ID#",
                "VARIABLES" => array("IBLOCK_ID")
             ),
             "section2" => array(
                "NAME" => GetMessage("SUB_SECTION_LIST_PATH_TEMPLATE_PHR"),
                "DEFAULT" => "#IBLOCK_ID#/#SECTION_ID#",
                "VARIABLES" => array("IBLOCK_ID", "SECTION_ID")
             ),
          ),
       )
    );
    ?>

    Опишем ключи массива $arComponentParameters подробнее.

    GROUPS

    Значением этого ключа является массив групп параметров компонента. Параметры в визуальных средствах среды Bitrix Framework (например, в визуальном редакторе) группируются. Группы в среде Bitrix Framework располагаются в том порядке, в котором заданы в файле. Массив групп параметров компонента состоит из элементов следующего вида:

    "код группы" => array(
        "NAME" => "название группы на текущем языке",
        "SORT" => "сортировка",
        )

    Перечень стандартных групп:

    код Сортировка Название Описание
    BASE 100 Основные параметры Базовые параметры для работы компонента
    DATA_SOURCE 200 Источник данных Параметры, указывающие, откуда выбирать данные для компонента (к примеру, для компонент модуля Инфоблоки это тип и ID инфоблока).
    VISUAL 300 Настройки внешнего вида Сюда предполагается размещать параметры, отвечающие за внешний вид.
    USER_CONSENT 350 Согласие пользователя Настройка параметров на получение согласия пользователя, согласно законодательству РФ,.
    URL_TEMPLATES 400 Шаблоны ссылок Служебная
    SEF_MODE 500 Управление адресами страниц Группа для всех параметров, связанных с использованием ЧПУ.
    AJAX_SETTINGS 550 Управление режимом AJAX Все, что касается ajax.
    CACHE_SETTINGS 600 Настройки кеширования Появляется при указании параметра CACHE_TIME.
    ADDITIONAL_SETTINGS 700 Дополнительные настройки Эта группа появляется, например, при указании параметра SET_TITLE.

    PARAMETERS

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

    "код параметра" => array(
        "PARENT" => "код группы",  // если нет - ставится ADDITIONAL_SETTINGS
        "NAME" => "название параметра на текущем языке",
        "TYPE" => "тип элемента управления, в котором будет устанавливаться параметр",
        "REFRESH" => "перегружать настройки или нет после выбора (N/Y)",
        "MULTIPLE" => "одиночное/множественное значение (N/Y)",
        "VALUES" => "массив значений для списка (TYPE = LIST)",
        "ADDITIONAL_VALUES" => "показывать поле для значений, вводимых вручную (Y/N)",
        "SIZE" => "число строк для списка (если нужен не выпадающий список)",
        "DEFAULT" => "значение по умолчанию",
        "COLS" => "ширина поля в символах",
    ),

    Для типа элемента управления TYPE есть значения:

    • LIST — выбор из списка значений. Для типа LIST ключ VALUES содержит массив значений следующего вида:
      VALUES => array(
         "ID или код, сохраняемый в настройках компонента" => "языкозависимое описание",
      ),

      Если в списке значений используются строковые ключи (RUB, ‘TRIPLEX’ и так далее), то массив выводится в том порядке, как [dw]сформирован[/dw][di]То есть строковые ключи массива автоматически JS-ом преобразуются при формировании данных из PHP в js. Если важен и критичен порядок, то лучше использовать ключи строки. [/di]. Если же ключи могут быть приведены к числовым — массив значений выводится отсортированный по ключам.

    • STRING — текстовое поле ввода.
    • CHECKBOX — да/нет.
    • CUSTOM — позволяет создавать кастомные элементы управления.
    • FILE — выбор файла.
    • COLORPICKER — указание цвета:

      $arComponentParameters["PARAMETERS"]["COLOR"]  = Array(
          "PARENT" => "BASE",
          "NAME" => 'Выбор цвета',
          "TYPE" => "COLORPICKER",
          "DEFAULT" => 'FFFF00'
      );
      

    Внешний вид списка меняется в зависимости от наличия/отсутствия ключей MULTIPLE и ADDITIONAL_VALUES:

    • Если MULTIPLE и ADDITIONAL_VALUES отсутствуют или равны «N», то выводится просто список, никаких значений в список не добавляется.
    • Если ADDITIONAL_VALUES = «Y», MULTIPLE = «N», то в список добавляется значение «другое» и рядом доп.поле для ввода значения вручную:

    • Если ADDITIONAL_VALUES = «N», MULTIPLE = «Y», то в список ничего не добавляется, просто появляется возможность выбрать несколько элементов:

    • Если ADDITIONAL_VALUES = «Y», MULTIPLE = «Y», то в список добавляется значение не выбрано и рядом множественное дополнительное поле для ввода значения вручную.
    • Примечание: Скриншоты делались для значения SIZE = 9. Если этот ключ не указывать, список будет выпадающим.

    Параметр REFRESH позволяет после выбора значения перегрузить всю форму с параметрами. Делается это, например, для выбора инфоблока конкретного типа. То есть имеем два параметра — тип инфоблока и код инфоблока. Исходное положение — в первом список всех типов инфоблоков, во втором — список всех инфоблоков данного сайта, а после выбора нужного типа инфоблока параметры компонента перегружаются и мы видим только инфоблоки нужного типа.

    Внешне для параметров типа LIST этот ключ проявляется как кнопка с надписью ОК возле параметра (см. скриншоты выше).

    Если нужно, чтобы некий параметр появлялся или нет в зависимости от другого, делается это так. Пусть нам необходимо показать список свойств инфоблока. Предположим, что ID инфоблока содержится в параметре компонента IBLOCK_ID, а параметр, где будет список свойств назовем PROP_LIST. У параметра IBLOCK_ID должен быть выставлен ключ REFRESH = ‘Y’. Код:

    if (0 < intval($arCurrentValues['IBLOCK_ID']))
    {
       $arPropList = array();
       $rsProps = CIBlockProperty::GetList(array(),array('IBLOCK_ID' => $arCurrentValues['IBLOCK_ID']));
       while ($arProp = $rsProps->Fetch())
       {
          $arPropList[$arProp['ID']] = $arProp['NAME'];
       }
       $arComponentParameters['PARAMETERS']['PROP_LIST'] = array(
          'NAME' => 'название параметра',
          'TYPE' => 'LIST',
          'VALUES' => $arPropList,
       );
    }

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

    "SET_TITLE" => array(),
    "CACHE_TIME" => array(),

    Первый из указанных параметров указывает, следует ли компоненту установить заголовок страницы, а второй — все настройки, связанные с кешированием.

    Только комплексные компоненты могут работать в режиме ЧПУ или переопределять переменные, которые приходят из HTTP запроса. В этом случае необходимо среди параметров указать ещё два особых параметра:

    • «VARIABLE_ALIASES» — массив, описывающий переменные, которые компонент может получать из HTTP запроса. Каждый элемент массива имеет вид:
      "внутреннее название переменной" => array(
        "NAME" => "название переменной на текущем языке",
      )
    • «SEF_MODE» — массив, описывающий шаблоны путей в режиме ЧПУ. Каждый элемент массива имеет вид:
      "код шаблона пути" => array(
          "NAME" => "название шаблона пути на текущем языке",
          "DEFAULT" => "шаблон пути по-умолчанию",
          "VARIABLES" => "массив внутренних названий переменных, которые могут использоваться в шаблоне"
      )

    Важно! Наиболее правильный вариант кастомизации компонента — скопировать его в отдельное пространство имен и работать уже с копией компонента. При этом нужно учитывать последствия:

    • Увеличивается общее количество компонентов, соответственно растет и количество выделяемых на их поддержку ресурсов.
    • Сложность в освоении новым разработчиком: сначала ему нужно будет найти в чем отличие от уже существующего стандартного компонента.

    Шаблоны компонента

    Цитатник веб-разработчиков.

    Роман Петров: Не все знают, что у стандартных компонентов «1С-Битрикс» есть много других красивых штатных шаблонов, которые практически никогда не используются. Для любопытствующих вопрос: знаете ли вы, что шаблон постраничной навигации имеет аж пять шаблонов: .default, arrows, js, modern, orange?

    Шаблон компонента — программный код, преобразующий данные, подготовленные компонентом, непосредственно в HTML-код.

  • Переменные
  • Шаблон простого компонента
  • Шаблон комплексного компонента
  • Как система ищет шаблон
  • Подключение шаблона
  • Редактирование шаблона
  • Особенности работы с ajax
  • Пример шаблона
  • Шаблоны компонента делятся на системные и пользовательские:

    • Системные шаблоны поставляются вместе с компонентом и лежат в подпапке templates папки компонента.
    • Пользовательские шаблоны компонента — шаблоны, которые изменены под нужды конкретного сайта. Они должны лежать в папках шаблонов сайтов (т.е. в /local/templates/шаблон_сайта/). При копировании шаблона компонента средствами системы, они будут расположены по следующему пути: /local/templates/шаблон_сайта/components/namespace/название_компонента/название_шаблона.

    Шаблоны компонента определяются по именам. Шаблон по умолчанию имеет имя .default. Если в настройках параметра компонента не указывается имя шаблона, вызывается шаблон по умолчанию. Остальные шаблоны могут называться произвольно.

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

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

    Например, у компонента bitrix:main.site.selector есть системный шаблон dropdown, который лежит в подпапке templates папки компонента. Если этот шаблон компонента требуется изменить под конкретный сайт, то папку шаблона dropdown следует скопировать в папку /local/templates/шаблон_сайта/components/bitrix/main.site.selector/ и изменить по своему усмотрению.

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

    Переменные, доступные в шаблоне компонента

    В шаблоне используются следующие переменные:

    • $templateFile – путь к шаблону относительно корня сайта, например /bitrix/components/bitrix/iblock.list/templates/.default/template.php).
    • $arResult – массив результатов работы компонента.
    • $arParams – массив входящих параметров компонента, может использоваться для учета заданных параметров при выводе шаблона (например, отображении детальных изображений или ссылок).
    • $arLangMessages – массив языковых сообщений шаблона (для php шаблонов не устанавливается).
    • $templateFolder — путь к папке с шаблоном от DOCUMENT_ROOT (например /bitrix/components/bitrix/iblock.list/templates/.default).
    • $parentTemplateFolder — путь относительно корня сайта к папке шаблона комплексного компонента, в составе которого подключается данный компонент (если компонент подключается самостоятельно, то эта переменная пуста).
    • $component — ссылка на текущий вызванный компонент (тип CBitrixComponent).
    • $this — ссылка на текущий шаблон (объект, описывающий шаблон, тип CBitrixComponentTemplate)
    • $templateName — имя шаблона компонента (например: .dеfault)
    • $componentPath — путь к папке с компонентом от DOCUMENT_ROOT (напр. /bitrix/components/bitrix/iblock.list)
    • $templateData — массив для записи, обратите внимание, таким образом можно передать данные из template.php в файл component_epilog.php, причем эти данные попадают в кеш, т.к. файл component_epilog.php исполняется на каждом хите.

    Кроме того внутри PHP шаблона объявлены глобальными переменные $APPLICATION, $USER, $DB.

    Шаблон простого компонента

    Папка шаблона простого компонента может содержать следующие подпапки и файлы:

    • подпапку /lang, в которой расположены файлы языковых сообщений (переводов) шаблона компонента;
    • файл result_modifier.php, который подключается непосредственно перед подключением шаблона компонента. Этот файл получает на вход массив результатов работы компонента $arResult и массив параметров вызова компонента $arParams. Таким образом, можно, например, изменить массив результатов работы компонента под конкретный шаблон.
    • файл component_epilog.php, который подключается после исполнения шаблона.
    • файл style.css, который определяет стили, необходимые данному шаблону.
    • файл script.js, который определяет и подключает яваскрипты, необходимые данному шаблону. Этот файл может отсутствовать.
    • файл .description.php, который содержит название и описание шаблона для визуального редактора.

      Пример файла .description.php

    • файл .parameters.php, который содержит описание дополнительных входных параметров шаблона для визуального редактора.

      Пример файла .parameters.php

    • файл template.ext, который и является собственно шаблоном. Расширение ext зависит от того, какой движок шаблонизации нужно подключать. По умолчанию расширение равно php. Этот файл должен обязательно присутствовать.
    • любые другие папки и файлы с ресурсами, необходимыми шаблону компонента. Например, папка image, содержащая изображения, необходимые шаблону.

    Шаблон комплексного компонента

    Шаблон комплексного компонента содержит все те же папки, что и шаблон простого компонента, и дополнительно:

    • шаблоны простых компонентов, которые входят в состав комплексного. Эти шаблоны располагаются в папках вида /пространство_имен/название_простого_компонента/ относительно папки шаблона комплексного компонента.
    • простые компоненты, входящие в состав комплексного, подключаются на шаблонах страниц комплексного компонента.

    Как система ищет шаблон

    Применяется следующий алгоритм поиска подходящего шаблона для компонента:

    • Если используется шаблон сайта из папки [dw]local[/dw][di]Папка local — папка для разработки в рамках ядра D7 Подробнее…[/di], то система сначала ищет шаблон компонента в папке /local/templates/текущий_шаблон_сайта/components/.

      Если там шаблон не найден, то берётся папка /local/templates/.default/components/. Если шаблон найден, то поиск прекращается.
    • Если используется шаблон сайта из папки /bitrix/templates/, то берется папка /bitrix/templates/текущий_шаблон_сайта/components/. В этой папке в пути /пространство_имен_компонента/название_компонента/ проверяется последовательно наличие файла или папки с именем шаблона. Если таковых нет, то проверяется наличие файла имя_шаблона.ext, где в качестве ext берутся последовательно все доступные расширения всех установленых на сайте движков шаблонизации. Если шаблон найден, то алгоритм завершается.
    • Если на шаге 1 шаблон не найден, то берется папка /bitrix/templates/.default/components/. И применяется алгоритм, описанный в шаге 1. Если шаблон найден, то алгоритм завершается.
    • Если на шаге 2 шаблон не найден, то производится поиск среди системных (т.е. поставляемых с компонентом) шаблонов.

    Особенности поиска:

    • Если имя шаблона не задано, то ищется шаблон с именем .default.
    • Если шаблон задан именем папки, то в случае простого компонента в этой папке ищется файл template.ext, а в случае комплексного — название_страницы.ext. Расширение ext сначала принимается равным php, а затем расширениям других доступных на сайте движков шаблонизации.

    Например, требуется показать компонент bitrix:catalog.list с помощью шаблона table. Пусть на сайте, кроме стандартного движка шаблонизации (файлы с расширением php), доступен так же движок Smarty (файлы с расширением tpl). Система проверит сначала папку /local/templates/текущий_шаблон_сайта/components/bitrix/catalog.list/ на наличие файла или папки с именем table. Если таковых нет, система проверит эту же папку на наличие файлов table.php и table.tpl. Если ничего не найдено, система изучит папки /bitrix/templates/.default/components/bitrix/catalog.list/ и /bitrix/components/bitrix/catalog.list/templates/.

    Если папка компонента найдена, в этой папке сначала ищется файл template.php, и если этот файл не найден, то ищется template.tpl. Если шаблон задан в виде table/template.php, то сразу берется указанный файл.

    Если простой компонент вызывается в составе комплексного, то шаблон простого компонента сначала ищется в составе шаблона комплексного компонента, а потом (если не найден) в собственных шаблонах. Чтобы это правило работало, при вызове простых компонентов в составе комплексного не забывайте указывать четвертым параметром переменную $component, указывающую на родительский компонент. Т.е. код вызова простого компонента должен иметь вид:

    $APPLICATION->IncludeComponent("custom:catalog.element", "", array(...), $component);

    Примечание:

    В одной папке (например, /bitrix/templates/текущий_шаблон_сайта/components/) есть шаблоны двух компонентов, комплексного и простого:

    • catalog (комплексный, в котором есть еще простой catalog.section)
    • catalog.section (простой)

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

    Подключение шаблона

    В коде подключения указывается только <namespace>, имя компонента, имя шаблона (и параметры самого компонента). При обработке кода ядро сначала проверяет наличие шаблона компонента в шаблоне текущего сайта: /local/templates/<имя шаблона сайта>/components/<namespace>/<имя компонента>/<имя шаблона>/template.php.

    Если — bitrix (/bitrix/components/bitrix), то это папка для шаблонов стандартных компонентов. Если — выбранное вами <имя> (/local/components/<имя>), то это папка для шаблонов ваших компонентов.

    Если файла шаблона нет, проверяется шаблон сайта по умолчанию: /bitrix/templates/.default/components/<namespace>/<имя компонента>/<имя шаблона>/template.php.

    И только после этого происходит подключение шаблона компонента из папки компонента.

    Шаблон подключается командой:

    $this->IncludeComponentTemplate($templatePage = "");

    Где $templatePage – для комплексного компонента имя текущей страницы, для обычного компонента пустая строка.

    Примеры подключения шаблона компонента:

    1. Подключим шаблон текущего комплексного компонента для страницы section:

      $this->IncludeComponentTemplate("section");

    2. Подключим шаблон текущего простого компонента:

      $this->IncludeComponentTemplate();

    Редактирование шаблона

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

    Копирование шаблона выполняется по команде Копировать шаблон компонента при включенном режиме Правка:

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

    При отмеченной опции Перейти к редактированию шаблона сразу будет осуществлен переход на страницу редактирования шаблона компонента.

    Редактирование шаблона допускает добавление в него логики действий, но такую модификацию лучше выносить в файлы result_modifier.php и component_epilog.php (которые должны быть расположены в папке шаблона) для более сложного изменения результата работы.

    Особенности работы с ajax

    Использование режима ajax имеет свои особенности. Чтобы строка навигации в открываемой по ajax странице имела в цепочке навигации своё название, необходимо, чтобы в шаблоне компонента обязательно присутствовал элемент с id="navigation". Это необязательно должен быть div, это может быть span, h1, p и так далее.

    Аналогично, для заголовка обязательно наличие элемента с id="pagetitle".

    Пример шаблона

    Шаблон компонента Меню
    Проверка включения <?if (!defined(«B_PROLOG_INCLUDED»)
    || B_PROLOG_INCLUDED!==true)die();?>
    Старт скрипта <?if (!empty($arResult)):?>
    Открытие тега <ul> — ненумерованный список <ul class=»…»>
    Старт цикла поиска <?foreach ($arResult as $arItem):?>
    Ссылка активная <?if($arItem[«SELECTED»]):?>
    Вывод ссылки <li><a href=»<?=$arItem[«LINK»]?>» class=
    «selected»><?=$arItem[«TEXT»]?></a></li>
    Проверка на продолжение цикла <?else:?>
    Ссылка неактивная <li><a href=»<?=$arItem[«LINK»]?>»>
    <?=$arItem[«TEXT»]?></a></li>
    Завершение вывода ссылки <?endif;?>
    Завершение цикла поиска <?endforeach;?>
    Закрытие тега <ul> — ненумерованный список </ul>
    Завершение скрипта <?endif;?>

    Список ссылок по теме:

    • Пользовательские движки шаблонизации

    Типичные ошибки

    Цитатник веб-разработчиков.

    TeppopucT: И правда, все проблемы в руках! Ищите ошибки в коде!!! Все теги должны отвечать стандартам. А Битрикс, подхватит!!! Который раз помогает очистка кода. Пусть и ручная работа, и кропотливая, но с достойным финалом!

    Не удалось обнаружить код вызова компонента

    Довольно распространенная ошибка, когда вы в режиме редактирования пытаетесь отредактировать параметры какого-то компонента на странице. Хоть в коде и присутствует строка $APPLICATION->IncludeComponent() (вызов компонента), всё равно иногда появляется ошибка Не удалось обнаружить код вызова компонента. К сожалению, универсального решения данной проблемы нет.

    Ошибка может возникать из-за разных причин:

    • Код вызова компонента не взят в отдельные <? ?>.

      Решение: проверить отделенность кода компонента от другого php-кода на странице.

      То есть, если у вас на странице php-код в таком виде:

      <?
      php-код
      
      компонент
      
      php-код
      ?>

      то будет ошибка.

      Необходимо, чтобы было так:

      <?
      php-код
      ?>
      
      <?
      компонент
      ?>
      
      <?
      php-код
      ?>
      
      

      Можно также попробовать вставить такую конструкцию перед вызовом компонента: <?/**/?>.

    • Ошибки в html коде на странице.

      Решение: проверить валидность html кода, убрать все html-комментарии со страницы.

    • Несоответствие кодировки файла. До версии 20.100.0 модуля main

    • Несоответствие между владельцем файла и пользователем под которым система файлы редактирует.

      Решение: проверить права пользователя.

    • Взаимное влияние аналогичных компонентов.

      Решение: удалить несколько аналогичных компонентов рядом с неработающим.

    Кастомизация шаблонов компонентов

    Цитатник веб-разработчиков.

    Рамиль Юналиев: По себе знаю, что не хватало примеров работы именно с шаблонами, потому что с них начинал. Если программисты «въедут» в эти шаблоны, то там уже пойдет…

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

    Кастомизация [dw]штатного[/dw][di]В штатных шаблонах «1С-Битрикс: Управление сайтом» используется Bootstrap 4.
    Это определяет требования к клиентскому обеспечению.
    Рекомендации таковы:

    Chrome — не ниже версии 45,
    Firefox — не ниже версии 38

    Edge — не ниже версии 12

    Explorer — не ниже версии 10
    Safari — не ниже версии 9

    Opera — не ниже версии 30

    iOS — не ниже версии 9
    Android — не ниже версии 4.4

    [/di] шаблона компонента, как правило, преследует две цели:

    • Приведение формы вывода данных компонента в соответствие с дизайном сайта;
    • Организация вывода данных компонента в виде, недоступном в стандартном варианте.

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

    Кастомизация шаблона

    Как изменить стандартный вид компонента

    Вопрос с форума: Хотелось бы научиться верстать (интегрировать) шаблоны под битрикс, но у меня опыта нет. Буду благодарен всем за любые подсказки, которые помогут в короткие сроки достичь цели.

    Иван, веб-разработчик:

    1. Действительно хотеть это.
    2. Очень много работать в выбранном направлении.
    3. Начинать с

    CSS руководство

    ,

    HTML manual

    , и т.д. для javascript и php думаю найдете сами. Возможно стоит вначале посмотреть вот этот ресурс: htmlbook.ru
    4. Никогда не верстать от «сделаю так, посмотрю на результат». Действуйте от знаний, полученных в п. 3.
    5. Не париться если не получается и делать заново пока не получится и не разберетесь в основах основ почему именно так работает, а так не работает.
    6. Вам еще предстоит узнать, что такое IE, вы стрессоустойчивы? Это вам поможет.
    7. Никогда не брать чужие куски кода/верстки и использовать «как есть», всегда добираться до сути реализации.

    В веб-программировании и программировании вообще есть популярный миф, что логику, данные и представления легко и очевидно можно отделить друг от друга, что решение этой задачи однозначно. Если немного подумать, становится очевидно что логика, данные и представления легко переходят между собой в зависимости от того, с какого уровня абстракции вы смотрите на задачу. Например, html-код шаблона для разработчика сайта это безусловно представление, для браузера это код, а для ядра CMS это данные.

    Разработчики, впервые видящие код шаблона любого компонента, например списка новостей, ужасаются циклу, каше из php и HTML (и js изредка можно встретить). Это шаблон, он для вывода. Все данные уже собраны, запросы отработали, понятно что и куда мы выводим, даже отлов угроз уже прошел. Шаблон — последнее звено в цепочке кода и данных. Тут логика вывода действительно смешана с оформлением, и это правильно.

    Начинающему разработчику часто очень непросто разобраться в том, где в шаблоне данные, где представление. В шаблоне можно писать любой php-код, можно написать прямое, не API, обращение к базе данных, можно написать десяток строк на API и решить задачу. Битрикс этим очень «развращает» разработчика: не запрещено писать бизнес-логику в шаблонах и HTML в компонентах, слишком много свободы. В собственном коде и по идеологии Bitrix Framework задача разделения данных решена нормально, а вот написание в шаблоне ерунды, не рекомендованной по идеологии, система не контролирует.

    Приступая к кастомизации шаблона нужно помнить:

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

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

    1. Приведение формы вывода данных компонента в соответствие с дизайном сайта;
    2. Организация вывода данных компонента в виде, недоступном в стандартном варианте.

    Пользовательские шаблоны компонента — шаблоны, которые изменены под нужды конкретного проекта. Они должны лежать в папках шаблонов портала (т.е. в [ds]/local[/ds][di]При обработке папок приоритет всегда у папки /local перед /bitrix. Это означает, что если в /local/templates/ и /bitrix/templates/ будут находиться шаблоны сайта с одинаковым названием, то подключится шаблон из /local.

    Подробнее...[/di]/templates/шаблон_сайта/

    ). При копировании шаблона компонента средствами системы, они будут расположены по следующему пути: /local/templates/шаблон_сайта/components/namespace/название_компонента/название_шаблона.

    Примечание: Теоретически шаблон можно расположить в каталоге /bitrix/templates/текущий_шаблон_сайта/components/. Но в этом случае возникают нюансы, которые нужно учитывать. Предположим, что в этой папке оказались шаблоны двух компонентов с общей начальной частью в названии. Например: catalog (комплексный) и собственно catalog.section (простой). При этом в шаблоне комплексного есть еще простой catalog.section. В этом случае для catalog.section нужно использовать единый шаблон с именем, отличным от .default. Иначе он не будет подхвачен.

    Копировать шаблон можно следующими способами:

    • В рамках файловой системы копированием папки /bitrix/components/bitrix/_нужный_компонент_/templates/ в папку /local/templates/шаблон_сайта/components/namespace/название_компонента/_название_шаблона.
    • Средствами интерфейса системы с помощью команды Копировать шаблон компонента (при включённом режиме Правка):

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

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

    При кастомизации шаблонов можно использовать буферизацию.

    В главе приведены некоторые примеры кастомизации шаблонов.

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

    В главе приводятся примеры кастомизации шаблонов. Кроме этих материалов рекомендуем познакомится ещё с сообщениями в блогах разработчиков:

    • Выводим в корзине свойства элемента инфоблока
    • Модифицируем постраничную навигацию

    Модификация шаблона простого компонента в составе комплексного

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

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

    Желательно обходиться без кастомизации компонента там, где в этом нет особой необходимости. В этом случае:

    • не теряются обновления;
    • легче решать проблемы через техподдержку (ТП не занимается решением проблем, возникающих в работе кастомного кода если явно не обозначена ошибка в работе API);
    • в комплексных компонентах стандартно реализован AJAX.

    В качестве примера возьмем компонент Социальная сеть. Задача: заменить форму редактирования описания группы (textarea) на визуальный редактор с возможностью вставки html.

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

    Как сделать

    • Скопируйте шаблон компонента
    • Сделайте замену в шаблоне:
      <textarea name="GROUP_DESCRIPTION"     style="width:98%" rows="5"><?= $arResult["POST"]["DESCRIPTION"]; ?></textarea>

      на

      <input type="hidden" name="GROUP_DESCRIPTION"><?
      $GLOBALS['APPLICATION']->IncludeComponent( 
         "bitrix:fileman.light_editor", 
         ".default", 
         Array( 
            "CONTENT" => htmlspecialchars_decode($arResult["POST"]["DESCRIPTION"]), 
            "INPUT_NAME" => "GROUP_DESCRIPTION", 
            "WIDTH" => "98%", 
            "HEIGHT" => "200px", 
            "USE_FILE_DIALOGS" => "N", 
            "FLOATING_TOOLBAR" => "N", 
            "ARISING_TOOLBAR" => "N", 
            "VIDEO_ALLOW_VIDEO" => "N",
         ) 
      );
      ?>

    Получаем результат. Было:

    Стало:

    При этом весь остальной код остаётся стандартный, т.е. будет обновляться и поддерживаться компанией «1С-Битрикс».

    Пример. Вывод голосования

    Задача:

    1. Выдать голосование не в детальном товаре, а на странице со списком товаров.
    2. Выдать ее на AJAX стандартными средствами

    В каталоге нет голосования. Зато оно есть в комплексном компоненте Новости, в детальном просмотре элемента.

    Решение (для слабоподготовленного разработчика, методом копи-паст).

    Первая часть задачи

    В шаблоне комплексного компонента Новости находим такой код:

    <?$APPLICATION->IncludeComponent(
       "bitrix:iblock.vote",
       "",
       Array(
          "IBLOCK_TYPE" => $arParams["IBLOCK_TYPE"],
          "IBLOCK_ID" => $arParams["IBLOCK_ID"],
          "ELEMENT_ID" => $ElementID,
          "MAX_VOTE" => $arParams["MAX_VOTE"],
          "VOTE_NAMES" => $arParams["VOTE_NAMES"],
          "CACHE_TYPE" => $arParams["CACHE_TYPE"],
          "CACHE_TIME" => $arParams["CACHE_TIME"],
       ),
       $component
    );?>

    Вставляем этот код в шаблон компонента bitrix:catalog.top куда-нибудь, где он должен выводиться. Например, в таблицу после вывода <?=$arElement["PREVIEW_TEXT"]?>.

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

    "ELEMENT_ID" => $ElementID,

    на

    "ELEMENT_ID" =>$arElement["ID"],

    Вторая часть задачи

    В папке с шаблонами компонента bitrix:iblock.vote есть два шаблона: .default и ajax. Применяем второй. Вторая проблема тоже решена.
    В итоге вызов компонента получился вот таким:

    <?$APPLICATION->IncludeComponent(
       "bitrix:iblock.vote",
       "ajax",
       Array(
          "IBLOCK_TYPE" => $arParams["IBLOCK_TYPE"],
          "IBLOCK_ID" => $arParams["IBLOCK_ID"],
          "ELEMENT_ID" =>$arElement["ID"],
          "MAX_VOTE" => $arParams["MAX_VOTE"],
          "VOTE_NAMES" => $arParams["VOTE_NAMES"],
          "CACHE_TYPE" => $arParams["CACHE_TYPE"],
          "CACHE_TIME" => $arParams["CACHE_TIME"],
       ),
       $component
    );?>

    Пример. Добавление типа отсутствия

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

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

    В качестве примера рассмотрим такую задачу в рамках коробочной версии «Битрикс24»: необходимо добавить в Графике отсутствий (Сотрудники > График отсутствий) новый Тип отсутствий, например: отсутствие Отгул в счет отработанного времени.

    Задания свойств инфоблока

    • В свойствах инфоблока (Контент > Информ. блоки > Типы информ. блоков > Оргструктура > график отсутствий) в закладке Свойства в строке Тип присутствия нажмите на кнопку с многоточием. Откроется форма Настройка свойства информационного блока:
    • В Значениях списка добавьте новое значение Отгул в счёт отработанного времени. И смените параметры сортировки, чтобы расположить новый тип отсутствия в нужном месте.

    Изменение шаблона

    • Скопируйте шаблон и откройте его для редактирования.
    • Найдите строки:
      GetMessage('INTR_ABSC_TPL_LEGEND_*************')?>'},
      			{NAME:'************',TITLE:'
      
    • Вместо звездочек вставьте XML_ID, который вы использовали при добавлении значения свойства в форме настройки информационного блока.
    • Сохраните внесенные изменения.
    • Назначьте модифицированный шаблон для использования компонентом.

    Изменение файла CSS шаблона

    • Откройте для редактирования файл стилей этого шаблона.
    • Найдите строки:
      div.bx-calendar-color-************* 
         {background-image: url(/bitrix/components/bitrix/intranet.absence.calendar/templates/.default/images/calendar_grad_red.gif);}
    • Добавьте свою аналогичную строку с файлом формата gif (или другого, это не важно) нужного вам цвета (предварительно загрузив файл в систему).
    • Введите вместо звездочек XML_ID, который вы задали при добавлении значения свойства в форме настройки информационного блока.
    • Сохраните внесенные изменения.

    Добавление языкового сообщения

    • Откройте для редактирования файл с языковыми сообщениями bitrixtemplates.defaultcomponentsbitrixintranet.absence.calendar_имя вашего шаблона_langrutemplate.php.
    • Добавьте строку:
      $MESS["INTR_ABSC_TPL_LEGEND_************"] = "отгул за отработанное время";

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

    • Сохраните внесенные изменения.

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

    Пример. Внешние файлы css

    С версии 15.5.1 появилась штатная поддержка вызова стороннего файла css. Под сторонним файлом понимается как локальный файл вне компонента так и файл на внешнем домене.

    Для подключения такого файла в template.php необходимо прописать:

    $this->addExternalCss("/local/styles.css");
    $this->addExternalJS("/local/liba.js");

    Пример. Редактирование шаблона меню

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

    Выделите в HTML-верстке код, отвечающий за показ верхнего меню. Например:

    <div class="topmenu">
    		<ul class="topmenu">
    			<li><a href="#" class="first">На главную</a></li>
    			<li><a href="#">Новости</a></li>
    			<li><a href="#" class="selected">Магазины</a></li>
    			<li><a href="#">Книги</a></li>
    			<li><a href="#">Форум</a></li>
    			<li><a href="#">О компании</a></li>
    			<li><a href="#">О Контакты</a></li>
    		</ul>
    </div>

    В этом коде пункты меню представлены в виде списка, который обладает следующими нюансами:

    • У первого пункта меню должен быть указан стиль first;
    • У выделенного пункта меню должен быть указан стиль selected;
    • Меню является одноуровневым.

    Модифицировать будем код шаблона gray-tabs-menu. Скопируйте шаблон в собственное пространство имен и откройте его для редактирования.

    Редактирование шаблона можно проводить как в форме Bitrix Framework, так и копированием кода и правкой его в другом редакторе. Использовать другой редактор удобнее в случае объемных текстов, так как форма редактирования в Bitrix Framework не поддерживает цветовое выделение тегов. Для примера используйте Notepad++.

    Код меню для этого шаблона выглядит так:

    1 <?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
    2
    3 <?if (!empty($arResult)):?>
    4 <div class="gray-tabs-menu">
    5	<ul>
    6 <?foreach($arResult as $arItem):?>
    7
    8   <?if ($arItem["PERMISSION"] > "D"):?>
    9	<li><a href="<?=$arItem["LINK"]?>"><nobr><?=$arItem["TEXT"]?></nobr></a>
    10	<?endif?>
    11
    12 <?endforeach?>
    13
    14	</ul>
    15</div>
    16<div class="menu-clear-left"></div>
    17<?endif?>

    Рассмотрим каждую строчку этого шаблона:

    Строки шаблона
    1. Проверка, вызван ли этот файл напрямую или нет. Если напрямую – прекратить работу.
    3. Если массив с пунктами меню $arResult не пуст, то выполнять дальнейшие действия
    4,5. Внешний блок и начало списка пунктов меню
    6. Цикл по массиву с пунктами меню. В $arItem – текущий элемент цикла.
    8-10. Если текущий пользователь обладает правами на просмотр данной страницы, вывести элемент списка с ссылкой на эту страницу. В полях LINK и TEXT содержится адрес страницы и название пункта меню, соответственно.
    12. Конец цикла по массиву с пунктами меню.
    14,15. Конец списка пунктов меню и конец блока
    16. Специальный HTML-тэг, специфичный для использованной верстки
    17. Конец условия на наличие пунктов меню (см. строку 3)

    Таким образом, шаблон меню содержит:

    • область пролога шаблона меню;
    • область тела шаблона меню (вывод повторяющихся элементов);
    • область эпилога шаблона меню.

    После адаптации шаблон примет вид (зеленым цветом выделены изменения):

    1 <?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
    2
    3 <?if (!empty($arResult)):?>
    4 <div class="topmenu">
    5	<ul class="topmenu">
    5a <? $cnt=0; ?>
    6 <?foreach($arResult as $arItem):?>
    7
    8   <?if ($arItem["PERMISSION"] > "D"):?>
    8a   <?if ($arItem["SELECTED"]): ?>
    8b	<li><a href="<?=$arItem["LINK"]?>" class="selected"><?=$arItem["TEXT"]?></a></li>
    8c   <?else:?>
    8d        <?if ($cnt==0):?>
    8e	<li><a href="<?=$arItem["LINK"]?>" class="first"><?=$arItem["TEXT"]?></a></li>
    8f        <?else:?>
    9	<li><a href="<?=$arItem["LINK"]?>"><?=$arItem["TEXT"]?></a></li>
    10a	<?endif?>
    10b   <?endif?>
    10c <?$cnt++; ?>
    10 <?endif?>
    11
    12 <?endforeach?>
    13
    14	</ul>
    15</div>
    16
    17<?endif?>

    Итак, что мы сделали?

    • В строках 4,5 заменили стили у блока и у списка.
    • В строке 5а ввели переменную $cnt с единственной целью – отследить первый элемент списка – в верстке он задается другим стилем. Эта переменная используется в строке 8d в строке 10c.
    • В строках 8-10 добавили условие проверки активного пункта меню и первого пункта меню.
    • И, наконец, удалили специфику верстки из 16 строки.
    • Кроме того, у нас нет необходимости в специализированных стилях и картинках для этого шаблона. Поэтому нужно удалить из каталога /bitrix/templates/.default/components/menu/top_menu/ файл style.css и папку /images/.

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

    Пример. Редактирование шаблонов «Корзина» и «Оформление заказа»

      Постановка задачи

    Рассмотрим кастомизацию шаблонов компонентов [comp include_sale_basket_basket]Корзина[/comp] и [comp include_sale_order_ajax]Оформление заказа[/comp] на примере ограничения минимальной стоимости заказа.

    Задача:

    1. В корзине:
      • Деактивировать кнопку «Оформить заказ», если сумма заказа меньше установленной.
      • Выдать сообщение о возможности оформления заказа при достижении суммы.
      • При изменении суммы заказа выше минимальной кнопку активировать, предупреждение убрать.
    2. При оформлении заказа:
      • Добавить проверку итоговой стоимости товаров для избежания возможности оформления заказа по прямой ссылке.
      • Вывести предупреждение, если сумма заказа меньше установленной.

      Компонент «Корзина»

    Посмотрите в видеоролике, как можно кастомиризовать шаблон компонента Корзина:

    Описание решения:

    Копируем шаблон компонента sale.basket.basket средствами интерфейса системы с помощью команды Копировать шаблон компонента, как описано в уроке Теория.Кастомизация шаблона.

    В шаблоне компонента Корзина в файле mutator.php находим такой код:

    $totalData = array(
    	'DISABLE_CHECKOUT' => (int)$result['ORDERABLE_BASKET_ITEMS_COUNT'] === 0,
    	'PRICE' => $result['allSum'],
    	'PRICE_FORMATED' => $result['allSum_FORMATED'],
    	'PRICE_WITHOUT_DISCOUNT_FORMATED' => $result['PRICE_WITHOUT_DISCOUNT'],
    	'CURRENCY' => $result['CURRENCY']
    );

    Вставляем сразу за этим фрагментом код:

    if ($result['allSum'] < 1500) {
    $totalData['DISABLE_CHECKOUT'] = true;
    echo "<h3>Оформление заказа возможно после наполнения корзины на сумму более 1500 рублей.</h3>";
    }

      Компонент «Оформление заказа»

    Посмотрите в видеоролике, как можно кастомиризовать шаблон компонента Оформление заказа:

    Описание решения:

    Копируем шаблон компонента sale.order.ajax средствами интерфейса системы с помощью команды Копировать шаблон компонента, как описано в уроке Теория.Кастомизация шаблона.

    В шаблоне компонента Оформление заказа в файле template.php находим следующий код:

    if ($request->get('ORDER_ID') <> '')
    {
    	include(MainApplication::getDocumentRoot().$templateFolder.'/confirm.php');
    }
    elseif ($arParams['DISABLE_BASKET_REDIRECT'] === 'Y' && $arResult['SHOW_EMPTY_BASKET'])
    {
    	include(MainApplication::getDocumentRoot().$templateFolder.'/empty.php');
    }
    else
    {
    

    Добавляем код:

        if ($arResult['ORDER_PRICE'] < 1500)
        {
            echo "<h3>Оформление заказа возможно после наполнения корзины на сумму более 1500 рублей.</h3>";
            return;
        }
    

    Задача решена.

    Создание структуры сайта

    Цитатник веб-разработчиков.

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

    Создание структуры сайта производится в соответствии с ТЗ на сайт.

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

    Создайте требуемую для вас структуру файлов и папок. При создании структуры нельзя забывать про служебные страницы — поиск и карту сайта.

    Примечание: разработчик должен взять за правило: на проекте должны быть вменяемые тексты на страницах 404, 500 ошибок и сообщение о технических работах.

    Пример структуры сайта:

    Раздел сайта Каталог/файл Тип информации Используемые компоненты
    Главная /index.php Страница с динамической информацией bitrix:news.list
    bitrix:catalog.top
    Новости /news Страница с динамической информацией bitrix:news
    Магазины /shops Статическая страница нет
    Книги /books/ Страница с динамической информацией bitrix:catalog
    Форум /forum Страница с динамической информацией bitrix:forum
    О компании /about/ Статическая страница нет
    Контакты /contacts/ Статическая страница нет
    Карта сайта (служебная страница) /search/map.php Страница с динамической информацией bitrix:main.map
    Результаты поиска (служебная страница) /search/index.php Страница с динамической информацией bitrix:search.page
    Обработчик 404 ошибки /404.php Страница с динамической информацией bitrix:main.map
    обработчик 500-й ошибки /500.html Статическая HTML страница (не Bitrix Framework) нет

    Список ссылок по теме:

    • Создание новых страниц и разделов в интерфейсе Эрмитаж в курсе Контент-менеджер
    • Управление структурой проекта в курсе Контент-менеджер

    Структура файлов

    Структура файлов

    Файловая структура Bitrix Framework организована таким образом, что программные компоненты ядра продукта были отделены от пользовательских файлов, а также файлов, определяющих внешнее представление сайта. Данная особенность позволяет:

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

    Вся система целиком лежит в каталоге /bitrix/, в него входят следующие подкаталоги и файлы:

    • /activities/ — папки действий для бизнес-процессов;
    • /admin/ — административные скрипты;
    • /cache/ — файлы кэша;
    • /components/ — папка для системных и пользовательских компонентов;
    • /css/ — общие стили модулей;
    • /gadgets/ — папки гаджетов;
    • /js/ — файлы javascript модулей;
    • /stack_cache/ — файлы кеша «с вытеснением»;
    • /services/ — публичные сервисы модулей;
    • /themes/ — темы административного раздела;
    • /wizards/ — папки мастеров;
    • /images/ — изображения используемые как системой в целом, так и отдельными модулями;
    • /managed_cache/ — управляемый кеш;
    • /modules/ — каталог с модулями системы, каждый подкаталог которого имеет свою строго определённую структуру;
    • /php_interface/ — вспомогательный служебный каталог, в него входят следующие каталоги и файлы:
      • dbconn.php — параметры соединения с базой. С версии 20.900.0 параметры соединения берутся из файла /bitrix/.settings.php,
      • init.php — дополнительные параметры портала,
      • after_connect.php — подключается сразу же после создания соединения с базой,
      • dbconn_error.php — подключается при ошибке в момент создания соединения с базой,
      • dbquery_error.php — подключается при ошибке в момент выполнения SQL запроса,
      • /ID сайта/init.php — дополнительные параметры сайта; файл подключается сразу же после определения специальной константы c идентификатором сайта — SITE_ID,
    • /templates/ — каталог с шаблонами сайтов и компонентов , в него входят следующие подкаталоги:
      • /.default/ — подкаталог с общими файлами, используемыми тем или иным шаблоном по умолчанию, структура данного каталога аналогична нижеописанной структуре каталога содержащего конкретный шаблон,
      • /ID шаблона сайта/ — подкаталог с шаблоном сайта, в него входят следующие подкаталоги и файлы:
        • /components/ — каталог с кастомизированными шаблонами компонентов,
        • /lang/ — языковые файлы принадлежащие как данному шаблону в целом, так и отдельным компонентам,
        • /images/ — каталог с изображениями данного шаблона,
        • /page_templates/ — каталог с шаблонами страниц и их описанием хранящимся в файле .content.php. Когда пользователь создает новую страницу, он может выбрать, по какому шаблону из представленных в этом каталоге это будет сделано,
        • header.php — пролог данного шаблона,
        • footer.php — эпилог данного шаблона,
        • template_styles.css — основной файл стилей для шаблона,
        • styles.css — CSS стили шаблона для визуального редактора (вкладка Стили сайта),
    • /tools/ — при инсталляции в этот каталог копируются дополнительные страницы, которые могут быть непосредственно использованы на любых страницах сайта: помощь, календарь, показ изображения и т.п.;
    • /updates/ — каталог, автоматически создаваемый системой обновлений;
    • .settings.php — [ds]файл настроек[/ds][di]Bitrix Framework имеет ряд специфичных настроек ядра, которые не имеют визуального интерфейса редактирования. Этот подход вызван тем, что изменение настроек или ошибка в них легко могут привести к неработоспособности системы (настройки подключения к базе данных, настройки кеширования и т.д.).

      Подробнее …[/di] ядра D7;

    • header.php — стандартный файл, подключающий в свою очередь конкретный пролог текущего шаблона сайта; данный файл должен использоваться на всех страницах публичной части;
    • footer.php — стандартный файл, подключающий в свою очередь конкретный эпилог текущего шаблона сайта; данный файл должен использоваться на всех страницах публичной части;
    • license_key.php — файл с лицензионным ключом;
    • spread.php — файл используемый главным модулем для переноса [ds]cookie (куков)[/ds][di];
      Cookie — это текстовая строка информации, которую веб-сервер передает в браузер посетителя сайта и которая сохраняется в файле на устройстве посетителя сайта. Как правило, используется для определения уникальности посетителя, времени его последнего визита, личных настроек, уникального идентификатора корзины покупок и т.д.

      Подробнее…[/di] посетителя на дополнительные домены различных сайтов;

    • .access.php — файл формируется динамически правами доступа;
    • routing_index.php — входная точка нового [ds]роутинга[/ds][di]Для запуска новой системы роутинга нужно перенаправить обработку 404 ошибок на файл routing_index.php в файле .htaccess:

      Подробнее …[/di];

    • virtual_file_system.php — поддержка кириллицы в системе;
    • и другие служебные файлы и папки.

    В зависимости от используемой редакции некоторые каталоги и файлы могут отсутствовать либо добавлены теми или иными [dw]модулями[/dw][di]Например:
    redirect.php — файл используемый модулем Статистика для фиксации событий перехода по ссылке;
    rk.php — файл по умолчанию используемый модулем Реклама для фиксации событий клика по баннеру;
    stop_redirect.php — файл используемый модулем Статистика для выдачи какого либо сообщения посетителю, попавшему в стоп-лист;
    activity_limit.php — файл используемый модулем Статистика для выдачи сообщения роботу при превышении им лимита активности;
    и другие.
    [/di].

    Настройка инфоблоков

    Вывод динамичной информации из базы данных в Bitrix Framework осуществляется в основном с помощью информационных блоков. Создавая сайт необходимо продумать структуру информационных блоков. Рассмотрим пример простого использования информационного блока на примере каталога.

    Схема каталога товаров которую необходимо построить на сайте:

    • Группа 1
      • Группа 1.1
        • Свой фильтр по свойствам
      • Группа 1.2
        • Свой фильтр по свойствам

    Возможные способы реализации

    Первый способ. Все товары в одном инфоблоке. Информационный блок расположен на первом уровне (Группа 1).

    Плюсы:

    • иерархия, которой можно управлять из 1С;
    • легко управляемая структура каталогов в рамках сайта;

    Минусы:

    • сложности со свойствами товаров, если товары разнородные;
    • свойства будут храниться в одной таблице, что плохо повлияет на производительность.

    Второй способ. Товары размещены в нескольких инфоблоках. Информационные блоки расположены на втором уровне (Группа 1.1; Группа 1.2 и так далее).

    Плюсы:

    • индивидуальный фильтр с возможностью хранения свойств в различных таблицах;
    • списки сравнения будут сразу разбиты по типам товаров и соответствующим свойствам;

    Минусы:

    • дополнительные усилия по настройке импорта из 1С: в настройках выгрузки необходимо указывать, какие разделы привязываются к какому инфоблоку;
    • дополнительные усилия по созданию структуры сайта: необходимо вручную создать нужные подразделы, а в них на нужном уровне на страницах расположить простые или комплексные компоненты каталога для соответствующих инфоблоков;

    После выбора схемы реализации нужно создать тип информационного блока, собственно информационный блок, задать его свойства и наполнить контентом через импорт (csv, xml, 1C) или вручную.

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

    Список ссылок по теме:

    • Инфоблоки в курсе Контент-менеджер
    • Пример создания каталога товаров в курсе Администратор. Бизнес.

    Инфоблоки

    Информационные блоки — модуль, позволяющий каталогизировать и управлять различными типами (блоками) однородной информации. С помощью информационных блоков может быть реализована публикация различных типов динамической информации: каталоги товаров, блоки новостей, справочники и т.д.

    Информационные блоки — ключевой момент Bitrix Framework. Практически всё, что делается в системе в той или иной мере завязано на этот модуль, даже если это и не отображается явно.

    Информационные блоки представляют собой очередной уровень абстракции над обычными таблицами СУБД, своеобразная «база данных в базе данных». Поэтому к ним частично применимы все те правила, которых придерживаются при проектировании БД.

    Инфоблоки — сущность, которая в физической структуре БД создает 4 таблицы, не меняющиеся при изменении структуры данных: типы объектов, экземпляры объектов, свойства объектов и значения свойств объектов.

    Плюсы такого подхода:

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

    Минусы такого подхода:

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

    Особенности упорядочивания элементов по разделам

    Упорядочивание элементов инфоблоков по разделам может быть очень удобным для навигации по инфоблоку в административном интерфейсе. Фасетное упорядочивание делает навигацию ещё более удобной.

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

    Особенности работы заключаются в некотором неудобстве работать отдельно именно с разделами. Так, например, если помимо инфоблока Статьи существует инфоблок Книги, то велика вероятность, что его элементы также будут нуждаться в классификации по дате публикации и по тематикам. В этом случае придётся ещё раз создавать такую же структуру разделов. Также весьма непросто будет вывести, например, список всех материалов (статей и книг) по одной тематике, упорядочив их по дате публикации. Также сложно будет вывести общий рубрикатор тематик в меню сайта.

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

    Интеграция ORM в информационных блоках

    С версии 19.0.0 модуля iblock добавлена поддержка ORM при работе с элементами инфоблоков.

    Список ссылок по теме:

    • Обсуждение на партнерском форуме Архитектура в «больших» магазинах
    • Статья Информационная архитектура сайта
    • Документация для разработчиков по модулю «Информационные блоки»

    Работа с инфоблоками штатными средствами

    Порядок работы

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

    • Внимательное продумывание структуры инфоблоков.
    • Создание нужного типа инфоблоков с настройкой параметров.
    • Создание самих инфоблоков с настройкой параметров.
    • Создание структуры внутри инфоблока.
    • Создание элементов инфоблока.
    • Создание физической страницы (в случае использования комплексного компонента) или страниц (при использовании простых компонентов) и размещение на ней компонента (компонентов) с последующей настройкой его свойств.
    • Кастомизация работы компонента под потребности ТЗ и дизайна сайта (кастомизация шаблона компонента, использование файлов result_modifier.php или component_epilog.php, кастомизация собственно компонента).
    • Настройка отображения данных инфоблока в административной части под нужды обычного пользователя. Как показывает практика, почти никто из Контент-менеджеров не желает ознакомиться с этой возможностью и не умеет перенастраивать отображение структуры инфоблоков под себя.

    Штатные возможности

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

    Советы от веб-разработчиков.

    Максим Месилов: Для важных ИБ, которыми управляют несколько людей, желательно включать журналирование. Так вы сможете быстро найти концы в случае непонятного удаления элементов или их редактирования.

    Настройки журналирования — вкладка в настройках ИБ.

    Свойства инфоблоков

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

    • Строка — значение свойства задается в виде текстовой строки;
    • Число — значение свойства задается в виде числа;
    • Список — значение свойства выбирается из списка;
    • Файл — в качестве значения свойства используется файл;
    • Привязка к разделам — с помощью данного свойства можно задать связь между элементом данного инфоблока и разделами другого информационного блока;
    • Привязка к элементам — задание связи между элементами информационных блоков «поштучно»;
    • HTML/текст — значение свойства задается в виде текста с HTML-тегами;
    • Привязка к элементам по XML_ID — привязка хранится как строка и значением является XML_ID привязанного элемента;
    • Привязка к карте Google Maps — задается связь между элементом инфоблока и компонентом [dw]Google Map[/dw][di]для использования компонентов Google необходимо иметь ключ доступа. Инструкция по получению ключа находится в этом уроке.[/di]
    • Справочник — задается связь между элементом инфоблока и highload блоками;
    • Привязка к Яндекс.Карте — задается связь между элементом инфоблока и компонентом Яндекс.Карта;
    • Счетчик — аналог autoincrement для БД. При добавлении элемента инфоблока значение будет больше на единицу, чем последнее. Стартовое значение задается произвольно. Можно использовать для журналов учета входящих документов и т.п., где должна быть непрерывная нумерация документов.
    • Привязка к пользователю — с помощью данного свойства можно задать связь между элементом данного инфоблока и пользователями системы;
    • Дата/Время — значение свойства задается в виде даты/времени;
    • Видео — задается связь между элементом списка и медиафайлом;
    • Привязка к элементам в виде списка — задание связи между элементами списком;
    • Привязка к теме форума — с помощью данного свойства можно задать связь между элементом данного инфоблока и темами форума;
    • Привязка к файлу (на сервере) — с помощью данного свойства можно задать связь между элементом инфоблока и файлом на удаленном сервере;
    • Привязка к элементам с автозаполнением — задается связь с элементами с автозаполнением;
    • Привязка к разделам с автозаполнением — задается связь с разделами с автозаполнением;
    • Привязка к товарам (SKU) — задается связь с товарными предложениями (SKU).

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

    Свойства разделов инфоблока

    Имеется возможность задавать пользовательские свойства для разделов инфоблоков. Пользовательские поля в своем коде должны обязательно иметь приставку UF_. Список типов полей несколько меньше, чем для самого инфоблока:

    • Видео;
    • Привязка к элементам highload-блоков;
    • Строка;
    • Целое число;
    • Число;
    • Дата со временем;
    • Дата;
    • Да/Нет;
    • Файл;
    • Список;
    • Привязка к разделам инф. блоков;
    • Привязка к элементам инф. блоков;
    • Опрос;
    • Шаблон.

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

    Экспорт-импорт

    Добавление большого числа элементов инфоблоков вручную — очень трудоемкое занятие. С целью облегчения добавления информации можно применять
    импорт/экспорт данных с использованием разных форматов файлов. Поддерживаются форматы:

    • RSS
    • CSV
    • XML

    Экспорт и импорт в формате RSS организуются с помощью специальных компонентов RSS новости (экспорт) (bitrix:rss.out) и RSS новости (импорт) (bitrix:rss.show) соответственно.

    Экспорт данных из инфоблока в CSV файл выполняется с помощью формы Выгрузка информационного блока (Контент > Информационные блоки > Экспорт > CSV). Импорт данных, хранящихся в отдельном CSV файле, в информационный блок выполняется в форме Загрузка информационного блока (Контент > Информационные блоки > Импорт > CSV).

    Примечание: начиная с версии модуля 14.0.5, уровень глубины вложенности разделов для CSV-экспорта/импорта определяется настройками модуля Информационные блоки.

    В более ранних версиях экспорт в CSV был ограничен тремя уровнями вложенности и, если была необходимость увеличить уровень вложенности, то приходилось менять значение у переменной $NUM_CATALOG_LEVELS в файле bitrixmodulesiblockadmindata_import.php. Такая операция являлась изменением ядра продукта, пользоваться ею можно было только в крайнем случае, т.к. при последующем обновлении это изменение удалялось из кода.

    Примечание: если нужно осуществить экспорт инфоблока как торгового каталога, то необходимо воспользоваться путем Магазин > Настройки > Экспорт данных. Возможен и импорт из файла формата CSV: в качестве торгового каталога. В этом случае необходимо воспользоваться путем Магазин > Настройки > Импорт данных.

    Функционал экспортаимпорта инфоблоков в формат XML позволяет переносить не только содержимое инфоблоков, но и их свойства и изображения. Экспорт производится на странице Экспорт XML (Контент > Информ. блоки > Экспорт > XML). Импорт осуществляется на странице Импорт XML (Контент > Информ. блоки > Импорт > XML).

    Настройка форм

    Добавление/редактирование информационных блоков возможно как с административной, так и с публичной части. С публичной части это осуществляют контент-менеджеры. Формы добавленияредактирования инфоблоков желательно кастомизировать. В этом случае работа контент-менеджеров станет более легкой и удобной. Функция настройки форм — штатная и не требует программирования. Система позволяет:

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

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

    Типы хранения инфоблоков

    При создании информационных блоков рекомендуется хранить свойства инфоблока в отдельной таблице, причем все значения свойств одного элемента хранятся в одной строке. Эта технология называется Инфоблоки 2.0 и позволяет существенно ускорить работу системы, а также снять ряд ограничений в предыдущей версии инфоблоков. Например, теперь нет необходимости в дополнительном запросе CIBlockElement::GetProperty при выборе значений свойств функцией CIBlockElement::GetList.

    Возможности инфоблоков 2.0:

    • При выборке элементов можно сразу получать значения свойств, т.к. количество присоединяемых таблиц в запросе не увеличивается с каждым свойством, а всегда равно единице.
    • Фильтрация по значениям свойств происходит аналогично инфоблокам 1.0 (за исключением множественных).
    • Выборка значений множественных свойств не приводит к декартовому произведению результата запроса — значения свойств передаются в виде массива.
    • Для комбинированных фильтров по немножественным (единичным) свойствам появилась возможность ручного создания составных индексов БД для ускорения операций выборки.
    • Для инфоблоков 2.0 нет возможности «сквозной» выборки элементов, когда в фильтре указывается тип инфоблока и символьный код свойства. В фильтре необходимо указывать IBLOCK_ID.

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

    Связь между инфоблоками

    Bitrix Framework допускает создание взаимосвязей между информационными блоками с помощью свойств типа Привязка к элементам, Привязка к разделам, Привязка к элементам в виде списка, Привязка к элементам с автозаполнением, Привязка к разделам с автозаполнением и Привязка к товарам (SKU).

    Кеширование

    Цитатник веб-разработчиков.

    Антон Долганин: На данный момент кеширование Битрикса фактически совершенно, и не стоит изобретать своих велосипедов.

    Производительность

    При большом объеме базы данных может возникнуть проблема производительности. Связано это со следующими причинами:

    • обращения к этому массиву информации на чтение или на запись порождают конкурентные запросы;
    • запросы сами по себе быстрые, но их такое число, что БД начинает выстраивать из них очередь;
    • запросы медленные и тяжёлые, и к тому же очень частые.

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

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

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

    Кеширование

    Кеширование — технология, позволяющая сохранять результаты работы редко обновляемых и ресурсоемких кусков кода (например, активно работающих с базой данных) в специальном хранилище для более быстрого доступа к ним.

    Для реализации этого созданы классы:

    • Cache — финальный класс для кеширования PHP переменных и HTML результата выполнения скрипта.
    • В старом ядре есть классы:

    • CPageCache — класс для кеширования HTML результата выполнения скрипта;
    • CPHPCache — класс для кеширования PHP переменных и HTML результата выполнения скрипта.

    Система Bitrix Framework включают в себя разные технологии кеширования:

    • Кеширование компонентов (или Автокеширование) — все динамические компоненты, которые используются для создания веб-страниц, имеют встроенную поддержку управления кешированием.

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

    • Неуправляемое кеширование — возможность задать правила кеширования ресурсоемких частей страниц. Результаты кеширования сохраняются в виде файлов в каталоге /bitrix/cache/. Если время кеширования не истекло, то вместо ресурсоемкого кода будет подключен предварительно созданный файл кеша.

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

      Правильное использование кеширования позволяет увеличить общую производительность сайта на порядок. Однако необходимо учитывать, что неразумное использование кеширования может привести к серьезному увеличению размера каталога /bitrix/cache/.

    • Управляемый кеш — автоматически обновляет кеш компонентов при изменении данных. Для часто обновляемого большого массива данных использование управляемого кеша неоправданно.
    • HTML кеш лучше всего включить на какой-нибудь редко изменяющийся раздел с регулярным посещением анонимных посетителей. Технология проста в эксплуатации, не требует от пользователя отслеживать изменения, защищает дисковой квотой от накрутки данных и самовосстанавливает работоспособность при превышении квоты или изменении данных. (Считается устаревшей, рекомендуется использовать технологию Композитный сайт.) В плане многосайтовости поддерживает только многосайтовость на одном домене.
    • Кеширование меню. Для кеширования меню применяется специальный алгоритм, который учитывает тот факт, что большая часть посетителей — это незарегистрированные пользователи.

      Кеш меню управляемый и обновляется при редактировании меню или изменении прав доступа к файлам и папкам через административный интерфейс и API.

    Основные настройки кеширования расположены на странице Настройки кеширования (Настройки > Настройки продукта > Автокеширование).

    Примечание: В ядре D7 настройки кеширования производятся в специальном файле.

    Список ссылок по теме:

    • Настройки кеширования в курсе Администратор. Базовый.
    • Класс CPHPCache в Документации для разработчика
    • Класс CPageCache в Документации для разработчика
    • Класс Cache в Документации D7

    Кеширование компонентов (Автокеширование)

    Цитатник разработчиков.

    Денис Шаромов: Периодически приходится наблюдать картину: хостер отключает аккаунт за большую нагрузку на сервер, клиент обращается к нам в техподдержку, мы видим, что кеширование компонентов не используется. Клиент объясняет это так: «мне сказали ваши партнёры-разработчики компонентов, что для этого компонента нельзя включать кеширование т.к. оно в битриксе криво работает».

    На самом деле проблема в неумении работать с кешированием.

    Одним из видов кеширования в Bitrix Framework является кеширование компонентов.

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

    Все динамические компоненты, которые используются для создания веб-страниц, имеют встроенную поддержку управления кешированием. Для использования технологии достаточно включить автокеширование одной кнопкой на административной панели. Это удобно в [dw]большинстве случаев[/dw][di]Возможны ситуации, когда как раз при разработке можно по ошибке «заставить» страницу с одним или несколькими компонентами работать по-разному с кэшированием или без него.[/di] использовать на этапе разработки, когда автокеширование можно выключить, что облегчит работу, а перед сдачей проекта снова включить. При этом все компоненты, у которых в настройках был включен режим автокеширования, создадут кеши и полностью перейдут в режим работы без запросов к базе данных.

    Внимание! При использовании режима Автокеширования, обновление информации, выводимой компонентами, происходит в соответствии с параметрами отдельных компонентов.

    Управление автокешированием располагается на закладке Кеширование компонентов (Настройки > Настройки продукта > Автокеширование):

    Примечание: При включении режима автокеширования компонентов, компоненты с настройкой кеширования Авто + Управляемое будут переведены в режим работы с кешированием.

    Чтобы обновить содержимое закешированных объектов на странице, вы можете:

    1. Перейти на нужную страницу и обновить ее содержимое, используя кнопку Сбросить кеш на панели инструментов.
    2. В режиме Правки сайта использовать кнопки для очистки кеша в панели отдельных компонентов.
    3. Использовать автоматический сброс кеша по истечении времени кеширования, для чего в настройках компонента выбрать режим кеширования Кешировать или Авто + Управляемое.
    4. Использовать автоматический сброс кеша при изменении данных, для чего в настройках компонента выбрать режим кеширования Авто + Управляемое.
    5. Перейти к настройкам выбранных компонентов и перевести их в режим работы без кеширования.

    Добавление произвольного PHP кода

    Внимание! PHP-код непосредственно в теле страницы не рекомендуется!
    Использование php-кода в теле страницы считается дурным тоном при разработке сайта и говорит о низкой квалификации разработчика.

    В рабочую область страницы также может быть добавлен произвольный PHP-код, который в режиме редактирования в визуальном редакторе будет отображаться в виде значка . Если установить курсор на этот значок, то в панели Свойства отобразится непосредственно сам код:

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

    Список ссылок по теме:

    • Работа с PHP скриптом в курсе Администратор. Базовый.

    Middle, Средний уровень подготовки

    Вы уже можете работать со штатным функционалом. Теперь надо овладеть API и другими возможностями системы. К концу изучения главы вы должны уметь:

    • Обновлять систему.
    • Делать бекап и уметь восстанавливать сайт.
    • Настраивать систему через файл /bitrix/.settings.php.
    • Работать с компонентами: кастомизация и кеширование.
    • Работать с инфоблоками штатными методами и с помощью API.
    • Работать с агентами и событиями.
    • Уметь пользоваться отложенными функциями.
    • Уметь управлять SEF.
    • Уметь работать с ORM.
    • Знать и использовать Пользовательские поля
    • Понимать как можно тестировать готовые проекты.

    Обновление, бекап и восстановление

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

    • Обновление системы. Обновление системы необходимо для получения нового функционала, созданного вендором и для закрытия уязвимостей и обнаруженных багов.
    • Резервное копирование. Наличие бекапа — не просто «хороший тон» в работе. Это и гарантия того, что вам не придётся всё делать заново, с нуля.
    • Перенос сайта на хостинг. После завершения разработки необходимо перенести сайт с локальной установки на выбранный клиентом хостинг.

    Ядро D7

    Ядро D7

    Цитатник веб-разработчиков.

    Антон Долганин: D7 упрощает работу, значительно. Можно забыть про богомерзкое копирование осточертевших getlist из модуля в модуль. В общем, все низкоуровневое убрали под капот — круто, спасибо.

    Принцип совместимости, от которого компания «1С-Битрикс» не имеет права отказаться, обязывал выполнять большой объём работ, не направленных непосредственно на развитие Bitrix Framework. Это прямо влияло на скорость и качество разработки самой платформы, и косвенно влияло на распространение продуктов компании на рынке.

    D7 — основное программное ядро Bitrix Framework. Создано взамен начального на новом технологическом уровне с избавлением от «наслоений» устаревших технологий.

    Ядро D7 является основным, однако не весь функционал старого ядра на данный момент перенесён в него. В продукте продолжает работать весь старый API. И добавляется новый API D7. Постепенно, старый API должен стать чем-то типа адаптера, для совместимости. А вся логика с соответствующим рефакторингом должна переехать в D7.

    Внимание! Перед началом разработки убедитесь что в выбранном вами модуле есть классы и методы нового ядра.

    Основные отличия D7 от старого ядра

    • Базы данных
      • Поддерживаются базы данных: MySQL, [dw]MS SQL, Oracle, NoSQL[/dw][di]С 1 января 2017 года эти базы данных поддерживаются ограниченно: клиенты не могут скачивать обновления продукта платформы и воспользоваться возможностями новых версий продукта. [/di].
      • Отказ от неэффективного драйвера MSSQL ODBC, поддерживается только native драйвер.
      • Используется ORM (построитель запросов) c noSQL.
    • ООП
      • Сильная связанность. Весь код, относящийся к какой-то определённой области должен быть сосредоточен в одном месте, в одном классе, в одном наборе классов.
      • Компоненты с ООП (class.php) – возможность писать более структурированный код компонента и возможность наследования.
    • Разработка
      • Единообразный код. Все одинаковые вызовы называются одинаково, имеют одинаковые наборы параметров, возвращают унифицированные данные. То есть GetList пользователей не отличается от GetList’а групп пользователей.
      • Поддержка пространств имен.
      • Новые единые правила форматирования кода с жёстким контролем на уровне разработки.
      • Отказ от глобальных переменных
      • Поддержка exceptions.
      • Поддержка новых типов: дата, время, файлы. Неформатированные данные заменяются классами с методами. Значения таких типов являются объектами с методами форматирования и т.п.
      • Библиотека классов.
      • Унифицированные события. Возможность модификации и интеграции при помощи обработчиков.
      • Автозагрузка (autoload). Все сущности системы находятся в заранее определённых местах, соответственно, поддерживается автозагрузка без каких-то дополнительных действий со стороны разработчика.
      • Специализированные обработчики (классы, сущности) для разных ситуаций – типы приложений (http, cli).
      • Отложенная загрузка языковых файлов. Файлы из папкок /lang не подключаются одновременно с подключением компонента, они загружаются при первом запросе языковой фразы.
      • Провайдеры объектов для основных операций (кеш, лог).

    Настройка параметров ядра

    Особый файл

    Bitrix Framework имеет ряд специфичных настроек ядра, которые не имеют визуального интерфейса редактирования. Этот подход вызван тем, что изменение настроек или ошибка в них легко могут привести к неработоспособности системы (настройки подключения к базе данных, настройки кеширования и т.д.).

    Настройки в D7 выполняются в файле /bitrix/.settings.php. Напомним, что в старом ядре аналогичные настройки выполнялись в файле /bitrix/php_interface/dbconn.php. Файл .settings.php структурно сильно отличается от прежнего dbconn.php.

    Примечание: т.к. в системе параллельно используются 2 ядра — старое ядро и D7, то и оба файла настроек используются одновременно. Поэтому необходимо производить настройки обоих файлов.

    Даже если вы используете код только старого ядра, то файл .settings.php должен быть создан. Возможна ситуация, когда при установке обновлений какой-то из встроенных механизмов системы будет переписан на ядро D7. Если этот файл корректно не настроен, то это может привести к неработоспособности системы.

    Иногда бывают ситуации, что файл .settings.php отсутствует. Его можно создать в автоматическом режиме, если выполнить в [dw]командной строке[/dw][di]Командная PHP-строка — инструмент системы, позволяющий запускать произвольный код на PHP с вызовами функций. Подробнее…[/di]:
    BitrixMainConfigConfiguration::wnc();.

    Править параметры можно с помощью класса Configuration (BitrixMainConfigConfiguration).

    Примечание: Некоторые секции файла настроек содержат параметр readonly. Этот параметр означает, что данные настройки не будут изменены через API.

    Кроме этого настройки могут задаваться в файле .settings_extra.php. Базовый файл настроек содержит неизменные настройки, к которым есть API. Файл .settings_extra.php может содержать произвольный код, который меняет настройки динамически. Соответственно к нему нет API.

    Описание параметров

    Ниже описаны параметры, которые возможны для изменения:

  • Секция cache
  • Секция exception_handling
  • Секция connections
  • Корневая секция
  • Секция pull
  • Секция http_client_options
  • Секция services
  • Секция routing
  • Секция session
  • Ключ crypto_key
  • Секция smtp
  • Секция cache

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

    До версии 18.5.200 действовал такой формат записи:

    В версии 18.5.200 формат записи был изменён одновременно с возможностью использовать в кешировании Redis. Оба формата на данный момент работоспособны, но вендор настойчиво рекомендует использовать новый вариант записи.

    Примеры нового формата записи для разных способов кеширования.

    Параметр Значение
    type В качестве значения можно задать:

    • memcache
    • apc
    • xcache
    • files
    • [dw]redis[/dw][di]С версии 18.5.200.[/di]
    • none

    или указать массив со значениями:

    • class_name — класс, реализующий интерфейс ICacheEngine,
    • required_file — подключаемый файл с путем относительно папки /bitrix или /local (если требуется),
    • required_remote_file — подключаемый файл с абсолютным путем (если требуется),
    • extension — будет произведена попытка подключения расширения через extension_loaded. И только тогда подключится уже указанный класс.
    cache_flags Запрет на кеширование выборки или изменение ttl. Для этого установите ключи, куда входит название таблицы и суффиксы:

    'cache_flags'=>   array(
          'value'=> array(
             "b_group_max_ttl" => 200,
             "b_group_min_ttl" => 100,
          )
       ),

    Устанавливая b_group_max_ttl = 0, администратор запрещает кеширование этой сущности. Устанавливая b_group_min_ttl = 86400, админ расширяет наш TTL до суток (если в коде написано 3600).

    Примечание: Кроме type могут быть дополнительные параметры, если они нужны конкретному классу кеширования.

    Примечание: Настройки memcache могут задаваться так же в файле /bitrix/.settings_extra.php.

    Пример файла /bitrix/.settings_extra.php

    В базовом файле .settings.php содержатся неизменные настройки, к которым есть API. Файл .settings_extra.php может содержать произвольный код, который меняет настройки динамически в зависимости от каких-либо факторов. Соответственно для изменения настроек в этом файле нет API. Естественно в ходе выполнения этого произвольного кода должен быть возвращен массив подобной структуры базового файла.

    Секция exception_handling

    Отвечает за обработку ошибок.

      'exception_handling' => array (
        'value' => array (
          'debug' => false,
          'handled_errors_types' => E_ALL & ~E_NOTICE & ~E_STRICT & ~E_USER_NOTICE,
          'exception_errors_types' => E_ALL & ~E_NOTICE & ~E_WARNING & ~E_STRICT & ~E_USER_WARNING & ~E_USER_NOTICE & ~E_COMPILE_WARNING & ~E_DEPRECATED,
          'ignore_silence' => false,
          'assertion_throws_exception' => true,
          'assertion_error_type' => 256,
          'log' => array (
            'settings' => array (
              'file' => 'bitrix/modules/error.log',
              'log_size' => 1000000,
            ),
          ),
        ),
        'readonly' => false,
      ),
    Параметр Значение
    debug Ключ отвечает за то, будет ли выведена ошибка на страницу в браузере. Выводить ошибки рекомендуется только на время разработки или отладки. Иначе потенциально может быть разглашение информации.
    handled_errors_types В ключе задаются типы ошибок, которые система отлавливает (не игнорирует).
    exception_errors_types В ключе задаются типы ошибок, при которых система выбрасывает исключение.
    ignore_silence Ключ отменяет действие оператора управления ошибками (@).
    log В ключе задаются параметры логирования ошибок. Если ключа нет — логирования не будет. Если задать как показано в примере:

    'log' => array (
       'settings' => array (
          'file' => 'bitrix/modules/error.log',
          'log_size' => 1000000,
       ),
    ),

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

    Если задать в общем случае, то можно логировать куда угодно:

    'log' => array(
       'class_name' => 'MyLog', // custom log class, must extends ExceptionHandlerLog; 
                                // can be omited, in this case default DiagFileExceptionHandlerLog will be used
       'extension' => 'MyLogExt', // php extension, is used only with 'class_name'
       'required_file' => 'modules/mylog.module/mylog.php' // included file, is used only with 'class_name'
       'settings' => array( // any settings for 'class_name'
          ),
    ),

    В приведенном примере:

    • class_name — пользовательский класс, наследуемый от ExceptionHandlerLog. Может быть не указан. В этом случае будет использоваться BitrixMainDiagFileExceptionHandlerLog.
    • extension — расширение PHP, использовать можно только вместе с class_name.
    • required_file — включаемый файл. Используется только вместе с class_name.
    • settings — настройки для класса, указанного в class_name
    assertion_throws_exception Включение поддержки команды assert.
    assertion_error_type В ключе задаются типы ошибок, для которых не верный assert выбрасывает исключение.

    В handled_errors_types, exception_errors_types, assertion_error_type необходимо передать тип ошибки. Тип ошибки представляет из себя числовой код. Но коды знать и помнить не нужно. Например, параметр exception_errors_types. Что означает данная запись: E_ALL & ~E_NOTICE & ~E_WARNING & ~E_STRICT & ~E_USER_WARNING & ~E_USER_NOTICE & ~E_COMPILE_WARNING?

    Для начала обратимся к уровням ошибок интерпретатора PHP. Есть определенные значения и определенные константы, которые им соответствуют. В нашем же случае данная запись означает, что E_ALL (значение константы 2047), побитовое и не E_NOTICE, и не E_WARNING и не E_STRICT и не E_USER_WARNING и не E_USER_NOTICE и не E_COMPILE_WARNING. То есть E_ALL за исключением далее указанных констант, которые определяют тот или иной уровень ошибок интерпретатора PHP.

    Секция connections

    Внимание! С версии главного модуля 20.900.0 ядро продукта не использует параметры соединения БД из файла dbconn.php, настройки читаются только из .settings.php.

    Параметры соединения с базой данных и другими источниками данных. Задается имя класса и параметры соединения.

    'connections' => array (
            'value' => array (
                'default' => array (
                    'className' => '\Bitrix\Main\DB\MysqlConnection',
                    'host' => 'localhost:31006',
                    'database' => 'admin_bus',
                    'login' => 'admin_bus',
                    'password' => 'admin_bus',
                    'options' => 2,
                    'handlersocket' => array (
                        'read' => 'handlersocket',
                    ),
                ),
                'handlersocket' => array (
                    'className' => '\Bitrix\Main\Data\HsphpReadConnection',
                    'host' => 'localhost',
                    'port' => '9998',
                ),
            ),
            'readonly' => true,
        ),

    Внимание: Начиная с версии ядра 14.5.2 и выше возможно использование расширения mysqli.

    'className' => '\Bitrix\Main\DB\MysqliConnection',
    

    Также для этого в PHP должно быть установлено расширение mysqli, дополнительные проверки на наличие расширения не производятся! Включать mysqli нужно отдельно для старого (dbconn.php) и (.settings.php) ядра D7.

    Параметр Значение
    options Задаются флаги постоянного соединения и отложенности соединения с базой. Например:
    Connection::PERSISTENT == 1
    Connection::DEFERRED == 2
    Можно записывать их комбинации с помощью битовых операций. Например 3 — это и PERSISTENT, и DEFERRED.
    handlersocket В ключе указывается, какое соединение использовать для чтения (ключ read). Необходимо создать подключение, где будут указаны класс, хост и порт. В примере кода выше установлен параметр read, у которого указано значение handlersocket. А ниже собственно описание для [ds]соединения handlersocket[/ds][di]Традиционные ACID Базы данных в целом ряде задач затрудняют реализацию проектов. Для решения этих задач были предложены технологии NoSQL и HandlerSocket (в виде плагина к обычной MySQL).

    Подробнее…[/di].

    className имя класса, в которой собственно реализуется работа с конкретным типом БД.
    host имя хоста, где находится база данных. можно указать с портом
    database имя базы
    login логин пользователя базы данных
    password пароль пользователя базы данных

    Корневая секция

    В корневой секции размещаются настройки общего характера.

    Параметр Значение
    disable_iconv Запрещает использование библиотеки iconv. Аналог константы BX_ICONV_DISABLE в старом ядре.
    logger Логгеры, реализующие интерфейс PSR-3. Подробнее…

    Секция pull

    Эта секция реально нужна только для хостинг-партнеров (для автоматизации развертывания), для всех остальных рекомендуется использовать настройку через административный интерфейс.

    Пример кода секции и таблица параметров

    Секция http_client_options

    Секция задаёт опции по умолчанию для класса BitrixMainWebHttpClient.

    Параметр Значение
    redirect по умолчанию true, производить редирект
    redirectMax максимальное число таких редиректов (по умолчанию 5)
    waitResponse если true, то — ожидание ответа (по умолчанию), в ином случае — сразу возврат ответа
    socketTimeout время ожидания ответа в секундах (по умолчанию 30)
    streamTimeout таймаут потока в секундах (по умолчанию 60)
    version версия http — 1.0 или 1.1 (по умолчанию 1.0)
    proxyHost / proxyPort / proxyUser / proxyPassword группа параметров для установки прокси
    compress если true, будет послан Accept-Encoding: gzip
    charset кодировка для тела объекта (используется в поле заголовка запроса Content-Type для POST и PUT)
    disableSslVerification если true, верификация ssl-сертификатов производиться не будет

    Пример настройки:

      'http_client_options' =>
       array (
         'value' =>
            array (
             'redirect' => true,//делаем редиректы, если требуется
             'redirectMax' => 10,//но не более 10
             'version' => '1.1'//работаем по протоколу http 1.1
            ),
         'readonly' => false,
       ),

    Правильно ли вы указали настройки, можно проверить так:

    use BitrixMainConfigConfiguration;
    print_r(Configuration::getValue("http_client_options"));

    Должен быть выведен ваш массив.

    Секция services

    Секция предназначена для регистрации сервисов. Подробнее о настройках смотрите в уроке [ds]Сервис Локатор[/ds][di]Сервис локатор (локатор служб) — это шаблон проектирования для удобной работы с сервисами приложения. Идея сервиса в том, что вместо создания конкретных сервисов напрямую (с помощью new), используется специальный объект (сервис локатор), который будет отвечать за создание, нахождение сервисов.

    Подробнее …[/di]

    Секция routing

    Секция отвечает за подключение файлов с конфигурацией маршрутов [ds]роутинга[/ds][di]Доступно в модуле main начиная с версии 21.100.0.

    Подробнее…[/di], которые располагаются в папках /bitrix/routes/ и /local/routes/. Для подключения файлов опишите их:

    'routing' => ['value' => [
      'config' => ['web.php', 'api.php']
    ]], 
    
    // подключатся файлы:
    // /bitrix/routes/web.php, /local/routes/web.php,  
    // /bitrix/routes/api.php, /local/routes/api.php
    

    Секция session

    Ядро поддерживает четыре варианта для хранения (файлы, redis, database, memcache) данных сессии. Способ хранения описывается в секции session. Подробнее.

    Ключ crypto_key

    Чтобы ядро могло шифровать данные необходимо указать в настройках /bitrix/.settings.php ключ crypto_key. Подробнее…

    Секция smtp

    С версии 21.900.0 модуля main в файле /bitrix/.settings.php можно использовать опцию smtp, которая позволяет [ds]включить[/ds][di]
    Чтобы в продуктах 1С-Битрикс: Управление сайтом и коробочных версиях Битрикс24 включить возможность использования SMTP-сервера отправителя, отредактируйте файл /bitrix/.settings.php, добавив следующий код:

    Подробнее…[/di] возможность использования SMTP-сервера отправителя и организовать разделение потоков отправки писем.

    Список ссылок по теме:

    • Пример скрипта для редактирования настроек. (блог)

    Подключения к Redis, Memcache

    Для создания подключения в файле настроек bitrix/.settings.php, необходимо добавить в секцию connections именованное подключение.

    Redis

    Убедитесь, что у вас установлено расширение Redis для работы через PHP.

    Обычное подключение:

     'connections' => [
        'value' => [
          'default' => [
            'className' => BitrixMainDBMysqliConnection::class,
            //... настройки существующего подключения в БД
          ],
          'custom.redis' => [
            'className' => BitrixMainDataRedisConnection::class,
            'port' => 6379,
            'host' => '127.0.0.1',
            'serializer' => Redis::SERIALIZER_IGBINARY,
          ],
          'custom2.redis' => [
            'className' => BitrixMainDataRedisConnection::class,
            'port' => 6379,
            'host' => '127.0.0.4',
            'serializer' => Redis::SERIALIZER_IGBINARY,
          ],
        ],
        'readonly' => true,
      ]

    Про настройки вариантов serializer можно прочитать в официальной документации.

    Кластер

    Отличие от обычной конфигурации заключается лишь в servers дополнительных опциях: serializer, persistent, failover, timeout, read_timeout. Про них можно прочитать в официальной документации.

    Redis в режиме cluster может быть настроен двумя способами:

    1. Мультимастер кластер: N мастеров (и могут быть слейвы у каждого).
    2. Обычный кластер: 1 мастер и N слейвов

    Redis cluster в режиме мультимастер, указываются параметры всех мастеров:

    // bitrix/.settings.php
    return [
    //...        
        'session' => [
            'value' => [
                'mode' => 'default',
                'handlers' => [
                    'general' => [
                        'type' => 'redis',   
                        'servers' => [
                            [
                                'port' => 6379,
                                'host' => '127.0.0.1',
                            ],
                            [
                                'port' => 6379,
                                'host' => '127.0.0.2',
                            ],
                            [
                                'port' => 6379,
                                'host' => '127.0.0.3',
                            ],
                            'serializer' => Redis::SERIALIZER_IGBINARY,
                            'persistent' => false,
                            'failover' => RedisCluster::FAILOVER_DISTRIBUTE,
                            'timeout' => null,
                            'read_timeout' => null,
                        ],
                    ],           
                ],
            ]                   
        ] 
    ];

    Redis cluster в режиме 1 мастер + N слейвов. Указываются только параметры мастера блок с опциями опускается:

    return [
        'session' => [
            'value' => [
                'mode' => 'default',
                'handlers' => [
                    'general' => [
                        'type' => 'redis',
                        'servers' => [
                            [
                                'port' => '30015',
                                'host' => '127.0.0.1'
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ];

    Использование

    Чтобы получить экземпляр соединения, достаточно обратиться по имени соединения, используя метод BitrixMainApplication::getConnection.

    /** @var Redis $redisConnection **/
    $redisConnection = BitrixMainApplication::getConnection('custom.redis')->getResource();
    $redisConnection->setnx('foo', 'bar');

    Memcache

    Убедитесь, что у вас установлено расширение Memcache для работы через PHP.

    Обычное подключение

    'connections' => [
        'value' => [
          'default' => [
            'className' => BitrixMainDBMysqliConnection::class,
            //... настройки существующего подключения в БД
          ],
          'custom.memcache' => [
            'className' => BitrixMainDataMemcacheConnection::class,
            'port' => 11211,
            'host' => '127.0.0.1',
          ],
          'custom42.memcache' => [
            'className' => BitrixMainDataMemcacheConnection::class,
            'port' => 6379,
            'host' => '127.0.0.4',
          ],
        ],
        'readonly' => true,
      ]
    

    Кластер

    Если необходимо создать кластер из memcache серверов, то достаточно добавить настройку servers.

    'connections' => [
        'value' => [
          'default' => [
            'className' => BitrixMainDBMysqliConnection::class,
            //... настройки существующего подключения в БД
          ],
          'custom.memcache' => [
            'className' => BitrixMainDataMemcacheConnection::class,
            'servers' => [
                [
                    'port' => 11211,
                    'host' => '127.0.0.1',
                    'weight' => 1, //про настройку weight читайте внимательно в документации по memcahe 
                ],
                [
                    'port' => 11211,
                    'host' => '127.0.0.2',
                    'weight' => 1, //про настройку weight читайте внимательно в документации по memcahe
                ],
            ],
          ],
        ],
        'readonly' => true,
      ]

    Использование

    Чтобы получить экземпляр соединения, достаточно обратиться по имени соединения, используя метод BitrixMainApplication::getConnection.

    /** @var Memcache $memcacheConnection **/
    $memcacheConnection = BitrixMainApplication::getConnection('custom.memcache')->getResource();
    $memcacheConnection->set('foo', 'bar');

    Локальные настройки SMTP-сервера

    С версии 21.900.0 модуля main в продукты 1С-Битрикс: Управление сайтом и коробочные версии Битрикс24 добавлена новая опция smtp, с помощью которой можно организовать разделение потоков отправки писем.

       Теоретическая часть

    Как письма уходят с сайта/портала

    В продуктах 1С-Битрикс: Управление сайтом и коробочных версиях Битрикс24 вызывается глобальная функция bxmail, которая вызывает стандартную функцию mail языка PHP, оборачивая письмо заголовками. Далее эта функция mail обращается к Вашей внутренней инфраструктуре (в зависимости от того, как Вы [ds]её настроили[/ds][di] Работа через Bitrix Framework требует настройки отправки и приема электронной почты. Возможны три варианта отправки:

    через локальный sendmail или postfix (если сайт на Linux);

    через внешний SMTP-сервер без авторизации (если на Windows);

    через внешний сервер с авторизацией путем замены функции отправки почты.

    Подробнее…[/di]: postfix, sendmail или какое-то собственное решение, работающее на очередях – все письма отправляются через функцию mail).

    Все письма (рассылки, письма CRM, одиночные письма, письма для восстановления пароля, роботы и т.д.) уходят в один поток.

    С помощью новых локальных настроек SMTP-сервера можно настроить разделение потоков нужным Вам образом.

    Преимущества использования SMTP-сервера отправителя

    • разделение потоков;
    • использование [dw]алиасов[/dw][di] Алиасы — это дополнительные имена почтовых ящиков. С их помощью можно присвоить одному почтовому ящику на Вашем домене дополнительные имена. Например, у Вас есть почтовый ящик mysite@example.com. Можно добавить алиас marketing<mysite@example.com> для отдела маркетинга. После этого пользователи, получившие письмо от отдела маркетинга, в качестве отправителя увидят просто marketing.

      Если пользователи ответят на это письмо, то их ответ попадёт на реальный ящик mysite@example.com. [/di] (alias);

    • простота настройки;
    • возможность отладки;
    • возможность отправки на хитах;
    • возможность держать подключение открытым (полезно для массовых рассылок).

      Включение SMTP-сервера

    Чтобы в продуктах 1С-Битрикс: Управление сайтом и коробочных версиях Битрикс24 включить возможность использования SMTP-сервера отправителя, отредактируйте файл [ds]/bitrix/.settings.php[/ds][di] Bitrix Framework имеет ряд специфичных настроек ядра, которые не имеют визуального интерфейса редактирования. Этот подход вызван тем, что изменение настроек или ошибка в них легко могут привести к неработоспособности системы (настройки подключения к базе данных, настройки кеширования и т.д.).

    Подробнее…[/di], добавив секцию smtp со следующим кодом:

     'smtp' =>
        array (
            'value' =>
            array(
                'enabled' => true,
                'debug' => true, //optional
                'log_file' => '/var/mailer.log', //optional
            ),
        ),
    

    Напоминаем, что коды всех секций файла .settings.php должны располагаться внутри одного (основного) массива настроек.

    Важно! При редактировании файла .settings.php будьте внимательны: ошибка может привести к неработоспособности системы.

    Параметры настройки:

    • enabled – включение возможности использования SMTP-сервера отправителя;
    • debug – необязательный параметр (по умолчанию выключен). Включите его, если хотите увидеть полностью весь процесс взаимодействия с SMTP-серверами;
    • log_file – необязательный параметр. Можно указать файл для сбора логов, прописав полный путь до файла (главное, чтобы к этому файлу был доступ). По умолчанию в директории проекта/сайта уже создан файл mailer.log, куда будут записываться все логи.

      Настройка SMTP-подключения

    SMTP-подключение можно настроить двумя способами:

    • При создании нового почтового сообщения [dw]добавить отправителя[/dw][di]
      [/di], в открывшейся форме кликнуть по ссылке [dw]SMTP-сервер[/dw][di]
      [/di] и заполнить появившиеся поля настройки подключения SMTP-сервера:

      • введите имя и email;
      • определите, кто сможет видеть данного отправителя письма, использующего этот SMTP-сервер: только Вы или же все пользователи;

        Примечание: Каждому сотруднику можно подключить свой SMTP-сервер.

      • укажите почтовый сервер;
      • пропишите порт (в основном используются три значения: 25, 465, 587);
      • укажите [dw]ограничение[/dw][di] У разных почтовых серверов (Яндекс, Google, Mail и т.д.) есть разные ограничения на количество отправленных писем (чаще всего это ограничение на число отправленных писем за одни сутки). Если использовать эту опцию, то система 1С-Битрикс сама будет вести статистику числа отправленных писем и выдавать ошибку, если лимит писем исчерпан. [/di] отправки писем;
      • введите логин и пароль подключаемого SMTP-сервера.
    • Аналогичную форму подключения SMTP-сервера можно заполнить и в административном разделе сайта на странице Настройки > Настройки продукта > Почтовые и СМС события > Настройки SMTP, кликнув на кнопку Добавить SMTP-подключение:

      Нажмите на рисунок, чтобы увеличить

      Примечание: На текущий момент при добавлении SMTP-подключения через административный раздел сайта нельзя указать лимиты отправки писем.

    Обратите внимание! Отправка писем от имени и адреса, указанного при настройке локального SMTP-сервера, возможна только в тех случаях, когда в письме можно выбрать отправителя. Например, при создании Рассылок в Маркетинг > E-mail-маркетинг > [dw]Рассылки[/dw][di]local_smtp.png[/di], отправке писем из CRM или из раздела Почта корпоративного портала.

    Cистемные письма из «Битрикс24» или «1С-Битрикс: Управление сайтом» отправляются через .msmtprc.

    Связаны ли описанные в уроке настройки с настройками SMTP в виртуальной машине?

    Описанные в данном уроке настройки SMTP-серверов не связаны с настройками [ds]SMTP виртуальной машины BitrixVM[/ds][di] Для настройки SMTP-клиента выполните следующее:

    1. Перейти в главном меню в 6. Configure pool sites > 4. Change e-mail settings on site и ввести имя хоста, для которого нужно настроить отправку почты

    Подробнее…[/di].

    Пример: Допустим, у сайта на BitrixVM настроена почта Gmail [ds]через меню виртуальной машины[/ds][di] В уроке представлены настройки некоторых почтовых сервисов в виртуальной машине BitrixVM.

    Подробнее…[/di]. Добавим новое SMTP-подключение к Mail.ru через административный раздел сайта. Теперь при создании нового письма или рассылки в поле От кого можно выбрать отправителей двух почтовых сервисов: Gmail и Mail.ru. В зависимости от этого выбора почта будет отправлена либо средствами виртуальной машины, либо средствами ядра.

    Пространства имён

    Пространства имён

    Понятие [ds]пространств имен[/ds][di]Пространство имён (англ. namespace) — некоторое множество, под которым подразумевается модель, абстрактное хранилище или окружение, созданное для логической группировки уникальных идентификаторов (то есть имён).

    Подробнее…[/di] позволяет давать элементам системы более четкие имена, избавиться от множества префиксов имен, а также избежать потенциальных конфликтов. Все классы, поставляемые в стандартном дистрибутиве, должны находиться в пространстве имен Bitrix, которое не пересекается ни с PHP, ни с разработками партнёров. Каждый стандартный модуль определяет в пространстве имен Bitrix свое подпространство, совпадающее с именем модуля. Например, для модуля forum пространством имен будет BitrixForum, а для модуля mainBitrixMain.

    Примечание: Для классов партнеров namespace может быть таким:

    namespace AsdMetrika;  
     
    class CountersTable extends EntityDataManager  
    {  
       ....

    Это значит, что данный класс (в /lib/) принадлежит модулю asd.metrika и к нему (после подключения указанного модуля) можно обращаться так:

    AsdMetrikaCountersTable::update();

    Сам класс лежит в файле asd.metrika/lib/counters.php.

    При необходимости модуль может организовывать подпространства внутри своего пространства имен. Например, BitrixMainIO, BitrixForumSomeNameSomeNameTwo. Но такой возможностью следует пользоваться только если это оправдано для организации правильной архитектуры данного модуля.

    Правила наименования

    • Пространства имен должны именоваться «ВерхнимКэмелКейсом».
    • Не могут содержать в названии никаких символов, кроме букв латинского алфавита.
    • Название класса должно быть существительным. Надо стараться избегать ненужных сокращений и аббревиатур.

    Примеры:

    namespace BitrixMainLocalization;
    namespace BitrixMainEntityValidator;

    Сокращения, не являющиеся общепринятыми (в Bitrix Framework), использовать нельзя.

    Внимание! В тексте примеров на страницах курсов, как правило, будут отсутствовать упоминания о пространствах имён. Это делает текст читабельнее и проще для восприятия. Перед использованием примеров из документации в ваших проектах необходимо добавить неймспейс.

    Это можно сделать:

    • Используя PHPdoc
    • Используя IDE
    • кроме того, из контекста документации, как правило, понятно о каком классе идет речь.

    Допустимо сокращение полной записи. Вместо BitrixMainClass::Function() можно писать MainClass::Function().

    Также допустимо и использование синонимов, вместо длинных пространств имен. Для этого необходимо использовать use. Например, мы имеем длинную конструкцию:

    BitrixMainLocalizationLoc::getMessage("NAME");

    Чтобы сократить ее, объявим в начале файла синоним и далее уже будем использовать сокращенный вариант вызова:

    use BitrixMainLocalizationLoc;
    ...
    Loc::getMessage("NAME");

    Список ссылок по теме:

    • PHP Namespace.
    • PHP: пространства имен (документация PHP).

    Исключения

    Исключения

    В D7 используется механизм исключений (exceptions).

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

    Примеры

    Если пользователь отправил форму с пустым полем Имя, то это — не исключительная ситуация. Это обычная ожидаемая ситуация, которая должна быть обработана соответствующим образом.

    Если же при вызове метода API для изменения элемента инфоблока был указан пустой id элемента, то это исключительная ситуация. Она не ожидаема и продолжать изменение элемента не имеет смысла.

    Если метод ожидает id пользователя, а вы передаёте строку, то это — исключение, так как метод не знает что делать со строкой в данном случае.

    Если метод GetList принимает фильтр timestamp, а разработчик написал tymestamp, то это будет исключением.

    Иерархия исключений

    Все исключения D7 наследуются от встроенного в PHP класса Exception, который присутствует в PHP начиная с версии 5.1. У данного класса есть не переопределяемые методы getMessage(), getCode(), getFile(), getLine(), getTrace(), getTraceAsString(), а так же переопределяемый метод __toString().

    Есть понятие иерархии исключений. Оно нужно для того, чтобы можно было их (исключения) обработать, посмотреть какое из них сработало и в зависимости от этого предпринять какие-то действия. Общая схема иерархии имеет вид:

    • Exception
      • BitrixMainSystemException — базовый класс всех системных исключений
        • BitrixMainIOIoException — базовый класс всех исключений файлового ввода-вывода

          • BitrixMainIOFileDeleteException — исключение при удалении файла
          • BitrixMainIOFileNotFoundException — отсутствие требуемого файла
          • BitrixMainIOFileOpenException — исключение при открытии файла
          • BitrixMainIOInvalidPathException — не корректный путь
          • BitrixMainIOFileNotOpenedException — файл не открыт
        • BitrixMainConfigConfigurationException — ошибка в конфигурации
        • BitrixMainSecuritySecurityException — ошибка безопасности
          • BitrixMainSecuritySignBadSignatureException — исключения ошибок подписи.
        • BitrixMainArgumentException — базовый класс исключений, связанных с входящими параметрами методов
          • BitrixMainArgumentNullException — параметр должен быть не пустым
          • BitrixMainArgumentOutOfRangeException — параметр вне допустимого диапазона
          • BitrixMainArgumentTypeException — параметр не допустимого типа
        • BitrixMainDBException — базовый класс для исключений БД
          • BitrixMainDBConnectionException — исключение при соединении
          • BitrixMainDBSqlException — исключение при выполнении запроса
        • BitrixMainNotSupportedException — вызывается, если функционал не поддерживается
        • BitrixMainNotImplementedException — вызывается, если функционал должен поддерживаться, но пока не реализован
      • BitrixMainObjectPropertyException — исключение выводится когда свойства объекта не валидны.
      • BitrixMainObjectNotFoundException — выводит исключение когда объект не существует.
      • BitrixMainObjectException — исключение выводится, если объект не может быть создан.
      • BitrixMainLoaderException — исключение в загрузчике

    BitrixMainSystemException базовый класс всех системных исключений, от которого наследуются все остальные исключения. Данный класс переопределяет конструктор системного класса Exception. Если системный класс на вход принимает сообщение и код ошибки:

    <?php
    public function __construct($message = null, $code = 0, Exception $previous = null);

    , то конструктор MainSystemException на вход принимает кроме этого файл в котором было выброшено исключение и номер строки:

    <?php
    public function __construct($message = "", $code = 0, $file = "", $line = 0, Exception $previous = null);

    Выбрасываемое исключение должно иметь максимально подходящий тип.

    Если ваш метод создает исключение, то необходимо описать это в phpDoc метода.

    В Bitrix Framework это делается таким образом:

     /**
         * Searches connection parameters (type, host, db, login and password) by connection name
         *
         * @param string $name Connection name
         * @return array|null
         * @throws BitrixMainArgumentTypeException
         * @throws BitrixMainArgumentNullException
         */
        protected function getConnectionParameters($name) {}

    Игнорирование исключений

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

    Если включён CDN, то вверху страницы отображается информация о расходе трафика. В коде это реализовано так:

    $cdn_config = CBitrixCloudCDNConfig::getInstance()->loadFromOptions();
    $APPLICATION->SetTitle(GetMessage("BCL_TITLE"));
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_admin_after.php");
    if (is_object($message))
    	echo $message->Show();
    
    if (CBitrixCloudCDN::IsActive())
    {
    	try
    	{
    		if ($cdn_config->getQuota()->isExpired())
    			$cdn_config->updateQuota();
    
    		$cdn_quota = $cdn_config->getQuota();
    		if ($cdn_quota->getAllowedSize() > 0.0 || $cdn_quota->getTrafficSize() > 0.0)
    		{
    			CAdminMessage::ShowMessage(array(
    				"TYPE" => "PROGRESS",
    				"DETAILS" => '

    '.GetMessage("BCL_CDN_USAGE", array( "#TRAFFIC#" => CFile::FormatSize($cdn_quota->getTrafficSize()), "#ALLOWED#" => CFile::FormatSize($cdn_quota->getAllowedSize()), )).'

    #PROGRESS_BAR#', "HTML" => true, "PROGRESS_TOTAL" => $cdn_quota->getAllowedSize(), "PROGRESS_VALUE" => $cdn_quota->getTrafficSize(), )); } } catch (Exception $e) { CAdminMessage::ShowMessage($e->getMessage()); } }

    По коду видно, что если CDN активен, то формируется прогресс-бар с выводом информации о расходе трафика. Но если в ходе выполнения этого кода произойдёт ошибка, то будет выброшено исключение. Это исключение будет перехвачено так как весь код находится в try и сработает ветка, после catch, где выводится сообщение об ошибке штатной функцией. Выполнение скрипта при этом не будет прервано:

    Приложения и контекст

    Приложение — это объект, отвечающий за инициализацию ядра.

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

    Любой конкретный класс приложения является наследником абстрактного класса BitrixMainApplication.

    Конкретный класс BitrixMainHttpApplication отвечает за обычный http-хит на сайте.

    Приложение поддерживает шаблон Singleton (Одиночка). Т.е. в рамках хита существует только один экземпляр конкретного типа приложения. Его можно получить инструкцией

    $application = Application::getInstance();

    Контекст — это объект, отвечающий за конкретный хит. Он содержит запрос текущего хита, ответ ему, а также серверные параметры текущего хита. То есть это изменяемая часть, зависящая от текущего хита.

    Любой конкретный класс контекста является наследником абстрактного класса BitrixMainContext. Осуществляется поддержка двух конкретных классов контекста: BitrixMainHttpContext и BitrixMainCliContext. Конкретный класс BitrixMainHttpContext отвечает за обычный http-хит на сайте.

    Чтобы получить контекст выполнения текущего хита, можно воспользоваться кодом

    $context = Application::getInstance()->getContext();

    Если было инициализировано приложение типа BitrixMainHttpApplication, то этот вызов вернет экземпляр контекста типа BitrixMainHttpContext.

    Контекст содержит в себе запрос текущего хита. Для того, чтобы получить запрос, можно воспользоваться кодом:

    $context = Application::getInstance()->getContext();
    $request = $context->getRequest();

    Примечание: везде в примерах используется полная форма записи (иногда без указания пространств имен), позволяющая получить результат из любой точки кода. Но для данной конкретной точки кода могут существовать сокращенные формы для доступа к результату.

    Запрос представляет собой экземпляр класса, являющегося наследником класса BitrixMainRequest. В случае обычного http-запроса запрос будет являться экземпляром класса BitrixMainHttpRequest, расширяющего BitrixMainRequest. Этот класс является по сути словарем, предоставляющим доступ к парам «ключ-значение» входящих параметров.

    Для того чтобы обратиться к входящему параметру, переданному методами GET или POST, можно использовать код:

    $value = $request->get("some_name");
    $value = $request["some_name"];

    Примечание: Код $value = $request["some_name"]; возвращает строку, которая прошла уже фильтры модуля безопасности. Однако это не говорит о ее безопасности, всё зависит от того, что с ней необходимо делать дальше.

    Другие полезные методы запроса:

    $value = $request->getQuery("some_name");   // получение GET-параметра
    $value = $request->getPost("some_name");   // получение POST-параметра
    $value = $request->getFile("some_name");   // получение загруженного файла
    $value = $request->getCookie("some_name");   // получение значения кука
    $uri = $request->getRequestUri();   // получение запрошенного адреса
    $method = $request->getRequestMethod();   // получение метода запроса
    $flag = $request->isPost();      // true - POST-запрос, иначе false

    Ошибки в D7

    В старом ядре API пытался додумывать за пользователя/разработчика, прощал ему ошибки и неточности. Результатом такого поведения обычно бывают сложно-отлавливаемые ошибки. К тому же такой подход порождает массу неявных соглашений, которые нужно знать.

    Например, пользователь/разработчик выбирает записи для удаления по фильтру. При этом он случайно описывается в названии фильтра. Типичное API старого ядра проигнорирует этот фильтр и вернет все записи. Следующая инструкция эти все записи успешно удалит.

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

    При выводе ошибки на экран (если режим отладки отсутствует), подключается файл [dwi include_error_php]/error.php[/dwi].

    В этом файле вы можете вывести ошибку в дизайне сайта, а также установить код статуса HTTP (например, «500 Internal Server Error»).

    API

    API

    API (классы) модуля не делятся по базам данных. ORM скрывает в себе все тонкости работы с конкретной базой данных.

    В названиях классов не должны использоваться какие-либо префиксы или суффиксы.

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

    Например, класс BitrixMainApplication должен быть расположен в файле /lib/application.php относительно корневой папки модуля main, класс BitrixMainIOFile должен быть расположен в файле /lib/io/file.php относительно корневой папки модуля main, класс BitrixForumMessage должен быть расположен в файле /lib/message.php относительно корневой папки модуля forum.

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

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

    Исключением из правил именования классов и файлов являются классы сущностей ORM (наследников класса BitrixMainEntityDataManager). Имена таких классов формируются с суффиксом Table. (Например: CultureTable, LanguageTable.) А имя файла не содержит суффикса table. Такие классы также подключаются автоматически.

    Примечание: Существует возможность вручную зарегистрировать класс в системе автозагрузки с помощью метода

    void BitrixMainLoader::registerAutoLoadClasses(
    	$moduleName,
    	array $arClasses
    )

    Это можно использовать для объединения маленьких классов в один файл.

    Нестандартные классы (классы партнеров), должны находиться в собственных пространствах имен, совпадающих с названиями соответствующих партнеров. Каждый партнерский модуль определяет в пространстве имен партнера свое подпространство, совпадающее с именем модуля без имени партнера. Например, для модуля mycompany.catalog партнера «Mycompany» пространством имен будет MyCompanyCatalog. Остальные правила совпадают с правилами для стандартных модулей.

    Для подключения модуля в новом ядре используется инструкция:

    mixed BitrixMainLoader::includeModule($moduleName);
    

    Правила наименования

    Классы

    • Классы должны именоваться «ВерхнимКэмелКейсом».
    • Не могут содержать в названии никаких символов, кроме букв латинского алфавита.
    • Название класса должно быть существительным. Надо стараться избегать ненужных сокращений и аббревиатур.

    Примеры:

    class User;
    class UserInformation;

    Методы

    • Методы, в том числе методы класса, должны именоваться «нижнимКэмелКейсом».
    • Не могут содержать в названии никаких символов, кроме букв латинского алфавита.
    • Использование цифр допускается, если избежать иного не получается. Например: encodeBase64, getSha1Key.
    • Название метода должно начинаться с глагола.
    • Длина названия должна быть не менее 3-х символов.

    Примеры:

    run();
    setImage();
    getName();

    Константы

    • Константы, в том числе константы класса, должны быть написаны в ВЕРХНЕМ_РЕГИСТРЕ_С_РАЗДЕЛИТЕЛЕМ_ПОДЧЕРКИВАНИЕМ.
    • Могут содержать буквы латинского алфавита, знак подчеркивания и числа (не в первой позиции).

    Примеры:

    DATE_TIME_FORMAT
    LEVEL_7

    Члены класса, параметры методов и переменные

    • Должны именоваться «нижнимКэмелКейсом».
    • Не должны содержать префиксов означающих членство в классе, принадлежность параметрам, тип и прочие бессодержательные вещи. Пример лишних префиксов: $this->mAge, function setName($pName), $arrArray.
    • Могут содержать буквы латинского алфавита и числа (не в первой позиции).

    Примеры:

    $firstName = '';
    $counter = 0;

    Общепринятые сокращения в названиях переменных и методов

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

    Пример:

    $xmlDocument
    $mainXmlDocument
    HttpParser

    Сокращения, не являющиеся общепринятыми (в Битриксе), использовать нельзя.

    Документация

    • API документация по старому ядру
    • API документация ядра D7

    Если нет описания API

    Живое описание АПИ

    Цитатник веб-разработчиков.

    Дмитрий Яковенко: Метод как всегда прекрасен и незадокументирован.

    При работе с Bitrix Framework очень большое значение имеет описание API. К сожалению составление описаний API нового функционала никогда не выходит одновременно с функционалом. Причин этого несколько:

    • Создавать описание API и сам функционал одновременно невозможно;
    • Создавать описание API сразу после выхода функционала – не практично, так как в первое время после выхода обнаруживается много багов, которые требуют исправления и, соответственно учета этих исправлений в документации.

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

    Для работы в таких условиях единственный выход у разработчика – смотреть сам код. В помощь для таких случаев есть специальный бесплатный модуль Живое описание АПИ, который сканирует текущие файлы ядра и выводит список доступных API функций и событий всех модулей.

    Возможности модуля:

    • Модуль доступен только пользователю с правами администратора системы.
    • Все модули сканируются последовательно один раз, после этого рядом с live_api.php появляется файл live_api.data.php, который содержит данные о функциях;
    • Можно выбрать не только модуль, но и интересующий класс;
    • В исходном коде имеющиеся функции и методы Bitrix Framework отображены в виде ссылок, которые ведут на их исходный код этих функций и методов.

    Как работать?

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

      Примечание: Если будет выбран только модуль, то в форме отобразится все, относящееся к этому модулю.

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

    Например, метод CCurrency::GetList (см. иллюстрацию выше) имеет два обязательных параметра: поле сортировки и порядок сортировки. Оба передаются по ссылке. Третий параметр язык, по умолчанию принимает значение текущего языка.

    По клику на функцию открывается её описание в новом окне. Код функции читается прямо из файла, при этом уже скрипты не парсятся, вся необходимая информация передаётся в URL.

    По клику на событие или константу открывается метод, где оно инициируется. Вызов события (константы) подсвечивается.

    Примечание: В файлах ядра D7 есть комментарии в PHPdoc, оставляемые разработчиками. Эти комментарии не всегда попадают в документацию по ряду причин, но они могут пригодиться разработчикам в работе.

    Практика. Некоторые классы

    Конфигурация

    Расположено в пространстве имен BitrixMainConfig. Состоит из двух классов: BitrixMainConfigConfiguration и BitrixMainConfigOption.

    Configuration

    $realm = BitrixMainConfigConfiguration::getValue("http_auth_realm");
    if (is_null($realm))
        $realm = "Bitrix Site Manager"

    Класс отвечает за глобальные настройки всего приложения. (Это то, что в старом ядре определяется константами.) Класс оперирует единой базой настроек, которые хранятся в файле /bitrix/.settings.php. Данные хранятся произвольные. Например, для соединений может храниться целый пул данных для именованных соединений.

    Option

    $cookiePrefix = BitrixMainConfigOption::get('main', 'cookie_name', 'BITRIX_SM');
    $cookieLogin = $request->getCookie($cookiePrefix.'_LOGIN');
    $cookieMd5Pass = $request->getCookie($cookiePrefix.'_UIDN');

    Является в некоторой степени аналогом класс COption старого ядра и работает с параметрами модулей, сайтов, хранимых в базе данных. Это то, что управляется из административной части: настройки каких-то форм, установка и так далее.

    Файлы

    Работа с файлами объектно-ориентированная, вынесена в пространство имён BitrixMainIO и обладает тремя базовыми классами:

    • Path – работа с путями, статический.
    • Directory – работа с папками.
    • File – работа с файлами.

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

    Другие классы

    В папке bitrix/modules/main/lib расположена библиотека классов для осуществления разных частых действий, которые вынесены в Main, а не разнесены по разным модулям. В том числе в соответствующих пространствах лежат файлы и API для работы:

    • BitrixMainData — с кешем, в том числе управляемый кеш.
    • BitrixMainText — с текстом: классы для конвертации текста и другие
    • BitrixMainType — с типами данных: дата, файл и другие.
    • BitrixMainWeb — с web: работа с URL, обращения по web’у и другие.

    Аналог CUtil::jSPostUnescape() в D7

    Если необходимо использовать HttpRequest при аякс запросах:

    Application::getInstance()->getContext()->getRequest()->getPost('name')

    то надо учитывать, что CUtil::JSPostUnescape не поможет в случае установки win-1251.

    Можно использовать:

    use BitrixMainWebPostDecodeFilter;
    ...
    Application::getInstance()->getContext()->getRequest()->addFilter(new PostDecodeFilter)
    

    После этого можно получать декодированные данные через getPost.

    Практика. Работа с D7 на примере местоположений

    Не забудьте подключить модуль sale.

    Типы местоположений

    Добавление типа местоположения:

    $res = BitrixSaleLocationTypeTable::add(array(
    	'CODE' => 'CITY',
    	'SORT' => '100', // уровень вложенности
    	'DISPLAY_SORT' => '200', // приоритет показа при поиске
    	'NAME' => array( // языковые названия
    		'ru' => array(
    			'NAME' => 'Город'
    		),
    		'en' => array(
    			'NAME' => 'City'
    		),
    	)
    ));
    if($res->isSuccess())
    {
    	print('Type added with ID = '.$res->getId());
    }

    Обновление типа местоположения

    $res = BitrixSaleLocationTypeTable::update(21, array(
    	'SORT' => '300',
    	'NAME' => array(
    		'ru' => array(
    			'NAME' => 'Новый Город'
    		),
    	)
    ));
    if($res->isSuccess())
    {
    	print('Updated!');
    }

    Удаление типа местоположения

    $res = BitrixSaleLocationTypeTable::delete(21);
    if($res->isSuccess())
    {
    	print('Deleted!');
    }

    Получение типа местоположения по ID

    $item = BitrixSaleLocationTypeTable::getById(14)->fetch();
    print_r($item);

    Получение списка типов с названиями на текущем языке

    $res = BitrixSaleLocationTypeTable::getList(array(
    	'select' => array('*', 'NAME_RU' => 'NAME.NAME'),
    	'filter' => array('=NAME.LANGUAGE_ID' => LANGUAGE_ID)
    ));
    while($item = $res->fetch())
    {
    	print_r($item);
    }

    Получаем группы с учётом иерархии, в которых есть данное местоположение

    <?
    BitrixMainLoader::includeModule('sale');
    
    function getGroupsByLocation($locationId)
    {
        $res = BitrixSaleLocationLocationTable::getList([
            'filter' => ['=ID' => $locationId],
            'select' => [
                'ID', 'LEFT_MARGIN', 'RIGHT_MARGIN'
            ]
        ]);
    
        if(!$loc = $res->fetch())
        {
            return [];
        }
    
        $locations = [$locationId];
    
        $res = BitrixSaleLocationLocationTable::getList([
            'filter' => [
                '<LEFT_MARGIN' => $loc['LEFT_MARGIN'],
                '>RIGHT_MARGIN' => $loc['RIGHT_MARGIN'],
                'NAME.LANGUAGE_ID' => LANGUAGE_ID,
            ],
            'select' => [
                'ID',
                'LOCATION_NAME' => 'NAME.NAME'
            ]
        ]);
    
        while($locParent = $res->fetch())
        {
            $locations[] = $locParent['ID'];
        }
    
        $res = BitrixSaleLocationGroupLocationTable::getList([
            'filter' => ['=LOCATION_ID' => $locations]
        ]);
    
        $groups = [];
    
        while($groupLocation = $res->fetch())
        {
            $groups[] = $groupLocation['LOCATION_GROUP_ID'];
        }
    
        return $groups;
    }

    Местоположения

    Добавление

    $res = BitrixSaleLocationLocationTable::add(array(
    	'CODE' => 'newly-created-location-code',
    	'SORT' => '100', // приоритет показа при поиске
    	'PARENT_ID' => 1, // ID родительского местоположения
    	'TYPE_ID' => 14, // ID типа
    	'NAME' => array( // языковые названия
    		'ru' => array(
    			'NAME' => 'Архангельск'
    		),
    		'en' => array(
    			'NAME' => 'Arkhangelsk'
    		),
    	),
    	'EXTERNAL' => array( // значения внешних сервисов
    		array(
    			'SERVICE_ID' => 1, // ID сервиса
    			'XML_ID' => '163000' // значение
    		),
    		array(
    			'SERVICE_ID' => 1,
    			'XML_ID' => '163061'
    		),
    	)
    ));
    if($res->isSuccess())
    {
    	print('Location added with ID = '.$res->getId());
    }
    else
    {
    	print_r($res->getErrorMessages());
    }

    Обновление

    $res = BitrixSaleLocationLocationTable::update(3156, array(
    	'PARENT_ID' => 33,
    	'NAME' => array(
    		'de' => array(
    			'NAME' => 'Arkhangelsk'
    		),
    	)
    ));
    if($res->isSuccess())
    {
    	print('Updated!');
    }

    Удаление

    $res = BitrixSaleLocationLocationTable::delete(3156);
    if($res->isSuccess())
    {
    	print('Deleted!');
    }

    Получение местоположения по ID

    $item = BitrixSaleLocationLocationTable::getById(3159)->fetch();
    print_r($item);

    Получение местоположения по CODE, с опциональной фильтрациейвыборкой полей. Фактически это обертка над BitrixSaleLocationLocationTable::getList().

    $item = BitrixSaleLocationLocationTable::getByCode('newly-created-location-code', array(
    	'filter' => array('=NAME.LANGUAGE_ID' => LANGUAGE_ID),
    	'select' => array('*', 'NAME_RU' => 'NAME.NAME')
    ))->fetch();
    print_r($item);

    Получение списка местоположений с названиями на текущем языке и кодами типов

    $res = BitrixSaleLocationLocationTable::getList(array(
    	'filter' => array('=NAME.LANGUAGE_ID' => LANGUAGE_ID),
    	'select' => array('*', 'NAME_RU' => 'NAME.NAME', 'TYPE_CODE' => 'TYPE.CODE')
    ));
    while($item = $res->fetch())
    {
    	print_r($item);
    }

    Получение прямых потомков узла с ID=1 с названиями на текущем языке, кодами и названиями типов местоположений

    $res = BitrixSaleLocationLocationTable::getList(array(
    	'filter' => array(
    		'=ID' => 1, 
    		'=CHILDREN.NAME.LANGUAGE_ID' => LANGUAGE_ID,
    		'=CHILDREN.TYPE.NAME.LANGUAGE_ID' => LANGUAGE_ID,
    	),
    	'select' => array(
    		'CHILDREN.*',
    		'NAME_RU' => 'CHILDREN.NAME.NAME',
    		'TYPE_CODE' => 'CHILDREN.TYPE.CODE',
    		'TYPE_NAME_RU' => 'CHILDREN.TYPE.NAME.NAME'
    	)
    ));
    while($item = $res->fetch())
    {
    	print_r($item);
    }

    Получение родительских узлов для трех узлов

    $res = BitrixSaleLocationLocationTable::getList(array(
    	'filter' => array(
    		'=ID' => array(3159, 85, 17), 
    		'=PARENT.NAME.LANGUAGE_ID' => LANGUAGE_ID,
    		'=PARENT.TYPE.NAME.LANGUAGE_ID' => LANGUAGE_ID,
    	),
    	'select' => array(
    		'PARENT.*',
    		'NAME_RU' => 'PARENT.NAME.NAME',
    		'TYPE_CODE' => 'PARENT.TYPE.CODE',
    		'TYPE_NAME_RU' => 'PARENT.TYPE.NAME.NAME'
    	)
    ));
    while($item = $res->fetch())
    {
    	print_r($item);
    }

    Получение пути от корня дерева до текущего элемента

    $res = BitrixSaleLocationLocationTable::getList(array(
    	'filter' => array(
    		'=ID' => 224, 
    		'=PARENTS.NAME.LANGUAGE_ID' => LANGUAGE_ID,
    		'=PARENTS.TYPE.NAME.LANGUAGE_ID' => LANGUAGE_ID,
    	),
    	'select' => array(
    		'I_ID' => 'PARENTS.ID',
    		'I_NAME_RU' => 'PARENTS.NAME.NAME',
    		'I_TYPE_CODE' => 'PARENTS.TYPE.CODE',
    		'I_TYPE_NAME_RU' => 'PARENTS.TYPE.NAME.NAME'
    	),
    	'order' => array(
    		'PARENTS.DEPTH_LEVEL' => 'asc'
    	)
    ));
    while($item = $res->fetch())
    {
    	print_r($item);
    }

    Получение списка корневых узлов с указанием количества потомков

    $res = BitrixSaleLocationLocationTable::getList(array(
    	'filter' => array(
    		'=PARENT_ID' => 0,
    		'=NAME.LANGUAGE_ID' => LANGUAGE_ID,
    		'=TYPE.NAME.LANGUAGE_ID' => LANGUAGE_ID,
    	),
    	'select' => array(
    		'ID',
    		'NAME_RU' => 'NAME.NAME',
    		'TYPE_CODE' => 'TYPE.CODE',
    		'TYPE_NAME_RU' => 'TYPE.NAME.NAME',
    		'CHILD_CNT'
    	)
    ));
    while($item = $res->fetch())
    {
    	print_r($item);
    }

    Получение внешних данных для местоположений с указанием кода сервиса

    $res = BitrixSaleLocationLocationTable::getList(array(
    	'filter' => array(
    		'CODE' => array('newly-created-location-code', '0000028090'),
    	),
    	'select' => array(
    		'EXTERNAL.*',
    		'EXTERNAL.SERVICE.CODE'
    	)
    ));
    while($item = $res->fetch())
    {
    	print_r($item);
    }

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

    $res = BitrixSaleLocationLocationTable::getList(array(
    	'runtime' => array(
    		'SUB' => array(
    			'data_type' => 'BitrixSaleLocationLocation',
    			'reference' => array(
    				'>=ref.LEFT_MARGIN' => 'this.LEFT_MARGIN',
    				'<=ref.RIGHT_MARGIN' => 'this.RIGHT_MARGIN'
    			),
    			'join_type' => "inner"
    		)
    	),
    	'filter' => array(
    		'=CODE' => '0000028042',
    		'=SUB.NAME.LANGUAGE_ID' => LANGUAGE_ID
    	),
    	'select' => array(
    		'S_CODE' => 'SUB.CODE',
    		'S_NAME_RU' => 'SUB.NAME.NAME',
    		'S_TYPE_CODE' => 'SUB.TYPE.CODE'
    	)
    ));
    while($item = $res->fetch())
    {
    	print_r($item);
    }

    Получаем местоположения входящие в группу без учёта иерархии.

    BitrixMainLoader::includeModule('sale');
    
    
    /* Идентификатор группы */
    $groupId = 1
    
    /* Получаем местоположения входящие в группу */
    $res = BitrixSaleLocationGroupLocationTable::getConnectedLocations(1);
    
    while($item = $res->fetch())
    {
        var_dump($item);
    }

    Сервис Локатор

    Описание

    Сервис локатор (локатор служб) — это шаблон проектирования для удобной работы с сервисами приложения. Подробнее можно прочитать в статье.

    Идея сервиса в том, что вместо создания конкретных сервисов напрямую (с помощью new), используется специальный объект (сервис локатор), который будет отвечать за создание, нахождение сервисов. Своего рода реестр.

    Класс BitrixMainDIServiceLocator реализует интерфейс PSR-11. Доступен с версии main 20.5.400.

    Простой пример использования:

    $serviceLocator = BitrixMainDIServiceLocator::getInstance();
    
    if ($serviceLocator->has('someService'))
    {
        $someService = $serviceLocator->get('someService');
        //...$someService использование сервиса
    }

    Регистрация сервиса

    Регистрация сервиса через файлы настроек bitrix/.settings.php

    Прежде чем обращаться к сервису его необходимо зарегистрировать и один из способов это использование файлов настроек .settings.php. Все необходимые сервисы перечисляются в секции services.

     [
            'value' => [
                'someServiceName' => [
                    'className' => VendorNameServicesSomeService::class,
                ],                      
                'someGoodServiceName' => [
                    'className' => VendorNameServicesSecondService::class,
                    'constructorParams' => ['foo', 'bar'],
                ],                      
            ],
            'readonly' => true,
          ],
          //...
       ];            

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

    $serviceLocator = BitrixMainDIServiceLocator::getInstance();
    $someGoodServiceName = $serviceLocator->get('someGoodServiceName');
    $someServiceName = $serviceLocator->get('someServiceName');

    Регистрация сервиса через файлы настроек модуля {moduleName}/.settings.php

    В корне модуля так же может располагаться свой файл .settings.php. И в нём можно описать сервисы, которые принадлежат данному модулю и используются в нём. Семантика аналогична описанию в глобальном bitrix/.settings.php и правилам описания конфигураций.

     [
            'value' => [
                'someModule.someServiceName' => [
                    'className' => VendorNameSomeModuleServicesSomeService::class,
                ],                      
                'someModule.someAnotherServiceName' => [
                    'constructor' => static function () {
                        return new VendorNameSomeModuleServicesSecondService('foo', 'bar');
                    },
                ],                      
                'someModule.someGoodServiceName' => [
                    'className' => VendorNameSomeModuleServicesSecondService::class,
                    'constructorParams' => static function (){
                        return ['foo', 'bar'];
                    },
                ],                      
            ],
            'readonly' => true,
          ],
          //...
       ];

    Внимание! Данные сервисы будут зарегистрированы только после подключения модуля. Также советуем именовать сервисы модулей, используя префикс имени модуля, чтобы не возникало проблемы уникальности кодов сервисов, например:

     iblock.imageUploader
        disk.urlManager
        crm.entityManager
        crm.urlManager
        someModule.urlManager.

    Регистрация сервиса через API

    Сервисы можно зарегистрировать и через API. Для этого воспользуйтесь методами класса BitrixMainDIServiceLocator

    Конфигурация сервиса

    Конфигурация описывается в виде массива и подсказывает сервис локатору способ создания объекта. На данный момент есть три способа описания:

    1. Указание класса сервиса. Сервис локатор создаст сервис вызвав new $className.
       'someModule.someServiceName' => [
                  'className' => VendorNameSomeModuleServicesSomeService::class,
              ]
    2. Указание класса сервиса и параметров, которые будут переданы в конструктор. Сервис локатор создаст сервис вызвав new $className('foo', 'bar').
      'someModule.someServiceName' => [
                  'className' => VendorNameSomeModuleServicesSomeService::class,
                  'constructorParams' => ['foo', 'bar'],
              ]                      
      
              'someModule.someServiceName' => [
                  'className' => VendorNameSomeModuleServicesSomeService::class,
                  'constructorParams' => static function (){
                     return ['foo', 'bar'];
                  },
              ]
    3. Указание замыкания-конструктора, который должен создать и вернуть объект сервиса.
       'someModule.someAnotherServiceName' => [
                  'constructor' => static function () {
                      return new VendorNameSomeModuleServicesSecondService('foo', 'bar');
                  },
              ]

    Контроллеры

    Термины

    • Действие, аякс-действие — это метод-ответчик, который реализует конечную логику, выполняет работу и возвращает данные.
    • Контроллер — это совокупность аякс-действий.
    • Конфигурация действий — это метод configureActions() внутри контроллера, где определяются правила доступа к действиям.
    • Пре-, пост-фильтры — это элементы конфигурации, которые по сути являются обработчиками события старта-конца действия. Префильтр может блокировать старт, постфильтр может повлиять на результат.

    Соглашения

    Примечание: С версии 20.600.87 Главного модуля (main) добавлена поддержка PSR-4 в ajax-контроллерах.

    • При вызове все имена параметров регистроЗАвисимые
    • При вызове все имена контроллеров регистроЗАвисимые
    • При вызове все имена действий регистроНЕзависимые
    • Полное имя действия из модуля генерируется по шаблону vendor:module.partOfNamespace0.partOfNamespace1.Controller.action.
          BitrixDiskControllerFolder::getAction() 
          bitrix:disk.Controller.Folder.get
      
          BitrixDiskControllerIntergationDropbox::connectAction() 
          bitrix:disk.Controller.Intergation.Dropbox.connect
      
          QsoftSomediskControllerSuperFolder::getAction() 
          qsoft:somedisk.Controller.SuperFolder.get
    • Если не указывать vendor:, то это означает, что это bitrix:
          BitrixDiskControllerFolder::getAction() 
          disk.Controller.Folder.get
    • Если указан defaultNamespace в настройках модуля, то его можно опускать и не указывать в действии.
      	defaultNamespace = BitrixDiskController
      	
          BitrixDiskControllerFolder::getAction() 
          disk.Folder.get
    • Если указан альяс в настройках модуля, то можно использовать его вместо сокращаемого namespace.
      	BitrixDiskCloudIntegrationController => cloud
      	
          BitrixDiskCloudIntegrationControllerFile::getAction() 
          disk.cloud.File.get
    • При вызове действия из компонента необходимо указывать полное имя компонента и имя действия (без суффикса Action).
          bitrix:list.example
          showFormAction
      
          BX.ajax.runComponentAction('bitrix:list.example', 'showForm', {
              ...
          }).then(function (response) {});
    • Время, дата, ссылки должны возвращаться не в строковом формате, а объектами.
              BitrixMainTypeDateTime
              BitrixMainTypeDate
              BitrixMainWebUri

    Контроллер

    Контроллеры — это часть MVC архитектуры, которая отвечает за обработку запроса и генерирование ответа.

    Действия

    Контроллеры состоят из действий, которые являются основной сутью и их в конечном итоге запрашивает пользователь для получения результата. В одном контроллере может быть одно или несколько действий. Для примера сделаем контроллер с двумя действиями item.add и item.view в модуле example.

    Первый шаг — создать в корне модуля файл .settings.php.

    <?php
    //modules/vendor.example/.settings.php
    return [
    	'controllers' => [
    		'value' => [
    			'defaultNamespace' => '\Vendor\Example\Controller',
    		],
    		'readonly' => true,
    	]
    ];

    Далее создаётся сам файл контроллера (смотри подробное описание доступных методов):

    namespace VendorExampleController;
    
    use BitrixMainError;
    
    class Item extends BitrixMainEngineController
    {
    	public function addAction(array $fields):? array
    	{
    		$item = Item::add($fields);
    
    		if (!$item)
    		{
    			$this->addError(new Error('Could not create item.', {код_ошибки}));
    
    			return null;
    		}
    
    		return $item->toArray();
    	}
    
    	public function viewAction($id):? array
    	{
    		$item = Item::getById($id);
    		if (!$item)
    		{
    			$this->addError(new Error('Could not find item.', {код_ошибки}));
    					
    			return null;
    		} 
    
    		return $item->toArray();
    	}
    }

    В действии add (определенным методом Item::addAction) сначала идёт попытка создания некого Item по переданным $fields.

    Примечание. Массив $fields получается путем автоматического связывания параметров метода и $_REQUEST.

    Если не удалось выполнить создание по каким-то причинам, то возвращаем null и наполняем ошибками сам контроллер. В этом случае ядро сгенерирует ответ:

    {
    	"status": "error", //обратите внимание, что статус автоматически сменился
    	"data": null,
    	"errors": [
    		{
    			"message": "Could not create item.",
    			"code": {код}
    		}
    	]
    }

    Иначе добавляем Item и возвращаем из действия его представление в виде массива $item->toArray(). Таким образом ядро сгенерирует ответ:

    {
    	"status": "success",
    	"data": {
    		"ID": 1,
    		"NAME": "Nobody",
    		//...поля элемента
    	},
    	"errors": null
    }

    В целом, действие может вернуть не просто скаляры, но и объекты.

    В действии view (определенным методом Item::viewAction) сначала идёт попытка загрузки некого объекта Item по переданному параметру $id. Важно заметить, что $id будет автоматически получен из $_POST['id'] или $_GET['id'].

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

    {
    	"status": "error",
    	"data": null,
    	"errors": [
    		{
    			"message": "Could not find value for parameter {id}",
    			"code": 0
    		}
    	]
    }
    

    Как обратиться к действию контроллера?

    Для вызова конкретного аякс-действия нужно знать и пользоваться соглашением по именованию. В нашем случае: Item::addAction -> vendor:example.Item.add Item::viewAction -> vendor:example.Item.view.

    vendor:example.Item.add, vendor:example.Item.view можно использовать для вызова действий через BX.ajax.runAction:

    BX.ajax.runAction('vendor:example.Item.add', {
    	data: {
    		fields: {
                ID: 1,
                NAME: "test"
            } 
    	}
    }).then(function (response) {
    	console.log(response);
    	/**
    	{
    		"status": "success", 
    		"data": {
    			"ID": 1,
    			"NAME": "test"
    		}, 
    		"errors": []
    	}
    	**/			
    }, function (response) {
    	//сюда будут приходить все ответы, у которых status !== 'success'
    	console.log(response);
    	/**
    	{
    		"status": "error", 
    		"errors": [...]
    	}
    	**/				
    });

    Либо можно получить ссылку на действие и послать http-запрос самостоятельно.

    /** @var BitrixMainWebUri $uri **/
    $uri = BitrixMainEngineUrlManager::getInstance()->create('vendor:example.Item.view', ['id' => 1]);
    echo $uri;
    // /bitrix/services/main/ajax.php?action=vendor:example.Item.view&id=1
    // выполняем GET-запрос

    Создание контроллеров и действий

    Создание контроллеров

    Контроллеры должны быть унаследованы от BitrixMainEngineController или его потомков. Контроллеры могут располагаться внутри модуля, либо внутри компонента в файле ajax.php и быть контроллером для компонента.

    Создание действий

    Создание действий, это создание просто методов в конкретном контроллере. Метод обязан быть public и иметь суффикс Action.

    namespace VendorExampleController;
    
    class Item extends BitrixMainEngineController
    {
    	public function addAction(array $fields)
    	{
            //...
    	}
    }

    Возвращаемое значение действия представляет собой данные ответа, которые будут высланы клиенту.

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

    В целом действие может вернуть не просто скаляры, но и объекты, которые реализуют следующие интерфейсы:

    • JsonSerializable
    • BitrixMainTypeContractArrayable
    • BitrixMainTypeContractJsonable

    Либо конкретные наследники BitrixMainHttpResponse:

    • Redirect
    • JSON
    • Типовой JSON
    • Файл b_file
    • Ресайзенное изображение
    • Архив
    • Компонент

    Создание классов-действий

    Есть возможность создавать классы-действия, которые унаследованы от BitrixMainEngineAction. Подобная возможность может потребоваться, когда необходимо повторно использовать логику в нескольких контроллерах. Например, если реализуется одинаковый протокол обмена в разных модулях (стандартный поиск, выполнение пошаговых действий с прогрессом и тому подобное.). Для использования нужно описать в карте конфигурации контроллера метод configureActions:

    class Test extends BitrixMainEngineController
    {
    	public function configureActions()
    	{
    		return [
    			'testoPresto' => [
    				'class' => TestAction::class,
    				'configure' => [
    					'who' => 'Me',
    				],
    			],
    		];
    	}
    }

    И вот сам TestAction:

    <?php
    
    use BitrixMainEngineAction;
    
    class TestAction extends Action
    {
    	protected $who;
    
    	//метод для дополнительного конфигурирования из контроллера. Если требуется установить
    	//какие-то значения во внутреннее состояние
    	public function configure($params)
    	{
    		parent::configure($params);
    
    		$this->who = $params['who']?: 'nobody';
    	}
    
    	//основной метод работы. Параметры так же автоматически связываются, как и в методе
    	//аякс-действии
    	public function run($objectId = null)
    	{
    		return "Test action is here! Do you know object with id {$objectId}? Mr. {$this->who}";
    	}
    }

    Для вызова этого действия нужно обращаться к нему testoPresto, как описано в карте конфигурации. Класс-действие поддерживает пре- и пост-фильтры и по сути ничем не отличается от обычного метода-действия. Смысл метода run() аналогичен другим методам аякс-действиям.

    Использование контроллеров внутри компонентов

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

    В простых случаях, если компонент самодостаточен и не используется активно с API-модуля, то можно использовать контроллеры внутри компонента.

    Жизненный цикл контроллера

    При обработке запроса Application создаёт контроллер на основе соглашения по именованию. Далее работу выполняет контроллер:

    • Controller::init() будет вызван после того, как контроллер создан и сконфигурирован.
    • Контроллер создает объект действия
      • Если действие не удалось создать, выкидывается исключение.
    • Контроллер вызывает метод подготовки параметров Controller::prepareParams.
    • Контроллер вызывает метод Controller::processBeforeAction(Action $action), в случае возврата true выполнение продолжается.
    • Контроллер выкидывает событие модуля main {полное_имя_класс_контроллера}::onBeforeAction, в случае возврата EventResult не равном EventResult::SUCCESS выполнение блокируется. На данном событии выполняются префильтры.
    • Контроллер запускает действие
      • Параметры действия будут сопоставлены с данными из запроса
    • Контроллер выкидывает событие модуля main {полное_имя_класс_контроллера}::onAfterAction. На данном событии выполняются постфильтры.
    • Контроллер вызывает методController::processAfterAction(Action $action, $result).
    • Приложение получает результат выполнения действия и если это данные, то создает BitrixMainEngineResponseAjaxJson с этими данными, либо отправляет объект ответа от действия.
    • Приложение вызывает метод Controller::finalizeResponse($response), передавая финальный вариант ответа, который будет отправлен пользователю после всех событий и подготовок.
    • Вывод $response пользователю.

    Несколько namespaces

    Указание нескольких namespaces в модуле.

    В .settings.php можно указать несколько namespaces, помимо defaultNamespace. Это может быть необходимо, когда контроллеры расположены рядом со своими бизнес-сущностями. Например, в некотором модуле «Диск» у нас есть интеграция с облаками.

    <?php
    return [
    	'controllers' => [
    		'value' => [
    			'namespaces' => [
    				'\Bitrix\Disk\CloudIntegration\Controller' => 'cloud', //cloud - это альяс
    			],
    			'defaultNamespace' => '\Bitrix\Disk\Controller',
    		],
    		'readonly' => true,
    	]
    ];

    Теперь у нас доступны для вызова контроллеры, которые расположены в обоих namespaces. Оба из них поддерживают вызов через полное имя действия и через сокращенную запись, так как у нас есть альяс cloud.

    Равносильны:

    disk.CloudIntegration.Controller.GoogleFile.get
    disk.cloud.GoogleFile.get
    
    disk.Controller.File.get
    disk.File.get

    Вызов модульного контроллера с
    подписанными параметрами компонента

    Вызов модульного контроллера с подписанными параметрами компонента

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

    BX.ajax.runAction('socialnetwork.api.user.stresslevel.get', {
        signedParameters: this.signedParameters, // результат $this->getComponent()->getSignedParameters() 
        data: {
            c: myComponentName, // например, 'bitrix:intranet.user.profile', параметры которого нам будут нужны
            fields: {
                //..
            }
        }
    });

    После этого внутри кода действия используйте:

    <?php
    
        //...
        public function getAction(array $fields)
        {
            //внутри распакованный, проверенный массив параметров
            $parameters = $this->getUnsignedParameters();
            
            return $parameters['level'] * 100;
        }

    Контроллеры и компонент

    Для обработки аяксовых запросов в компоненте можно использовать два подхода:

    class.php

    Обработчик запросов в классе компонента (файл class.php) позволяет:

    • Инкапсулировать весь код в одном классе
    • Повторно использовать методы, данные и параметры компонента
    • Использовать языковые фразы, шаблоны компонента
    • Переопределять в компонентах-потомках стандартное поведение

    Чтобы класс компонента мог обрабатывать запросы необходимо:

    • Реализовать интерфейс BitrixMainEngineContractControllerable
    • Определить метод-действия с суффиксом Action
    • Реализовать метод configureActions (обычно возвращает пустой массив === конфигурацию по умолчанию)
    • Если нужно добавлять, обрабатывать ошибки, то стоит реализовать BitrixMainErrorable

    Примечание: При выполнении компонента в аяксовом режиме выполняются последовательно CBitrixComponent::onIncludeComponentLang, CBitrixComponent::onPrepareComponentParams и запуск действия с фильтрами..

    Внимание! При выполнении компонента в аяксовом режиме метод CBitrixComponent::executeComponent() не запускается.

    Пример

    <?php
    #components/bitrix/example/class.php
    
    use BitrixMainError;
    use BitrixMainErrorCollection;
    
    if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    class ExampleComponent extends CBitrixComponent implements BitrixMainEngineContractControllerable, BitrixMainErrorable
    {
    	/** @var ErrorCollection */
    	protected $errorCollection;
    
    	public function configureActions()
    	{
    		//если действия не нужно конфигурировать, то пишем просто так. И будет конфиг по умолчанию 
    		return [];
    	}
    
    	public function onPrepareComponentParams($arParams)
    	{
    		$this->errorCollection = new ErrorCollection();
    		
    		//подготовка параметров
    		//Этот код **будет** выполняться при запуске аяксовых-действий
    	}
    	
    	public function executeComponent()
    	{
    		//Внимание! Этот код **не будет** выполняться при запуске аяксовых-действий
    	}
    		
    	//в параметр $person будут автоматически подставлены данные из REQUEST
    	public function greetAction($person = 'guest')
    	{
    		return "Hi {$person}!";
    	}
    
    	//пример обработки ошибок
    	public function showMeYourErrorAction():? string
    	{
    		if (rand(3, 43) === 42)
    		{
    			$this->errorCollection[] = new Error('You are so beautiful or so handsome');
    			//теперь в ответе будут ошибки и будет автоматически выставлен статус ответа 'error'. 
    			
    			return  null;					
    		}
    
    		return "Ok";
    	}
    	
    	/**
    	 * Getting array of errors.
    	 * @return Error[]
    	 */
    	public function getErrors()
    	{
    		return $this->errorCollection->toArray();
    	}
    
    	/**
    	 * Getting once error with the necessary code.
    	 * @param string $code Code of error.
    	 * @return Error
    	 */
    	public function getErrorByCode($code)
    	{
    		return $this->errorCollection->getErrorByCode($code);
    	}
    }

    ajax.php

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

    Чтобы его реализовать:

    • Создать в корне компонента файл ajax.php
    • Определить метод-действия с суффиксом Action

    Сама логика работы контроллера полностью аналогична описанию контроллера модуля.

    <?php
    #components/bitrix/example/ajax.php
    
    if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    class ExampleAjaxController extends BitrixMainEngineController
    {
    	#в параметр $person будут автоматически подставлены данные из REQUEST
    	public function sayByeAction($person = 'guest')
    	{
    		return "Goodbye {$person}";
    	}
    
    	public function listUsersAction(array $filter)
    	{
    		$users = [];
    		//выборка пользователей по фильтру
    		//наполнения массива данными для ответа
    		
    		return $users;
    	}
    }

    Практика. Советы

    Для удобной отладки ошибок в жизненном цикле AJAX включайте debug => true в .settings.php, тогда вы сможете увидеть трейс ошибок, исключений.

    Если нужно:

    • отдать файл, то воспользуйтесь классами BitrixMainEngineResponseFile и BitrixMainEngineResponseBFile:
      class Controller extends EngineController
         {
          public function downloadAction($orderId)
           {
            //... find attached fileId by $orderId
            	return BitrixMainEngineResponseBFile::createByFileId($fileId);
           }
          public function downloadGeneratedTemplateAction()
           {
             //... generate file ... $generatedPath
            	return new BitrixMainEngineResponseFile(
            	   $generatedPath, 
            	   'Test.pdf',
            	   BitrixMainWebMimeType::getByFileExtension('pdf')
            	);
           }
      
          public function showImageAction($orderId)
           {
            	//... find attached imageId by $orderId
            	return BitrixMainEngineResponseBFile::createByFileId($imageId)
            		->showInline(true)
            	;
            }
         }
      
    • отдать отресайзенное изображение, то используйте BitrixMainEngineResponseResizedImage.

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

      class Controller extends EngineController
         {
          public function showAvatarAction($userId)
            {
               //... find attached imageId by $userId
            	return BitrixMainEngineResponseResizedImage::createByImageId($imageId, 100, 100);
            }
         }    
      
    • сгенерировать ссылку в контроллере на действие из этого же контроллера, то используйте BitrixMainEngineController::getActionUri
      public function getAction(File $file)
        {
          return [
             'file' => [
                'id' => $file->getId(),
         	  'name' => $file->getName(),
                'links' => [
                    'rename' => $this->getActionUri('rename', array('fileId' => $file->getId())),
                    ]				
               ]
           ];
        }    
      
      public function renameAction(File $file)
         {
            ...
         }
      
    • сгенерировать ссылку в контроллере на действие, которое будет отдавать контент, например, скачивание файла, то используйте BitrixMainEngineResponseDataTypeContentUri. Это нужно для интеграции с модулем REST.
      public function getAction(File $file)
         {
            return [
               'file' => [
                  'id' => $file->getId(),
                  'name' => $file->getName(),
                  'links' => [
                       'download' => new ContentUri($this->getActionUri('download', array('fileId' => $file->getId()))),
                               ]				
                          ]
                     ];
         }    
      
      public function downloadAction(File $file)
         {
           ...
         }
      
    • преобразовать данные SNAKE_CASE по стандарту в camelCase, то можно воспользоваться вспомогательными методами контроллера BitrixMainEngineController::convertKeysToCamelCase, либо явной настройкой BitrixMainEngineResponseConverter:
      public function getAction(File $file)
         {
            return [
                'file' => $this->convertKeysToCamelCase($fileData)
                     ];
         }    
      
      public function showInformationAction(File $file)
         {
            $converter = new BitrixMainEngineResponseConverter(Converter::OUTPUT_JSON_FORMAT & ~Converter::RECURSIVE);
          		
            return $converter->process($data);
         }

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

    Как в AJAX-действии использовать параметры компонента?

    Часто бывает, что необходимо в аякс-запросе получить у компонента те же параметры, которые были при его отображении на странице.

    • Нужно описать те параметры, которые нужно использовать в методе listKeysSignedParameters
            class ExampleComponent extends CBitrixComponent implements BitrixMainEngineContractControllerable
            {
            	protected function listKeysSignedParameters()
            	{
            		//перечисляем те имена параметров, которые нужно использовать в аякс-действиях					
            		return [
            			'STORAGE_ID',
            			'PATH_TO_SOME_ENTITY',
            		];
            	}
    • Получить подписанные параметры в шаблоне и, например, передать в ваш js класс компонента
            <!--template.php-->
            <script type="text/javascript">
            	new BX.ExampleComponent({
            		signedParameters: '<?= $this->getComponent()->getSignedParameters() ?>',
            		componentName: '<?= $this->getComponent()->getName() ?>'
            	});
            </script>
    • Вызывать BX.ajax.runComponentAction (как в примерах) c параметром signedParameters.
            BX.ajax.runComponentAction(this.componentName, action, {
            	mode: 'class',
            	signedParameters: this.signedParameters, //вот способ для передачи параметров компоненту.
            	data: data
            }).then(function (response) {
            	//some work
            });

    В итоге в вашем аякс-действии можно использовать параметры STORAGE_ID, PATH_TO_SOME_ENTITY. При этом параметры подписаны и целостность контролируется ядром.

    Если необходимо работать с подписанными параметрам внутри ajax.php, то используйте внутри действия контроллера метод Controller::getUnsignedParameters(), в нём будет массив распакованных данных.

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

    • Аякс-запрос из JavaScript’a к этому компоненту

    Практика. Постраничная навигация

    Чтобы организовать в аякс-действии постраничную навигацию, нужно в параметрах метода внедрить BitrixMainUIPageNavigation и вернуть BitrixMainEngineResponseDataTypePage.

    Пример:

    use BitrixMainEngineResponse;
    use BitrixMainUIPageNavigation;
    
    public function listChildrenAction(Folder $folder, PageNavigation $pageNavigation)
    {
    	$children = $folder->getChildren([
            'limit' => $pageNavigation->getLimit(),
            'offset' => $pageNavigation->getOffset(),
        ]);
    
    	return new ResponseDataTypePage('files', $children, function() use ($folder) {
    		//отложенный подсчет количества всего записей по фильтру		
    		return $folder->countChildren(); 	
    	});
    }

    Чтобы передать номер страницы в JS API, обратите внимание на navigation.

    BX.ajax.runAction('vendor:someController.listChildren', {
    	data: {
    		folderId: 12 
    	},
    	navigation: {
    		page: 3
    	}
    });

    Внимание! В ResponseDataTypePage($id, $items, $totalCount) $totalCount может быть как числом, так и Closure, которое может быть вычислено отложено. Это сделано из соображений производительности..

    Например, для rest вычисление общего количества требуется всегда, а для обычного аякса — это необязательно. Куда производительней и удобнее сделать отдельное аякс-действие для получения количества записей по определенном фильтру.

    Практика. Интеграция с модулем REST

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

    Для этого нужно лишь поправить конфиг модуля .settings.php.

    Важно! Это новый способ, должна быть проставлена зависимость от REST 18.5.1).

    <?php
    return [
    	'controllers' => [
    		'value' => [
    			'defaultNamespace' => '\Bitrix\Disk\Controller',
    			'restIntegration' => [
    				'enabled' => true,
    			],
    		],
    		'readonly' => true,
    	]
    ];

    Как использовать в ajax-действии CRestServer?

    Если вдруг ajax-действие должно использовать CRestServer для какой-то специфической задачи, то это можно легко решить, объявив одним из параметров CRestServer.

    Пример:

    public function getStorageForAppAction($clientName, CRestServer $restServer)
    {
    	$clientId = $restServer->getClientId();
    	...
    }
    

    Будьте внимательны, в примере выше действие не будет работать через обычный ajax, так как CRestServer $restServer там отсутствует и не может быть внедрен. Оно будет доступно только для модуля REST. Если же объявить его необязательным, то всё будет работать.

    public function getStorageForAppAction($clientName, CRestServer $restServer = null)
    {
    	if ($restServer)
    	{
    		$clientId = $restServer->getClientId();
    	}
    	...
    }
    

    Как понять, вызываются ли действия в REST или AJAX окружении?

    Может возникнуть задача, что нужно отличить в каком контексте сейчас выполняется действие: это REST или это AJAX? Для этого можно спросить у контролера:

    BitrixMainEngineController::getScope()
    
    //возможные варианты
    BitrixMainEngineController::SCOPE_REST
    BitrixMainEngineController::SCOPE_AJAX
    BitrixMainEngineController::SCOPE_CLI
    

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

    Скалярные и нескалярные параметры

    Рассмотрим на примере ajax-действие, в котором есть параметры:

    public function renameUserAction($userId, $newName = 'guest', array $groups = array(2))
    {
    	$user = User::getById($userId);
    	...
    	$user->rename($newName);
    	
    	return $user;
    }
    

    Как будут получены параметры метода?

    Скалярные параметры $userId, $newName, $groups будут получены автоматически из REQUEST.

    • Сопоставление регистрозависимое.
    • Если поиск не удался, но есть значение по умолчанию — оно будет использовано.
    • Сначала поиск в $_POST, после в $_GET.

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

    Как внедрить объекты (нескалярные параметры)?

    По умолчанию внедрить можно:

    • BitrixMainEngineCurrentUser
    • BitrixMainUIPageNavigation
    • CRestServer

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

    public function listChildrenAction(Folder $folder, PageNavigation $pageNavigation);
    public function listChildrenAction(Folder $folder, PageNavigation $navigation);
    public function listChildrenAction(Folder $folder, PageNavigation $nav, CRestServer $restServer);
    

    Внедрение своих типов

    Начнем с примера:

    class Folder extends Controller
    {
    	public function renameAction($folderId)
    	{
    		$folder = Folder::getById($folderId);
    		if (!$folder)
    		{
    			return null;
    		}
    		...
    	}
    	
    	public function downloadAction($folderId)
    	{
    		$folder = Folder::getById($folderId);
    		...
    	}
    	
    	public function deleteAction($folderId)
    	{
    		$folder = Folder::getById($folderId);
    		...
    	}
    }
    

    У нас есть обычный ajax-контроллер для некой папки Folder. Но все действия у нас в итоге производятся над объектом и везде у нас идёт попытка загрузки папки и т.д. Было бы здорово получать на вход метода сразу Folder $folder.

    class Folder extends Controller
    {
    	public function renameAction(Folder $folder);
    	public function downloadAction(Folder $folder);
    	public function deleteAction(Folder $folder);
    }
    

    И теперь это возможно:

    class Folder extends Controller
    {
    	public function getPrimaryAutoWiredParameter()
    	{
    		return new ExactParameter(
    			Folder::class, //полное имя класса подклассы, которого нужно создавать 
    			'folder', //конкретное имя параметра, который будет внедряться
    			function($className, $id){ //функция, которая создаст объект для внедрения. На вход приходит конкретный класс и $id
    				return Folder::loadById($id);
    			}
    		);
    	}
    }
    

    В js вызов:

    BX.ajax.runAction('folder.rename', {
    	data: {
    		id: 1 
    	}
    });
    

    Важно, в создающем замыкании после $className можно указывать сколько угодно параметров, которые требуются для создания объекта. При этом они будут связываться с данными из $_REQUEST так же, как и скаляры в обычных методах-действиях.

    class Folder extends Controller
    {
    	public function getPrimaryAutoWiredParameter()
    	{
    		return new ExactParameter(
    			Folder::class, 
    			'folder',
    			function($className, $entityId, $entityType){
    				return $className::buildByEntity($entityId, $entityType);
    			}
    		);
    	}
    	
    	public function workAction(Folder $folder);
    }
    

    В js вызов:

    BX.ajax.runAction('folder.work', {
    	data: {
    		entityId: 1,
    		entityType: 'folder-type'
    	}
    });
    

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

    class Folder extends Controller
    {
    	/**
    	 * @return Parameter[]
    	 */
    	public function getAutoWiredParameters()
    	{
    		return [
    			new ExactParameter(
    				Folder::class, 
    				'folder',
    				function($className, $id){
    					return $className::loadById($id);
    				}
    			),
    			new ExactParameter(
    				File::class, 
    				'file',
    				function($className, $fileId){
    					return $className::loadById($fileId);
    				}
    			),
    		];
    	}
    	
    	public function workAction(Folder $folder, File $file);
    }
    

    Есть ещё обобщенный способ описания внедрений:

    new BitrixMainEngineAutoWireParameter(
    	Folder::class, 
    	function($className, $mappedId){
    		return $className::buildById($mappedId);
    	}
    );
    

    Чуть подробнее: сначала объявили имя класса, подклассы которого мы будем пытаться создавать, когда встретим их в ajax-действиях. Анонимная функция будет заниматься созданием экземпляра.

    • $className — это конкретное имя класса, которое указано в type-hinting’e.
    • $mappedId — это значение, которое получено из $_REQUEST. При этом в $_REQUEST будет искаться folderId. Имя параметра, который мы будем искать в $_REQUEST, по умолчанию создается как {имя переменной} + Id.
    •       Folder $folder   => folderId
            Folder $nene     => neneId
            File $targetFile => targetFileId 
      

      Таким образом, если в модуле есть класс, например, Model от которого наследуются все сущности, то можно описать тип:

      new BitrixMainEngineAutoWireParameter(
      	Model::class, 
      	function($className, $mappedId){
      		/** @var Model $className */
      		return $className::buildById($mappedId);
      	}
      );
      

      И в дальнейшем легко и просто использовать type-hinting в своих ajax-действиях, сразу оперируя сущностями.

    Роутинг

    Доступно в модуле main начиная с версии 21.400.0. Для пользовательских модулей использование собственных роутов в папке модуля на данный момент не предусмотрено.

      Запуск

    Для запуска новой системы роутинга нужно перенаправить обработку 404 ошибок на файл routing_index.php в файле .htaccess:

    #RewriteCond %{REQUEST_FILENAME} !/bitrix/urlrewrite.php$
    #RewriteRule ^(.*)$ /bitrix/urlrewrite.php [L]
    
    RewriteCond %{REQUEST_FILENAME} !/bitrix/routing_index.php$
    RewriteRule ^(.*)$ /bitrix/routing_index.php [L]

      Конфигурация

    Файлы с конфигурацией маршрутов располагаются в папках /bitrix/routes/ и /local/routes/. Для подключения файла следует описать его в файле [ds].settings.php[/ds][di]Bitrix Framework имеет ряд специфичных настроек ядра, которые не имеют визуального интерфейса редактирования. Этот подход вызван тем, что изменение настроек или ошибка в них легко могут привести к неработоспособности системы (настройки подключения к базе данных, настройки кеширования и т.д.).

    Подробнее …[/di] в секции routing:

    'routing' => ['value' => [
      'config' => ['web.php', 'api.php']
    ]], 
    
    // подключатся файлы:
    // /bitrix/routes/web.php, /local/routes/web.php,  
    // /bitrix/routes/api.php, /local/routes/api.php
    

    Формат файла предполагает возврат замыкания, в которое передается объект конфигурации маршрутов:

    <?php
    
    use BitrixMainRoutingRoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        // маршруты
    };

    Поиск совпадений производится в том же порядке, в каком маршруты описаны в конфигурации.

    Маршруты

      Запросы

    Описание маршрутов начинается с определения метода запроса. Поддерживаются 3 комбинации методов:

    $routes->get('/countries', function () {
        // сработает только на GET запрос
    });
    
    $routes->post('/countries', function () {
        // сработает только на POST запрос
    });
    
    $routes->any('/countries', function () {
        // сработает на любой тип запроса
    });

    Для указания произвольного набора методов следует использовать метод methods:

    $routes->any('/countries', function () {
        // сработает на любой тип запроса
    })->methods(['GET', 'POST', 'OPTIONS']);

      Параметры

    Для определения параметра в адресе используются фигурные скобки:

    $routes->get('/countries/{country}', function ($country) {
        return "country {$country} response";
    });

    По умолчанию для параметров используется паттерн [^/]+. Для указания своего критерия используется метод маршрута where:

    $routes->get('/countries/{country}', function ($country) {
        return "country {$country} response";
    })->where('country', '[a-zA-Z]+');

    Если значение параметра может содержать /, то следует использовать паттерн .*:

    $routes->get('/search/{search}', function ($search) {
        return "search {$search} response";
    })->where('search', '.*'); 

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

    $routes->get('/countries/{country}', function ($country) {
        return "country {$country} response";
    })->default('country', 'Australia');
    
    // маршрут будет выбран при запросе /countries/
    // при этом параметр country будет иметь указанное значение

    Для удобства можно задать и вовсе не участвующие в формировании адреса параметры:

    $this->routes->get('/countries/hidden', function ($viewMode) {
        return 'countries response {$viewMode}';
    })->default('viewMode', 'custom');

    Доступ к значениям параметров маршрута получается через параметры контроллера или объект текущего маршрута:

    $routes->get('/countries/{country}', function ($country) {
        return "country {$country} response";
    });
    
    ...
    
    $app = BitrixMainApplication::getInstance(); 
    $country = $app->getCurrentRoute()->getParameterValue('country');    

      Имена

    Для удобства и систематизации списка маршрутов присвойте маршруту уникальный идентификатор — имя:

    $routes->get('/path/with/name', function () {
        return 'path with name';
    })->name('some_name');

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

      Контроллеры

    В роутинге поддерживается несколько видов контроллеров:

    1. Контроллеры BitrixMainEngineController:
      $routes->get('/countries', [SomeController::class, 'view']);
      
           // будет запущено действие SomeController::viewAction()
    2. Отдельные действия контроллеров BitrixMainEngineContractRoutableAction:
      $routes->get('/countries', SomeAction::class);
    3. Замыкания:
      $routes->get('/countries/', function () {
               return "countries response";
           });

      В качестве аргументов возможно указать объект запроса BitrixMainHttpRequest, объект текущего маршрута BitrixMainRoutingRoute, а также именованные параметры маршрута в любой комбинации:

      use BitrixMainHttpRequest;
      use BitrixMainRoutingRoute;
      
      $routes->get('/countries/{country}', function ($country, HttpRequest $request) {
          return "country {$country} response";
      });
      
      $routes->get('/countries/{country}', function (Route $route) {
          return "country {$route->getParameterValue('country')} response";
      });
    4. Для обратной совместимости с публичными страницами предусмотрен класс BitrixMainRoutingControllersPublicPageController:
       $routes->get('/countries/', new PublicPageController('/countries.php'));

    Группы

      Объединение в группы

    При схожих признаках у нескольких маршрутов рекомендуется объединять их в группы:

    $routes->group(function (RoutingConfigurator $routes) {
        $routes->get('/path1, function () {});
        $routes->get('/path2, function () {});
        $routes->get('/path3, function () {});
    });

    Само по себе объединение не влияет на поведение системы и имеет смысл именно при наличии общих признаков: параметры, prefix или name, которые будут рассмотрены ниже.

      Параметры группы

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

    $routes
        ->where('serviceCode', '[a-z0-9]+')
        ->group(function (RoutingConfigurator $routes) {
            $routes->get('/{serviceCode}/info', [ServicesController::class, 'info']);
            $routes->get('/{serviceCode}/stats', [ServicesController::class, 'stats']);
    });

      prefix группы

    Если у маршрутов совпадает начало адреса, то вынесите его общим для группы:

    $routes->prefix('about')->group(function (RoutingConfigurator $routes) {
        $routes->get('company', function () {});
        $routes->get('personal', function () {});
        $routes->get('contact', function () {});
    }); 

    В примере выше адреса маршрутов будут восприняты как /about/company, /about/personal и /about/contact, таким образом не придется дублировать общую часть.

      name группы

    Похожим на prefix образом работает формирование общей части в имени роутов:

    $routes
        ->prefix('about')
        ->name('about_')
        ->group(function (RoutingConfigurator $routes) {
            $routes->name('company')->get('company', function () {});
            $routes->name('personal')->get('personal', function () {});
            $routes->name('contact')->get('contact', function () {});
        })
    ;

    В примере выше будут установлены имена маршрутов about_company, about_personal и about_contact.

    Генерация ссылок

      Маршруты с именем

    При описании маршрута задайте для него уникальное имя:

    $routes->get('/countries/{country}', function () {
        return 'some output';
    })->name('country_detail');

    И используйте это имя для генерации ссылки:

    $router = BitrixMainApplication::getInstance()->getRouter();
    $url = $router->route('country_detail', ['country' => 'Australia']);
    
    // $url: /countries/Australia

    Имена выступают в роли уникальных идентификаторов. Если понадобится поменять формат ссылки, например поменять статическую часть:

    - $routes->get('/countries/{country}', function () {
    + $routes->get('/страны/{country}', function () {
        return 'some output';
    })->name('country_detail');

    То в этом случае не придется менять все ссылки на данный маршрут, поскольку они используют именно name для адресации.

      Маршруты без имени

    Если для маршрута не задано уникальное имя, то допустимо в ссылке указывать его адрес вручную. При наличии GET параметров может быть полезен хелпер BitrixMainRoutingRouter::url():

    $country = 'Australia';
    $router = BitrixMainApplication::getInstance()->getRouter();
    $url = $router->url("/contries/{$country}", [
        'showMoreDetails' => 1
    ]);
    
    // $url: /contries/Australia?showMoreDetails=1

    Логгеры

      Введение

    В ядро добавлены логгеры, реализующие интерфейс PSR-3:

    • базовый абстрактный класс BitrixMainDiagLogger, реализующий интерфейс PSR-3;
    • файловый логгер BitrixMainDiagFileLogger;
    • syslog логгер BitrixMainDiagSysLogger.

    Логгеры пользуются форматтером логов BitrixMainDiagLogFormatter, который делает замены плейсхолдеров в соответствии с PSR-3.

    Примечание: Библиотека доступна с версии main 21.900.0.

      Logger Interface

    Интерфейс PsrLogLoggerInterface довольно прост, это — набор функций логирования, поддерживающих уровни логирования. Уровни задаются константами PsrLogLogLevel::*.

    interface LoggerInterface
    {
        public function emergency($message, array $context = array());
        public function alert($message, array $context = array());
        public function critical($message, array $context = array());
        public function error($message, array $context = array());
        public function warning($message, array $context = array());
        public function notice($message, array $context = array());
        public function info($message, array $context = array());
        public function debug($message, array $context = array());
        public function log($level, $message, array $context = array());
    }

    В сообщении могут быть {плейсхолдеры}, которые заменяются данными из ассоциативного массива $context.

    Также полезным может быть интерфейс PsrLogLoggerAwareInterface, если вы хотите сообщить, что ваш объект готов принять логгер PSR-3:

    interface LoggerAwareInterface
    {
        public function setLogger(LoggerInterface $logger);
    }

      Логгеры в продукте

    Логгеры в продукте расширены по сравнению с PSR-3. Можно:

    • установить минимальный уровень логирования, ниже которого логгер ничего не выведет,
    • установить форматтер.

    Файловый логгер BitrixMainDiagFileLogger умеет записывать сообщения в файл, указанный в конструкторе. Если размер лога превышает указанный максимальный, производится однократная ротация файла. Ноль означает не делать ротацию. По умолчанию размер 1 Мб.

    $logger = new DiagFileLogger($logFile, $maxLogSize);
    $logger->setLevel(PsrLogLogLevel::ERROR);
    // выведет в лог
    $logger->error($message, $context);
    // НЕ выведет в лог
    $logger->debug($message, $context);

    Syslog логгер BitrixMainDiagSysLogger является надстройкой над функцией php syslog. Конструктор принимает параметры, использующиеся функцией openlog.

    $logger = new DiagSysLogger('Bitrix WAF', LOG_ODELAY, $facility);
    $logger->warning($message);

    На файловый логгер переведена функция AddMessage2Log и класс BitrixMainDiagFileExceptionHandlerLog, а также логирование в модуле Проактивная защита (security).

      Форматирование сообщения

    В логгер можно установить форматтер сообщения. По умолчанию используется форматтер BitrixMainDiagLogFormatter, реализующий интерфейс BitrixMainDiagLogFormatterInterface:

    interface LogFormatterInterface
    {
    	public function format($message, array $context = []): string;
    }

    Конструктор форматтера принимает параметры $showArguments = false, $argMaxChars = 30 (показывать значение аргументов в трейсе, максимальная длина аргумента).

    $logger = new MainDiagFileLogger(LOG_FILENAME, 0);
    $formatter = new MainDiagLogFormatter($showArgs);
    $logger->setFormatter($formatter);

    Основная задача форматтера — подставлять значения в плейсхолдеры сообщения из массива контекста. Форматтер умеет обрабатывать определенные плейсхолдеры:

    • {date} — текущее время * ;
    • {host} — HTTP host * ;
    • {exception} — объект исключения (Throwable);
    • {trace} — массив бектрейса;
    • {delimiter} — разделитель сообщений * .

    * — не обязательно передавать в массиве контекста, подставляется автоматически.

    $logger->debug(
        "{date} - {host}n{trace}{delimiter}n", 
        [
            'trace' => DiagHelper::getBackTrace(6, DEBUG_BACKTRACE_IGNORE_ARGS, 3)
        ]
    );

    Значения из массива контекста форматтер старается привести к удобному виду в зависимости от типа значения. Принимаются строки, массивы, объекты.

    Использование

    В простом виде объект может принять логгер снаружи, поддерживая интерфейс PsrLogLoggerAwareInterface. Можно использовать соответствующий трейт:

    use BitrixMainDiag;
    use PsrLog;
    
    class MyClass implements LogLoggerAwareInterface
    {
    	use LogLoggerAwareTrait;
    	
    	public function doSomething()
    	{
    	    if ($this->logger)
    	    {
    	        $this->logger->error('Error!');
    	    }
    	}
    }
    
    $object = new MyClass();
    $logger = new DiagFileLogger("/var/log/php/error.log");
    $object->setLogger($logger);
    $object->doSomething();

    Однако, не удобно менять код на работающем проекте, чтобы передать логгер в нужный объект. Для этого в классе логгера предусмотрена своя фабрика. На вход фабрике передается строка-идентификатор логгера:

    use BitrixMainDiag;
    use PsrLog;
    
    class MyClass implements LogLoggerAwareInterface
    {
    	use LogLoggerAwareTrait;
    	
    	public function doSomething()
    	{
    	    if ($logger = $this->getLogger())
    	    {
    	        $logger->error('Error!');
    	    }
    	}
    
    	protected function getLogger()
    	{
    		if ($this->logger === null)
    		{
    			$logger = DiagLogger::create('myClassLogger', [$this]);
    			$this->setLogger($logger);
    		}
    
    		return $this->logger;
    	}
    }

    Настройка

    В корневой секции файла .settings.php логгеры указываются в ключе loggers. Синтаксис описания совпадает с настройками ServiceLocator. Отличие состоит в том, что сервис-локатор является реестром, а здесь настраивается фабрика.

    В замыкание-конструктор constructor можно передать дополнительные параметры через второй параметр фабрики DiagLogger::create('logger.id', [$this]). Параметры позволяют гибко включать логирование в зависимости от переданных параметров, в том числе вызывать методы самого объекта.

    Дополнительно можно указать минимальный уровень журналирования (level) и собственный форматтер (formatter). Форматтер ищется в сервис локаторе по его идентификатору.

    // /bitrix/.settings.php
    return [
    	//...
    	'services' => [
    		'value' => [
    			//...
    			'formatter.Arguments' => [
    				'className' => 'BitrixMainDiagLogFormatter',
    				'constructorParams' => [true],
    			],
    		],
    		'readonly' => true,
    	]
    	'loggers' => [
    		'value' => [
    			//...
    			'main.HttpClient' => [
    //				'className' => '\Bitrix\Main\Diag\FileLogger',
    //				'constructorParams' => ['/home/bitrix/www/log.txt'],
    //				'constructorParams' => function () { return ['/home/bitrix/www/log.txt']; },
    				'constructor' => function (BitrixMainWebHttpClient $http, $method, $url) { 
    					$http->setDebugLevel(BitrixMainWebHttpDebug::ALL);
    					return new BitrixMainDiagFileLogger('/home/bitrix/www/log.txt');
    				},
    				'level' => PsrLogLogLevel::DEBUG,
    				'formatter' => 'formatter.Arguments',
    			],
    		],
    		'readonly' => true,
    	],
    	//...
    ];

    При указании замыкания-конструктора constructor желательно использовать файл .settings_extra.php, чтобы не потерять код при сохранении настроек из API.

    Существует PsrLogNullLogger, его можно установить, если не хочется писать if($logger) перед каждым вызовом логгера. Однако, следует учитывать, стоит ли делать дополнительную работу по формированию сообщения и контекста.

      Классы

    Список классов, поддерживающих фабрику логгеров:

    Класс Id логгера Передаваемые параметры
    BitrixMainWebHttpClient main.HttpClient [$this, $this->queryMethod, $this->effectiveUrl]

    Вложенные транзакции

    «Вложенные» транзакции доступны с версии main 22.200.0.

    Что такое транзакция

    Транза́кция (англ. transaction) — группа последовательных операций с базой данных, которая представляет собой логическую единицу работы с данными. Транзакция может быть выполнена либо целиком и успешно, соблюдая целостность данных и независимо от параллельно идущих других транзакций, либо не выполнена вообще, и тогда она не должна произвести никакого эффекта.

    Mysql в движке InnoDB отвечает требованиям ACID к обработке транзакций (в MyISAM не поддерживается):

    • Atomicity — атомарность
    • Consistency — согласованность
    • Isolation — изолированность
    • Durability — надежность

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

    Вот классический случай, когда проблема может/должна быть решена транзакцией:

    При отправке писем методом CEvent::Send файл может быть не отправлен, если в момент выполнения скрипта будет запущен крон, который отправляет письма. Это происходит потому, что в функции /bitrix/modules/main/lib/mail/event.php запись в таблицу b_event происходит до сохранения файла (58 строка и далее по условию).

    Старое правило

    Ранее, до обновления main 22.200.0, действовало правило:

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

    Такое правило было потому, что Mysql не поддерживает вложенные транзакции. Могла сложиться ситуация, когда конечный сценарий открывает транзакцию, вызываемый API тоже открывает свою транзакцию, в итоге все коммиты и роллбеки перемешиваются непредсказуемым образом.

    Сейчас в ядре появилась поддержка вложенных транзакций на уровне приложения (то есть при вызове соответствующего API, а не прямых запросов).

    Вложенные транзакции

    Для транзакций нужно использовать API драйвера БД BitrixMainDBConnection, методы startTransaction(), commitTransaction() и rollbackTransaction().

    Если транзакции вложенные, то повторные старты транзакций создают именованные точки сохранения MySQL SAVEPOINT. Промежуточные коммиты ничего не коммитят. Последний закрывающий коммит коммитит все произведенные с начала первой транзакции изменения.

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

    Поэтому мы действуем в парадигме «вложенные роллбеки не поддерживаются» и даем возможность конечному сценарию решить, как поступить правильно. При наступлении события вложенного роллбека происходит частичный откат к соответствующей точке сохранения ROLLBACK TO SAVEPOINT ... и выбрасывается исключение BitrixMainDBTransactionException.

    Далее возможны три сценария:

    • Исключение никто не ловит, скрипт прекращает работу по ошибке, MySQL автоматически откатывает все незакомиченные изменения.
    • Исключение обрабатывает вызывающий код, он решает, что внутренние роллбеки для правильной работы не важны, и продолжает работу с последующим коммитом своих изменений.
    • Исключение обрабатывает вызывающий код, он решает, что нет смысла продолжать, и откатывает свои изменения. При этом если вызывающий код сам является вложенной транзакцией, то генерируется новое исключение — и так до самого первого уровня в конечном сценарии.

    Пример кода:

    $conn = BitrixMainApplication::getConnection();
    
    $conn->query("truncate table test");
    
    try
    {
    	$conn->startTransaction();
    
    	$conn->query("insert into test values (1, 'one')");
    
    	// nested transaction
    	$conn->startTransaction();
    
    	$conn->query("insert into test values (2, 'two')");
    
    	if (true)
    	{
    		$conn->commitTransaction();
    	}
    	else
    	{
    		$conn->rollbackTransaction();
    	}
    	// end of nested transaction
    
    	$conn->commitTransaction();
    }
    catch(BitrixMainDBTransactionException $e)
    {
    	$conn->rollbackTransaction();
    }

    Новое правило

    В итоге новое правило работы с транзакциями выглядит так:

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

    Не стоит также забывать, что транзакции не должны быть сильно протяженными по времени, иначе можно легко попасть на deadlock транзакций вплоть до полной остановки приложения. Могут наблюдаться бесконечные откаты транзакций, если операция не может быть завершена до прекращения скрипта по тайм-ауту.

    ORM

    ORM (англ. Object-relational mapping, рус. Объектно-реляционное отображение) — технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования, создавая «виртуальную объектную базу данных». (Wikipedia)

    Введение

    В старом ядре на каждую сущность программируется свой GetList, Update, Add, Delete.

    Недостатки такой идеологии:

    • разный набор параметров;
    • разный синтаксис полей фильтров;
    • события могут быть или не быть;
    • иногда разный код под разные БД (Add).

    В D7:

    Операции выборки и сохранения в БД — однотипные, с одинаковыми параметрами и фильтрами. По возможности, таблицы сущностей обслуживаются минимумом нового кода. Стандартные события добавления/изменения/удаления доступны автоматически.

    Для реализации этих целей введены понятия:

    1. Cущности (BitrixMainEntityBase);
    2. Поля сущностей (BitrixMainEntityField и его наследники);
    3. Датаменеджер (BitrixMainEntityDataManager).

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

    Внимание! Перед началом разработки убедитесь что в выбранном вами модуле есть классы и методы ядра D7. Проверить это можно по наличию описания в документации по D7.

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

    Концепция, описание сущности

    При создании интернет-проектов на платформе Bitrix Framework доступен обширный функционал «из коробки», использовать который можно посредством вызовов API соответствующих модулей. При этом каждый модуль в концепции фреймворка является самостоятельной рабочей единицей, которая обеспечивает решение определенного круга задач.

    Как правило, API каждого модуля разрабатывается исходя из специфики задач, и нередко формат вызовов отличается от модуля к модулю. Чтобы свести эти различия к минимуму базовый функционал, который присутствует практически в каждом модуле, стандартизирован. Это CRUD-операции: Create, Read, Update, Delete (создание, чтение, обновление и удаление данных).

  • Концепция сущностей
  • Типизация полей
  • Primary & autoincrement & required
  • Маппинг имени колонки
  • Выражения ExpressionField
  • Пользовательские поля
  • Пример
  • Концепция сущностей

    Сущность — совокупность коллекции объектов с присущей им базовой (низкоуровневой) бизнес-логикой. Сущность обладает набором характеристик, значения которых подчиняются определенным правилам обработки.

    Например, сущность Пользователь — это множество пользователей с набором полей:

    • ID
    • Имя
    • Фамилия
    • Пароль
    • Логин
    • и т.д.

    При этом ID автоматически выдается базой данных, Имя и Фамилия ограничены длиной 50 символов, Логин должен состоять только из латинских букв, цифр и знака подчеркивания и так далее.

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

    Book
      ID int [autoincrement, primary]
      ISBN str [match: /[0-9X-]+/]
      TITLE str [max_length: 50]
      PUBLISH_DATE date

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

    Типизация полей

    Для конфигурации сущностей не используются средства разметки (xml, yml и т.п.), вместо этого используется php. Такой вариант дает максимум возможностей развития и гибкости.

    Так выглядит определение типов данных из приведенного выше примера:

    namespace SomePartnerMyBooksCatalog;
    
    use BitrixMainEntity;
    
    class BookTable extends EntityDataManager
    {
    	public static function getTableName()
    	{
    		return 'my_book';
    	}
    	
    	public static function getMap()
    	{
    		return array(
    			new EntityIntegerField('ID'),
    			new EntityStringField('ISBN'),
    			new EntityStringField('TITLE'),
    			new EntityDateField('PUBLISH_DATE')
    		);
    	}
    }

    Внимание! Несмотря на то, что в примере под сущностью подразумевается Книга (Book), к имени класса дописан постфикс: BookTable. Это сделано специально — имя описательного класса сущности всегда должно завершаться словом Table. Основное имя Book в этом же пространстве имен считается зарезервированным, в будущем предполагается использовать основное имя (в данном случае — класс Book) для представления элементов сущности в виде объектов (в настоящий момент данные сущности представлены массивами, как и в старых методах getList).

    За описание структуры сущности отвечает метод getMap(), который возвращает массив экземплярами полей.

    Каждый тип поля представлен в виде класса-наследника EntityScalarField — эти поля работают с простыми скалярными значениями, которые сохраняются в базу данных «как есть». По умолчанию доступно 8 таких типов:

    • Целое число
    • Число
    • Строка
    • Текст
    • Дата
    • Дата/Время
    • Да/Нет
    • Значение из списка

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

    Как правило, в конструкторе поля первым параметром передается имя поля, а вторым параметром — дополнительные настройки. Общие настройки будут рассмотрены далее в этой главе, но есть и специфическая настройка для BooleanField и EnumField:

    new EntityBooleanField('NAME', array(
    	'values' => array('N', 'Y')
    ))
    
    new EntityEnumField('NAME', array(
    	'values' => array('VALUE1', 'VALUE2', 'VALUE3')
    ))

    Для BooleanField, поскольку true и false не могут храниться в таком виде в БД, задается маппинг значений в виде массива, где первый элемент заменяет при хранении false, а второй true.

    Примечание: при описании сущности можно задать имя таблицы в методе getTableName, в данном примере это `my_book`. Если не определить этот метод, то имя таблицы будет сформировано автоматически из неймспейса и названия класса, для данной сущности это будет `b_somepartner_mybookscatalog_book`.

    Primary & autoincrement & required

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

    new EntityIntegerField('ID', array(
    	'primary' => true
    ))

    Примечание: составной первичный ключ тоже возможен. Например, в отношениях двух сущностей составным ключом будут ID обеих сущностей. Подробнее узнать об этом и посмотреть пример можно в разделе N:M relations.

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

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

    new EntityIntegerField('ID', array(
    	'primary' => true,
    	'autocomplete' => true
    ))

    Флаг ‘autocomplete’, для сущности означает, что при добавлении новой записи не нужно требовать от разработчика установки значения для данного поля. По умолчанию, такое требование применяется только к полям из первичного ключа, но можно попросить систему требовать установку и любого другого поля:

    new EntityStringField('ISBN', array(
    	'required' => true
    ))

    Теперь нельзя будет добавить новую книгу, не указав ее ISBN код.

    Маппинг имени колонки

    При описании сущности для уже имеющейся таблицы может возникнуть желание по-другому назвать колонку. Например, изначально в таблице `my_book` поле ISBN называлось как ISBNCODE, и старый код использует это название колонки в SQL запросах. Если в новом API необходимо оптимизировать название до более читаемого ISBN, то в этом поможет параметр ‘column_name’:

    new EntityStringField('ISBN', array(
    	'required' => true,
    	'column_name' => 'ISBNCODE'
    ))

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

    Выражения ExpressionField

    Предусмотрено не только хранение данных как есть, но и их преобразование при выборке. Допустим, возникла потребность наравне с датой издания сразу же получать возраст книги в днях. Хранить это число в БД накладно: придется каждый день пересчитывать обновлять данные. Можно просто считать возраст на стороне базы данных:

    SELECT DATEDIFF(NOW(), PUBLISH_DATE) AS AGE_DAYS FROM my_book

    Для этого нужно описать в сущности виртуальное поле, значение которого базируется на SQL-выражении с другим полем или полями:

    new EntityExpressionField('AGE_DAYS',
    	'DATEDIFF(NOW(), %s)', array('PUBLISH_DATE')
    )

    Первым параметром, как и у остальных полей, задается имя. Вторым параметром нужно передать текст SQL выражения, но при этом другие поля сущности нужно заменить на плейсхолдеры согласно формату sprintf. Третьим параметром нужно передать массив с именами полей сущности в определенном порядке, который был задан в выражении.

    Примечание: в качестве плейсхолдеров рекомендуется использовать `%s` или `%1$s`, `%2$s` и так далее. Например, когда в выражении EXPR участвует несколько полей (FIELD_X + FIELD_Y) * FIELD_X, то выражение можно описать так: '(%s + %s) * %s', [FIELD_X, FIELD_Y, FIELD_X]; или так: '(%1$s + %2$s) * %1$s', [FIELD_X, FIELD_Y].

    Очень часто выражения могут применяться для агрегации данных (например, COUNT(*) или SUM(FIELD)), такие примеры будут рассмотрены в главе Выборка данных.

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

    Пользовательские поля

    Помимо полей ScalarField и ExpressionField, сущность может содержать Пользовательские поля. Они конфигурируются через Административный интерфейс и не требуют дополнительного описания на стороне сущности. Все, что требуется указать в сущности, это выбранный Объект пользовательского поля:

    class BookTable extends EntityDataManager
    {
    	...
    	
    	public static function getUfId()
    	{
    		return 'MY_BOOK';
    	}
    	
    	...
    }

    Примечание: С версии 20.5.200 Главного модуля (main) добавлена поддержка SqlExpression значений для пользовательских полей в ORM.

    В дальнейшем именно этот идентификатор нужно указывать при прикреплении пользовательских полей к сущности:

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

    Пример

    По результатам данной главы получена следующая сущность:

    namespace SomePartnerMyBooksCatalog;
    
    use BitrixMainEntity;
    
    class BookTable extends EntityDataManager
    {
    	public static function getTableName()
    	{
    		return 'my_book';
    	}
    	
    	public static function getUfId()
    	{
    		return 'MY_BOOK';
    	}
    
    	public static function getMap()
    	{
    		return array(
    			new EntityIntegerField('ID', array(
    				'primary' => true,
    				'autocomplete' => true
    			)),
    			new EntityStringField('ISBN', array(
    				'required' => true,
    				'column_name' => 'ISBNCODE'
    			)),
    			new EntityStringField('TITLE'),
    			new EntityDateField('PUBLISH_DATE')
    		);
    	}
    }
    
    // код для создания таблицы в MySQL 
    // (получен путем вызова BookTable::getEntity()->compileDbTableStructureDump())
    CREATE TABLE `my_book` (
    	`ID` int NOT NULL AUTO_INCREMENT,
    	`ISBNCODE` varchar(255) NOT NULL,
    	`TITLE` varchar(255) NOT NULL,
    	`PUBLISH_DATE` date NOT NULL,
    	PRIMARY KEY(`ID`)
    );

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

    Внимание! Метод getMap используется только как получение первичной конфигурации сущности. Если вы хотите получить действительный список полей сущности, воспользуйтесь методом BookTable::getEntity()->getFields().

    Осталось только зафиксировать код сущности в проекте. Согласно общим правилам именования файлов в D7, код сущности нужно сохранить в файле: local/modules/somepartner.mybookscatalog/lib/book.php

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

    Примечание: В примере выше использована рекомендуемая форма записи данных. Старая форма записи в виде массива:

    'ID' => array(
    'data_type' => 'integer',
    'primary' => true,
    'autocomplete' => true,
    ),

    оставлена для совместимости. При инициализации все равно создаются объекты классов BitrixMainEntity*. Использовать можно оба варианта, правильней — через объекты.

    Операции с сущностями

    Для операций записи используются три метода уже описанного нами класса: BookTable::add, BookTable::update, BookTable::delete.

  • BookTable::add
  • BookTable::update
  • BookTable::delete
  • Валидаторы
  • События
  • Форматирование значений
  • Вычисляемые значения
  • Предупреждения об ошибках
  • BookTable::add

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

    namespace SomePartnerMyBooksCatalog;
    
    use BitrixMainType;
    
    $result = BookTable::add(array(
    	'ISBN' => '978-0321127426',
    	'TITLE' => 'Patterns of Enterprise Application Architecture',
    	'PUBLISH_DATE' => new TypeDate('2002-11-16', 'Y-m-d')
    ));
    
    if ($result->isSuccess())
    {
    	$id = $result->getId();
    }

    Метод возвращает объект результата EntityAddResult, и в примере выше показано, как проверить успешность добавления и получить ID добавленной записи.

    Примечание. Для значений полей типов DateField и DateTimeField, а также для пользовательских полей Дата и Дата со временем, необходимо использовать объекты классов BitrixMainTypeDate и BitrixMainTypeDateTime. По умолчанию в конструктор передается строковая дата в формате сайта, но можно и явно указать формат передаваемой даты.

    BookTable::update

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

    $result = BookTable::update($id, array(
    	'PUBLISH_DATE' => new TypeDate('2002-11-15', 'Y-m-d')
    ));

    В примере исправлена неправильно указанная при добавлении дата. В качестве результата возвращается объект EntityUpdateResult, у которого так же есть проверочный метод isSuccess() (не было ли ошибок в запросе), и, дополнительно, можно узнать была ли запись фактически обновлена: getAffectedRowsCount().

    BookTable::delete

    Для удаления записи нужен только первичный ключ:

    $result = BookTable::delete($id);

    Результаты операции

    Если во время операции произошла одна или несколько ошибок, их текст можно получить из результата:

    $result = BookTable::update(...);
    
    if (!$result->isSuccess())
    {
    	$errors = $result->getErrorMessages();
    }

    Значения по умолчанию

    Бывает, что у большинства новых записей значение какого-то поля всегда одно и то же, или вычисляется автоматически. Пусть у каталога книг дата издания/публикации по умолчанию будет сегодняшним днем (логично добавлять книгу в каталог сразу в день ее выхода). Вернемся к описанию поля в сущности и используем параметр `default_value`:

    new EntityDateField('PUBLISH_DATE', array(
    	'default_value' => new TypeDate
    ))

    Теперь при добавлении записи без явного указания даты издания ее значением будет текущий день:

    $result = BookTable::add(array(
    	'ISBN' => '978-0321127426',
    	'TITLE' => 'Some new book'
    ));

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

    new EntityDateField('PUBLISH_DATE', array(
    	'default_value' => function () {
    		// figure out last friday date
    		$lastFriday = date('Y-m-d', strtotime('last friday'));
    		return new TypeDate($lastFriday, 'Y-m-d');
    	}
    ))

    Значением параметра `default_value` может быть любой `callable`: имя функции, массив из класса/объекта и названия метода, или анонимная функция.

    Валидаторы

    Перед записью новых данных в БД нужно обязательно проверять их на корректность. Для этого предусмотрены валидаторы:

    new EntityStringField('ISBN', array(
    	'required' => true,
    	'column_name' => 'ISBNCODE',
    	'validation' => function() {
    		return array(
    			new EntityValidatorRegExp('/[d-]{13,}/')
    		);
    	}
    ))

    Теперь при добавлении и изменении записей ISBN будет проверен по шаблону [d-]{13,} — код должен содержать только цифры и дефис, минимум 13 цифр.

    Валидация задается параметром 'validation' в конструкторе поля и представляет собой callback, который возвращает массив валидаторов.

    Примечание: Почему validation — callback, а не сразу массив валидаторов? Это своего рода отложенная загрузка: валидаторы будут инициализированы только тогда, когда действительно нужна будет валидация данных. В большинстве же случаев — при выборке данных из БД — валидация не нужна.

    В качестве валидатора принимается наследник EntityValidatorBase или любой callable, который должен вернуть true, или текст ошибки, или объект EntityFieldError (в случае, если вы хотите использовать собственный код ошибки).

    Точно известно, что в ISBN коде должно быть 13 цифр, эти цифры могут разделять несколько дефисов:

    978-0321127426
    978-1-449-31428-6
    9780201485677

    Чтобы удостовериться, что цифр там именно 13, напишем свой собственный валидатор:

    new EntityStringField('ISBN', array(
    	'required' => true,
    	'column_name' => 'ISBNCODE',
    	'validation' => function() {
    		return array(
    			function ($value) {
    				$clean = str_replace('-', '', $value);
    				
    				if (preg_match('/^d{13}$/', $clean))
    				{
    					return true;
    				}
    				else
    				{
    					return 'Код ISBN должен содержать 13 цифр.';
    				}
    			}
    		);
    	}
    ))

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

    new EntityStringField('ISBN', array(
    	'required' => true,
    	'column_name' => 'ISBNCODE',
    	'validation' => function() {
    		return array(
    			function ($value, $primary, $row, $field) {
    				// value - значение поля
    				// primary - массив с первичным ключом, в данном случае [ID => 1]
    				// row - весь массив данных, переданный в ::add или ::update
    				// field - объект валидируемого поля - EntityStringField('ISBN', ...)
    			}
    		);
    	}
    ))

    С таким набором данных можно произвести гораздо больший спектр сложных проверок.

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

    // описываем валидатор в поле сущности
    new EntityStringField('ISBN', array(
    	'required' => true,
    	'column_name' => 'ISBNCODE',
    	'validation' => function() {
    		return array(
    			function ($value) {
    				$clean = str_replace('-', '', $value);
    
    				if (preg_match('/^d{13}$/', $clean))
    				{
    					return true;
    				}
    				else
    				{
    					return 'Код ISBN должен содержать 13 цифр.';
    				}
    			},
    			function ($value, $primary, $row, $field) {
    				// проверяем последнюю цифру
    				// ...
    				// если цифра неправильная - возвращаем особую ошибку
    				return new EntityFieldError(
    					$field, 'Контрольная цифра ISBN не сошлась', 'MY_ISBN_CHECKSUM'
    				);
    			}
    		);
    	}
    ))
    // выполняем операцию
    $result = BookTable::update(...);
    
    if (!$result->isSuccess())
    {
    	// смотрим, какие ошибки были выявлены
    	$errors = $result->getErrors();
    	
    	foreach ($errors as $error)
    	{
    		if ($error->getCode() == 'MY_ISBN_CHECKSUM')
    		{
    			// сработал наш валидатор
    		}
    	}
    }

    По умолчанию есть 2 стандартных кода ошибки: BX_INVALID_VALUE, если сработал валидатор, и BX_EMPTY_REQUIRED, если при добавлении записи не указано обязательное required поле.

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

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

    • EntityValidatorRegExp — проверка по регулярному выражению,
    • EntityValidatorLength — проверка на минимальную/максимальную длину строки,
    • EntityValidatorRange — проверка на минимальное/максимальное значение числа,
    • EntityValidatorUnique — проверка на уникальность значения

    Описанные валидаторы не применимы к Пользовательским полям — проверка их значений конфигурируется в настройках поля через административный интерфейс.

    События

    В примере с валидаторами одной из проверок поля ISBN была проверка на наличие 13 цифр. Помимо цифр, в ISBN коде могут встречаться дефисы, но с технической точки зрения они не несут никакой ценности. Чтобы хранить в БД «чистые» данные — только 13 цифр, без дефисов — можно воспользоваться внутренним обработчиком события:

    class BookTable extends EntityDataManager
    {
    	...
    	
    	public static function onBeforeAdd(EntityEvent $event)
    	{
    		$result = new EntityEventResult;
    		$data = $event->getParameter("fields");
    
    		if (isset($data['ISBN']))
    		{
    			$cleanIsbn = str_replace('-', '', $data['ISBN']);
    			$result->modifyFields(array('ISBN' => $cleanIsbn));
    		}
    
    		return $result;
    	}
    }

    Метод onBeforeAdd, определенный в сущности, автоматически распознается системой как обработчик события «перед добавлением», и в нем можно изменить данные или провести дополнительные проверки. В приведенном примере мы изменили поле ISBN посредством метода `modifyFields`.

    // до преобразования
    978-0321127426
    978-1-449-31428-6
    9780201485677
    
    // после преобразования
    9780321127426
    9781449314286
    9780201485677

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

    'validation' => function() {
    	return array(
    		//function ($value) {
    		//	$clean = str_replace('-', '', $value);
    		//
    		//	if (preg_match('/^d{13}$/', $clean))
    		//	{
    		//		return true;
    		//	}
    		//	else
    		//	{
    		//		return 'Код ISBN должен содержать 13 цифр.';
    		//	}
    		//},
    		new EntityValidatorRegExp('/d{13}/'),
    		...
    	);
    }

    Помимо изменения данных, в обработчике события можно удалить данные или вовсе прервать выполнение операции. Например, необходимо запретить обновление ISBN кода для уже существующих в каталоге книг. Сделать это можно в событии onBeforeUpdate двумя способами:

    public static function onBeforeUpdate(EntityEvent $event)
    {
       $result = new EntityEventResult;
       $data = $event->getParameter("fields");
    
       if (isset($data['ISBN']))
       {
          $result->unsetFields(array('ISBN'));
       }
    
       return $result;
    }

    В таком варианте ISBN будет «тихо» удален из набора данных, будто его и не передавали. Второй способ запретить его обновлять — сгенерировать ошибку:

    public static function onBeforeUpdate(EntityEvent $event)
    {
    	$result = new EntityEventResult;
    	$data = $event->getParameter("fields");
    
    	if (isset($data['ISBN']))
    	{
    		$result->addError(new EntityFieldError(
    			$event->getEntity()->getField('ISBN'),
    			'Запрещено менять ISBN код у существующих книг'
    		));
    	}
    
    	return $result;
    }

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

    public static function onBeforeUpdate(EntityEvent $event)
    {
    	$result = new EntityEventResult;
    	$data = $event->getParameter("fields");
    
    	if (...) // комплексная проверка данных
    	{
    		$result->addError(new EntityEntityError(
    			'Невозможно обновить запись'
    		));
    	}
    
    	return $result;
    }

    В примерах использовались два события: onBeforeAdd и onBeforeUpdate, всего же таких событий девять:

    • OnBeforeAdd (параметры: fields)
    • OnAdd (параметры: fields)
    • OnAfterAdd (параметры: fields, primary)
    • OnBeforeUpdate (параметры: primary, fields)
    • OnUpdate (параметры: primary, fields)
    • OnAfterUpdate (параметры: primary, fields)
    • OnBeforeDelete (параметры: primary)
    • OnDelete (параметры: primary)
    • OnAfterDelete (параметры: primary)

    Порядок вызова событий и допустимые действия в обработчиках каждого из них:

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

    $eventManager = MainEventManager::getInstance();
    $eventManager->addEventHandler(
    	"module_name",
    	"SomePartnerMyBooksCatalogBook::onBeforeUpdate",
    	<ваш callback>
    );

    Форматирование значений

    Иногда может возникнуть необходимость хранить данные в одном формате, а работать с ними в программе уже в другом. Самый распространенный пример: работа с массивом и его сериализация перед сохранением в БД. На этот случай предусмотрены параметры поля 'save_data_modification' и 'fetch_data_modification'. Определяются они аналогично валидаторам, через callback.

    На примере каталога книг опишем текстовое поле EDITIONS_ISBN: оно будет хранить коды ISBN других изданий книги, если таковые имеются:

    new EntityTextField('EDITIONS_ISBN', array(
    	'save_data_modification' => function () {
    		return array(
    			function ($value) {
    				return serialize($value);
    			}
    		);
    	},
    	'fetch_data_modification' => function () {
    		return array(
    			function ($value) {
    				return unserialize($value);
    			}
    		);
    	}
    ))

    В параметре save_data_modification мы указали сериализацию значения перед сохранением в БД, а в параметре fetch_data_modification рассериализацию при выборке из БД. Теперь при написании бизнес-логики вы можете просто работать с массивом, не отвлекаясь на вопросы конвертации.

    Внимание! Прежде чем создать у себя сериализованное поле, подумайте не помешает ли сериализация при фильтрации или связывании таблиц. Искать по одиночному значению в WHERE среди сериализованных строк крайне неэффективно. Возможно, вам больше подойдет нормализованная схема хранения данных.

    Поскольку сериализация — это наиболее типичный пример для конвертации значений, она вынесена в отдельный параметр serialized:

    new EntityTextField('EDITIONS_ISBN', array(
    	'serialized' => true
    ))

    Но вы по-прежнему можете описать свои callable для других вариантов модификации данных.

    Вычисляемые значения

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

    UPDATE my_book SET READERS_COUNT = READERS_COUNT + 1 WHERE ID = 1

    Если описать числовое поле READERS_COUNT в сущности, то инкремент счетчика можно будет запустить следующим образом:

    BookTable::update($id, array(
    	'READERS_COUNT' => new DBSqlExpression('?# + 1', 'READERS_COUNT')
    ));

    Плейсхолдер ?# означает, что следующим аргументом в конструкторе идет идентификатор БД — имя базы данных, таблицы или колонки, и это значение будет экранировано соответствующим образом. Для всех изменяемых параметров рекомендуется обязательно использовать плейсхолдеры — такой подход поможет избежать проблем с SQL инъекциями.

    Например, если инкрементируемое число читателей переменно, то лучше описать выражение так:

    // правильно
    BookTable::update($id, array(
    	'READERS_COUNT' => new DBSqlExpression('?# + ?i', 'READERS_COUNT', $readersCount)
    ));
    
    // неправильно
    BookTable::update($id, array(
    	'READERS_COUNT' => new DBSqlExpression('?# + '.$readersCount, 'READERS_COUNT')
    ));

    Список доступных на данный момент плейсхолдеров:

    • ? или ?s — значение экранируется и заключается в кавычки ‘
    • ?# — значение экранируется как идентификатор
    • ?i — значение приводится к integer
    • ?f — значение приводится к float

    Предупреждения об ошибках

    В предыдущих примерах есть нюанс: запрос на обновление данных вызывается без проверки результата:

    // вызов без проверки успешности выполнения запроса
    BookTable::update(...);
    
    // с проверкой
    $result = BookTable::update(...);
    if (!$result->isSuccess())
    {
    	// обработка ошибки
    }

    Несомненно, второй вариант более предпочтителен с точки зрения контроля происходящего. Но если код выполняется только в режиме агента, нам некому и незачем показывать список возникших в процессе валидации ошибок. В таком случае, если запрос не прошел из-за «проваленной» валидации, и не была вызвана проверка isSuccess(), система сгенерирует E_USER_WARNING со списком ошибок, который можно будет увидеть в логе сайта (если соответствующим образом настроить .settings.php).

    По результатам данной главы произошли некоторые изменения в описании сущности, теперь оно выглядит так:

    namespace SomePartnerMyBooksCatalog;
    
    use BitrixMainEntity;
    use BitrixMainType;
    
    class BookTable extends EntityDataManager
    {
    	public static function getTableName()
    	{
    		return 'my_book';
    	}
    	
    	public static function getUfId()
    	{
    		return 'MY_BOOK';
    	}
    
    	public static function getMap()
    	{
    		return array(
    			new EntityIntegerField('ID', array(
    				'primary' => true,
    				'autocomplete' => true
    			)),
    			new EntityStringField('ISBN', array(
    				'required' => true,
    				'column_name' => 'ISBNCODE',
    				'validation' => function() {
    					return array(
    						new EntityValidatorRegExp('/d{13}/'),
    						function ($value, $primary, $row, $field) {
    							// проверяем последнюю цифру
    							// ...
    							// если цифра неправильная - возвращаем особую ошибку
    							return new EntityFieldError(
    								$field, 'Контрольная цифра ISBN не сошлась', 'MY_ISBN_CHECKSUM'
    							);
    						}
    					);
    				}
    			)),
    			new EntityStringField('TITLE'),
    			new EntityDateField('PUBLISH_DATE', array(
    				'default_value' => function () {
    					// figure out last friday date
    					$lastFriday = date('Y-m-d', strtotime('last friday'));
    					return new TypeDate($lastFriday, 'Y-m-d');
    				}
    			)),
    			new EntityTextField('EDITIONS_ISBN', array(
    				'serialized' => true
    			)),
    			new EntityIntegerField('READERS_COUNT')
    		);
    	}
    
    	public static function onBeforeAdd(EntityEvent $event)
    	{
    		$result = new EntityEventResult;
    		$data = $event->getParameter("fields");
    
    		if (isset($data['ISBN']))
    		{
    			$cleanIsbn = str_replace('-', '', $data['ISBN']);
    			$result->modifyFields(array('ISBN' => $cleanIsbn));
    		}
    
    		return $result;
    	}
    }

    Скопировав этот код, вы можете поэкспериментировать со всеми описанными выше возможностями.

    Объекты

    Для начала использования объектов достаточно иметь лишь описанную сущность. Просто замените fetch на fetchObject в своем коде:

    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    

    Теперь $book — это полноценный объект сущности Book, наделенный множеством методов по манипуляции с собственными данными и отношениями с другими сущностями.

    Класс объекта

    Все объекты сущностей являются наследниками класса BitrixMainORMObjectifyEntityObject, при этом у каждой сущности — свой собственный класс для объектов. По умолчанию, такой класс создается автоматически, на лету. Если вы уже сгенерировали аннотации классов ORM, то IDE раскроет этот момент:

    Как видно, класс объекта EO_Book находится в том же пространстве имен, что и класс Table, так же назван, но вместо суффикса Table имеет префикс EO_ (аббревиатура EntityObject). Такой префикс добавлен из соображений обратной совместимости: в существующих проектах уже может быть класс Book или конструкция вида

    use SomeAnotherBook;

    что приведет к конфликту повторного использования слова Book. Мы посчитали префикс EO_ достаточно уникальным, тем более в обычном случае использование знака _ противоречит стандартам именования кода, и конфликтов с вручную описанными классами быть не должно.

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

    //Файл bitrix/modules/main/lib/test/typography/book.php
    
    namespace BitrixMainTestTypography;
    
    class Book extends EO_Book
    {
    }
    

    Ключевой момент — наследование от базового виртуального класса объекта EO_Book.

    Сущность нужно уведомить о новом классе следующим образом:

    //Файл bitrix/modules/main/lib/test/typography/booktable.php
    
    namespace BitrixMainTestTypography;
    
    class BookTable extends BitrixMainORMDataDataManager
    {
    	public static function getObjectClass()
    	{
    		return Book::class;
    	}
    	//...
    }

    Теперь метод fetchObject будет возвращать объекты класса BitrixMainTestTypographyBook. А после перегенерации аннотаций новый класс начнет показывать IDE:

    Определять собственные классы объектов стоит лишь тогда, когда вам требуется явно использовать имя класса или расширить класс своим функционалом. Не рекомендуется использовать имена стандартных классов в своем коде, например instanceof EO_Book, new EO_Book или EO_Book::class. В таких случаях рекомендуется описать свой класс с «красивым» именем, соответствующий стандартам именования, либо использовать обезличенные методы BookTable::getObjectClass(), BookTable::createObject(), BookTable::wakeUpObject() и т.п.

    В своем классе можно не только добавлять свой функционал, но и переопределять стандартные именованные методы. Не следует лишь описывать в классе свойства с именами primary, entity и dataClass, поскольку эти имена уже используются базовым классом.

    Именованные методы

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

    $book->getTitle();
    $book->setTitle($value);
    $book->remindActualTitle();
    $book->resetTitle();
    // и т.д.
    

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

    Такой подход был выбран по нескольким причинам:

    • инкапсуляция — можно контролировать доступ индивидуально к каждому полю;
    • удобство использования — не нужно помнить названия полей каждой сущности наизусть, IDE подскажет их и ускорит ввод автодополнением;
    • читаемость кода — такие записи выглядят целостно и исчерпывающе.

    При этом для всех методов существует альтернатива в виде универсальных методов, принимающих имя поля в качестве одного из аргументов:

    $fieldName = 'TITLE';
    
    $book->get($fieldName);
    $book->set($fieldName, $value);
    $book->remindActual($fieldName);
    $book->reset($fieldName);
    // и т.д.
    

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

    Если вы описали свой класс для объекта и переопределили какой-либо именованный метод, то при использовании универсального метода будет вызван ваш явно заданный именованный метод:

    namespace BitrixMainTestTypography;
    
    class Book extends EO_Book
    {
    	public function getTitle()
    	{
    		return 'custom title';
    	}
    }
    
    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    echo $book->getTitle(); // выведет 'custom title'
    echo $book->get('TITLE'); // тоже выведет 'custom title'
    

    Исключение составляет метод fill, в нем именованный метод вызван не будет. Архитектурно метод рассчитан на оптимизацию работы с базой данных, и в случае заполнения нескольких полей одновременно вызовы именованных методов по отдельности создадут дополнительную нагрузку.

    Внутренняя реализация именованных методов основана на magic-методе __call. Альтернативой могла быть кодогенерация — компиляция классов со всеми методами и их последующее кеширование. Мы сделали выбор в пользу magic по следующим причинам:

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

    Минусом magic-методов можно назвать увеличенный расход ресурсов процессора, но в особых случаях это можно решить явным определением часто используемых методов, как это сделано с методом getId в базовом классе. В то же время, такой ресурс проще всего поддается горизонтальному масштабированию, позволяя добавлять новые машины вместо бесконечного «апгрейда» одной существующей.

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

    Приведение типов

    В объектах действует строгое приведение значений к типу поля. Это значит, что числа всегда будут числами, а строки — строками:

    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    var_dump($book->getId());
    // выведет int 1
    
    var_dump($book->getTitle());
    // выведет string 'Title 1' (length=7)
    

    Особое внимание стоит уделить типу BooleanField: в качестве значения ожидается true или false, несмотря на то, что фактически в базе могут храниться другие значения:

    //(new BooleanField('IS_ARCHIVED'))
    //	->configureValues('N', 'Y'),
    
    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    var_dump($book->getIsArchived());
    // выведет boolean true
    
    // при установке значений тоже ожидается boolean
    $book->setIsArchived(false);
    

    Чтение (get, require, remindActual, primary, collectValues, runtime)

    • get

      Чтение данных реализовано несколькими методами. Самый простой из них возвращает значение поля либо null в случае его отсутствия (например, если поле не было указано в select при выборке):

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      $title = $book->getTitle();
      
    • require

      Если вы уверены, что поле должно быть заполнено значением, и без этого значения продолжать выполнение сценария не имеет смысла, можете требовать это значение методом require:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      $title = $book->requireTitle();
      

      В данном случае результат requireTitle() не будет отличаться от вышеприведенного getTitle(). А следующий пример уже закончится исключением, поскольку поле не будет заполнено значением:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1, ['select' => ['ID', 'PUBLISHER_ID', 'ISBN']])
      	->fetchObject();
      
      $title = $book->requireTitle();
      // SystemException: "TITLE value is required for further operations"
      
    • remindActual

      Еще один «геттер» remindActual пригодится вам при переустановке значения, чтобы отличить оригинальное значение от установленного в процессе сеанса и еще несохраненного в базу данных:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      echo $book->getTitle();
      // выведет "Title 1"
      
      $book->setTitle("New title");
      
      echo $book->getTitle();
      // выведет "New title"
      
      echo $book->remindActualTitle();
      // выведет "Title 1"
      

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

      $fieldName = 'TITLE';
      
      $title = $book->get($fieldName);
      $title = $book->require($fieldName);
      $title = $book->remindActual($fieldName);
      
    • primary

      Системный «геттер» primary реализован в виде виртуального read-only свойства, чтобы не использовать метод getPrimary(), резервируя тем самым имя поля PRIMARY с соответствующим именованным геттером. Свойство возвращает значения первичного ключа в формате массива независимо от того, составной ли первичный ключ или одиночный:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      $primary = $book->primary;
      // вернет ['ID' => 1]
      
      $id = $book->getId();
      // вернет 1
      
    • collectValues

      Метод collectValues используется для получения всех значений объекта в виде массива.

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      $values = $book->collectValues();
      

      В данном примере вернутся все имеющиеся значения. Если для некоторых полей значения были переустановлены через «сеттер», но еще не сохранены, то вернутся именно эти значения. Для неизмененных полей будут взяты актуальные значения.

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

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      $values = $book->collectValues(BitrixMainORMObjectifyValues::ACTUAL);
      // вернет только актуальные значения, без учета еще не сохраненных
      
      $values = $book->collectValues(BitrixMainORMObjectifyValues::CURRENT);
      // вернет только текущие значения, еще не сохраненные в базу данных
      
      $values = $book->collectValues(BitrixMainORMObjectifyValues::ALL);
      // равнозначно вызову collectValues() без параметров - сначала CURRENT, затем ACTUAL
      

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

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      $values = $book->collectValues(
      	BitrixMainORMObjectifyValues::CURRENT,
      	BitrixMainORMFieldsFieldTypeMask::SCALAR
      );
      // вернутся только измененные значения скалярных полей
      
      $values = $book->collectValues(
      	BitrixMainORMObjectifyValues::ALL,
      	BitrixMainORMFieldsFieldTypeMask::ALL & ~BitrixMainORMFieldsFieldTypeMask::USERTYPE
      );
      // вернутся значения всех полей, кроме пользовательских
      
    • runtime

      Для runtime полей, создаваемых в рамках отдельных запросов, предусмотрен только универсальный «геттер» get:

      $author = BitrixMainTestTypographyAuthorTable::query()
      	->registerRuntimeField(
      		new BitrixMainEntityExpressionField(
      			'FULL_NAME', 'CONCAT(%s, " ", %s)', ['NAME', 'LAST_NAME']
      		)
      	)
      	->addSelect('ID')
      	->addSelect('FULL_NAME')
      	->where('ID', 17)
      	->fetchObject();
      
      echo $author->get('FULL_NAME');
      // выведет 'Name 17 Last name 17'
      

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

    Запись (set, reset, unset)

    • set

      Установка значения происходит схожим образом:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      $book->setTitle("New title");
      

      При этом объект запоминает свои исходные значения. С этого момента доступ к текущему значению осуществляется через основной «геттер» get, а к изначальному, актуальному для базы данных значению, через вспомогательный «геттер» remindActual:

      $book->getTitle(); // текущее значение
      $book->remindActualTitle(); // актуальное для базы данных значение
      

      Значения первичного ключа primary можно устанавливать только в новых объектах, в существующих изменить его будет нельзя. При такой необходимости придется создать новый объект и удалить старый. Также не сработает установка полей BitrixMainORMFieldsExpressionField, поскольку их значения рассчитываются автоматически и не подлежат изменению извне.

      При установке значения, не отличающегося от актуального, значение фактически не будет изменено и не попадет в SQL-запрос при сохранении объекта.

    • reset

      Чтобы отменить установку нового значения и вернуть исходное, можно воспользоваться вспомогательным «сеттером» reset:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      echo $book->getTitle();
      // выведет "Title 1"
      
      $book->setTitle("New title");
      
      echo $book->getTitle();
      // выведет "New title"
      
      $book->resetTitle();
      
      echo $book->getTitle();
      // выведет "Title 1"
      
    • unset

      Еще один вспомогательный «сеттер» unset удалит значение объекта так, будто бы оно никогда не выбиралось из базы данных и не было установлено:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      echo $book->getTitle();
      // выведет "Title 1"
      
      $book->unsetTitle();
      
      echo $book->getTitle();
      // null
      

      Для «сеттеров» тоже есть универсальные варианты вызова с именем поля в качестве аргумента:

      $fieldName = 'TITLE';
      
      $book->set($fieldName, "New title");
      $book->reset($fieldName);
      $book->unset($fieldName);
      

      Все операции по изменению значения приводят к изменениям только во время сеанса. Чтобы зафиксировать изменения в базе данных, объект нужно сохранить — об этом читайте в уроке Создание и редактирование (save, new).

    Проверки (isFilled, isChanged, has)

    • isFilled

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

      use BitrixMainTestTypographyBook;
      
      // актуальными считаются значения из методов fetch* и wakeUp
      // в примере при инициализации объекта передается только первичный ключ
      $book = Book::wakeUp(1);
      
      var_dump($book->isTitleFilled());
      // false
      
      $book->fillTitle();
      
      var_dump($book->isTitleFilled());
      // true
    • isChanged

      Метод isChanged отвечает на вопрос, было ли установлено новое значение в течение сеанса:

      use BitrixMainTestTypographyBook;
      
      // объект может иметь исходное значение, а может и не иметь
      // это не повлияет на дальнешее поведение
      $book = Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1']);
      
      var_dump($book->isTitleChanged());
      // false
      
      $book->setTitle('New title 1');
      
      var_dump($book->isTitleChanged());
      // true

      Такое поведение справедливо и для новых объектов, пока их значения не сохранены в базу данных.

    • has

      Метод has проверяет, есть ли в объекте хоть какое-то значение поля — актуальное из БД или установленное в течение сеанса. По сути, это сокращение от isFilled() || isChanged():

      use BitrixMainTestTypographyBook;
      
      $book = Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1']);
      $book->setIsArchived(true);
      
      var_dump($book->hasTitle());
      // true
      
      var_dump($book->hasIsArchived());
      // true
      
      var_dump($book->hasIsbn());
      // false

    Состояние объекта

    Объект может принимать 3 состояния:

    • новый, данные которого еще ни разу не сохранялись в БД;
    • актуальный, данные которого совпадают с хранящимися в БД;
    • измененный, данные которого отличаются от хранящихся в БД.

    Проверить состояние объекта можно с помощью публичного read-only свойства state и констант класса BitrixMainORMObjectifyState:

    use BitrixMainTestTypographyBook;
    use BitrixMainORMObjectifyState;
    
    $book = new Book;
    $book->setTitle('New title');
    
    var_dump($book->state === State::RAW);
    
    $book->save();
    
    var_dump($book->state === State::ACTUAL);
    
    $book->setTitle('Another one title');
    
    var_dump($book->state === State::CHANGED);
    
    $book->delete();
    
    var_dump($book->state === State::RAW);
    
    // true

    Создание и редактирование (save, new)

    Для фиксации изменений объекта в базе данных используется метод save:

    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    $book->setTitle("New title");
    
    $book->save();
    

    Примечание: если вы скопируете этот пример и попробуете выполнить его с тестовой сущностью из пространства имен BitrixMainTestTypography, то в силу специфики тестовых данных получите SQL- ошибку. Но при этом вы увидите, что часть запроса с данными построена корректно.

    С момента сохранения все текущие значения объекта преобразуются в актуальные:

    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    echo $book->remindActualTitle();
    // выведет "Title 1"
    
    $book->setTitle("New title");
    
    echo $book->remindActualTitle();
    // выведет "Title 1"
    
    $book->save();
    
    echo $book->remindActualTitle();
    // выведет "New title"
    

    Что касается новых объектов, есть два пути их создания. Наиболее читаемый способ — через прямое инстанциирование:

    $newBook = new BitrixMainTestTypographyBook;
    $newBook->setTitle('New title');
    $newBook->save();
    
    $newAuthor = new BitrixMainTestTypographyEO_Author;
    $newAuthor->setName('Some name');
    $newAuthor->save();
    

    Способ работает как со стандартными EO_ классами, так и с переопределенными. И даже если вы сначала использовали EO_ класс, а потом решили создать свой, то не придется переписывать существующий код — обратная совместимость сохранится автоматически. Системный класс с префиксом EO_ станет «алиасом» вашему классу.

    Более универсальный и обезличенный метод создавать новые объекты — через фабрику сущности:

    $newBook = BitrixMainTestTypographyBookTable::createObject();
    $newBook->setTitle('New title');
    $newBook->save();
    

    По умолчанию, в новом объекте устанавливаются все значения по умолчанию, описанные в «маппинге» getMap. Абсолютно чистый объект можно получить, передав соответствующий аргумент в конструктор:

    $newBook = new BitrixMainTestTypographyBook(false);
    $newBook = BitrixMainTestTypographyBookTable::createObject(false);
    

    Состояние значений меняется аналогично, как при редактировании. До сохранения объекта значения считаются текущими, после сохранения в базе данных переходят в статус actual.

    Удаление (delete)

    Для удаления используется метод delete():

    // удаление записи
    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    $book->delete();
    
    // удаление по primary ключу
    $book = BitrixMainTestTypographyBook::wakeUp(1);
    
    $book->delete();

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

    Как и в случае удаления через одноименный метод Table-класса, в этом случае сработают все нужные События. Поэтому дополнительные действия можно описать в обработчике события onDelete.

    Восстановление (wakeUp)

    Если у вас уже есть данные записи, для инициализации объекта необязательно выбирать их повторно из базы данных. Объект можно восстановить, имея как минимум значения первичного ключа:

    $book = BitrixMainTestTypographyBook::wakeUp(1);

    Указывать можно не только первичный ключ, но и частичный или полный набор данных:

    $book = BitrixMainTestTypographyBook::wakeUp(['ID' => 1, 'TITLE' => 'Title 1', 'PUBLISHER_ID' => 253]);

    Аналогично созданию объектов, метод актуален и для EO_ классов, и для вызова непосредственно из entity:

    // свой класс
    $book = BitrixMainTestTypographyBook::wakeUp(
    	['ID' => 1, 'TITLE' => 'Title 1']
    );
    
    // системный класс
    $book = BitrixMainTestTypographyEO_Book::wakeUp(
    	['ID' => 1, 'TITLE' => 'Title 1']
    );
    
    // через фабрику entity
    $book = BitrixMainTestTypographyBookTable::wakeUpObject(
    	['ID' => 1, 'TITLE' => 'Title 1']
    );
    

    В wakeUp можно передавать не столько скалярные значения, но и значения Отношений:

    $book = BitrixMainTestTypographyBook::wakeUp([
    	'ID' => 2,
    	'TITLE' => 'Title 2',
    	'PUBLISHER' => ['ID' => 253, 'TITLE' => 'Publisher Title 253'],
    	'AUTHORS' => [
    		['ID' => 17, 'NAME' => 'Name 17'],
    		['ID' => 18, 'NAME' => 'Name 18']
    	]
    ]);
    

    Заполнение (fill)

    Когда в объекте заполнены не все поля, и вам нужно дозаполнить их, не следует применять следующий подход:

    // изначально у нас есть только ID и NAME
    $author = BitrixMainTestTypographyEO_Author::wakeUp(
    	['ID' => 17, 'NAME' => 'Name 17']
    );
    
    // мы хотим дозаписать LAST_NAME, довыбрав его из базы данных
    $row = BitrixMainTestTypographyAuthorTable::getByPrimary($author->getId(),
    	['select' => ['LAST_NAME']]
    )->fetch();
    
    // добавление значения в объект
    $author->setLastName($row['LAST_NAME']);
    

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

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

    // изначально у нас есть только ID и NAME
    $author = BitrixMainTestTypographyEO_Author::wakeUp(
    	['ID' => 17, 'NAME' => 'Name 17']
    );
    
    // добавляем LAST_NAME из базы данных
    $author->fillLastName();
    

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

    $author = BitrixMainTestTypographyEO_Author::wakeUp(17);
    
    // заполнение нескольких полей
    $author->fill(['NAME', 'LAST_NAME']);
    
    // заполнение всех незаполненных на данный момент полей
    $author->fill();
    
    // заполнение полей по маске, например все незаполненные скалярные поля
    $author->fill(BitrixMainORMFieldsFieldTypeMask::SCALAR);
    
    // незаполненные скалярные и пользовательские поля
    $author->fill(
    	BitrixMainORMFieldsFieldTypeMask::SCALAR
    	| BitrixMainORMFieldsFieldTypeMask::USERTYPE
    );
    
    /*
     * Маски бывают следующие:
     *
     * SCALAR - скалярные поля (ORMScalarField)
     * EXPRESSION - выражения (ORMExpressionField)
     * USERTYPE - пользовательские поля
     * REFERENCE - отношения 1:1 и N:1 (ORMFieldsRelationsReference)
     * ONE_TO_MANY - отношения 1:N (ORMFieldsRelationsOneToMany)
     * MANY_TO_MANY - отношения N:M (ORMFieldsRelationsManyToMany)
     *
     * FLAT - скалярные поля и выражения
     * RELATION - все отношения
     *
     * ALL - абсолютно все доступные поля
     */
    

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

    Отношения (addTo, removeFrom, removeAll)

    Подробное описание отношений можно найти в соседней главе Отношения. Здесь же приведена спецификация управляющих отношениями методов.

    Для полей отношений работают уже описанные выше методы get, require, fill, reset, unset.

    Важно! Несмотря на то, что в качестве значения отношений используется объект Коллекции, изменять связи можно только через методы addTo, removeFrom, removeAll объектов-партнеров. Изменение коллекции напрямую (add, remove) не приведет к желаемому результату.

    • addTo

      Метод addTo добавляет новую связь между объектами:

      // инициализация издателя
      $publisher = BitrixMainTestTypographyPublisherTable::getByPrimary(253)
      	->fetchObject();
      
      // инициализация книги
      $book = BitrixMainTestTypographyBookTable::getByPrimary(2)
      	->fetchObject();
      
      // добавление книги в коллекцию отношения
      $publisher->addToBooks($book);
      
      // сохранение
      $publisher->save();
      

      Вызов метода связывает объекты лишь в памяти, после него необходимо зафиксировать изменения методом save.

    • removeFrom

      Удаление отношений removeFrom работает похожим образом:

      // инициализация издателя
      $publisher = BitrixMainTestTypographyPublisherTable::getByPrimary(253)
      	->fetchObject();
      
      // инициализация книги
      $book = BitrixMainTestTypographyBookTable::getByPrimary(2)
      	->fetchObject();
      
      // удаление одной конкретной книги издателя
      $publisher->removeFromBooks($book); 
      
      // сохранение
      $publisher->save();
      
    • removeAll

      Удаление сразу всех записей можно сделать одним вызовом:

      // инициализация издателя
      $publisher = BitrixMainTestTypographyPublisherTable::getByPrimary(253)
      	->fetchObject();
      	
      // удаление всех книг издателя
      $publisher->removeAllBooks();
      
      // сохранение
      $publisher->save();
      

      Для такой операции необходимо знать исходные значения — какие в данный момент есть Книги у Издателя. Поэтому, если значение поля BOOKS не было выбрано изначально, оно будет выбрано автоматически перед удалением.

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

    $fieldName = 'BOOKS';
    
    $publisher->addTo($fieldName, $book);
    $publisher->removeFrom($fieldName, $book);
    $publisher->removeAll($fieldName);
    

    ArrayAccess

    Интерфейс доступа к объекту как к массиву может помочь обеспечить обратную совместимость при переходе с массивов на объекты:

    $author = BitrixMainTestTypographyAuthorTable::getByPrimary(17)->fetchObject();
    		
    echo $author['NAME'];
    // вызов аналогичен методу $author->getName()
    
    $author['NAME'] = 'New name';
    // вызов аналогичен методу $author->setName('New name')
    

    Что касается runtime полей, то в данном случае можно только считывать их значения, но не устанавливать:

    $author = BitrixMainTestTypographyAuthorTable::query()
    	->registerRuntimeField(
    		new BitrixMainEntityExpressionField('FULL_NAME', 'CONCAT(%s, " ", %s)', ['NAME', 'LAST_NAME'])
    	)
    	->addSelect('ID')
    	->addSelect('FULL_NAME')
    	->where('ID', 17)
    	->fetchObject();
    
    echo $author['FULL_NAME'];
    // вызов аналогичен методу $author->get('FULL_NAME');
    
    $author['FULL_NAME'] = 'New name';
    // вызовет исключение
    

    Коллекции

    Коллекция — это умный массив, позволяющий оптимизировать групповые операции с объектами одного типа.

    Как и в случае с Объектами, для начала использования Коллекции объектов достаточно иметь лишь описанную сущность. Используйте fetchCollection вместо привычного fetchAll или зацикленного fetch:

    $books = BitrixMainTestTypographyBookTable::getList()
    	->fetchCollection();
    

    $books — коллекция объектов класса Book, наделенная методами для групповых манипуляций с объектами. Также коллекции используются в Отношениях.

    Класс коллекции

    Действует абсолютно та же логика с префиксом EO_, что и у Объектов. У каждой сущности свой класс Коллекции, унаследованный от BitrixMainORMObjectifyCollection. Для сущности Book по умолчанию он будет иметь вид EO_Book_Collection. Чтобы задать свой класс, нужно создать наследника этого класса и обозначить его в классе Table сущности:

    //Файл bitrix/modules/main/lib/test/typography/books.php
    
    namespace BitrixMainTestTypography;
    
    class Books extends EO_Book_Collection
    {
    }
    
    //Файл bitrix/modules/main/lib/test/typography/booktable.php
    
    namespace BitrixMainTestTypography;
    
    class BookTable extends BitrixMainORMDataDataManager
    {
    	public static function getCollectionClass()
    	{
    		return Books::class;
    	}
    	//...
    }

    Теперь метод fetchCollection будет возвращать коллекцию BitrixMainTestTypographyBooks объектов класса BitrixMainTestTypographyBook. Аннотации позволят IDE давать подсказки, облегчая работу разработчика.

    Доступ к элементам коллекции

    • foreach

      Базовый класс коллекции реализует интерфейс Iterator, что позволяет перебирать элементы в цикле:

      $books = BitrixMainTestTypographyBookTable::getList()
      	->fetchCollection();
      
      foreach ($books as $book)
      {
      	// ...
      }
    • getAll, getByPrimary

      Объекты коллекции можно получить не только в цикле, но и напрямую. Метод getAll вернет все содержащиеся объекты в виде массива:

      $books = BitrixMainTestTypographyBookTable::getList()
      	->fetchCollection();
      
      $bookObjects = $books->getAll();
      
      echo $bookObjects[0]->getId();
      // выведет значение ID первого объекта
      

      Для получения конкретных объектов, содержащихся в коллекции, предусмотрен метод getByPrimary:

      // 1. пример с простым первичным ключном
      $books = BitrixMainTestTypographyBookTable::getList()
      		->fetchCollection();
      	
      $book = $books->getByPrimary(1);
      // книга с ID=1
      
      // 2. пример с составным первичным ключом
      $booksToAuthor = BitrixMainTestTypographyBookAuthorTable::getList()
      	->fetchCollection();
      
      $bookToAuthor = $booksToAuthor->getByPrimary(
      	['BOOK_ID' => 2, 'AUTHOR_ID' => 18]
      );
      // будет присвоен объект отношения книги ID=2 с автором ID=18
      
    • has, hasByPrimary

      Проверить наличие конкретного объекта в коллекции можно методом has:

      $book1 = BitrixMainTestTypographyBook::wakeUp(1);
      $book2 = BitrixMainTestTypographyBook::wakeUp(2);
      
      $books = BitrixMainTestTypographyBookTable::query()
      	->addSelect('*')
      	->whereIn('ID', [2, 3, 4])
      	->fetchCollection();
      
      var_dump($books->has($book1));
      // выведет false
      
      var_dump($books->has($book2));
      // выведет true
      

      Аналогично работает метод hasByPrimary, когда удобнее сделать проверку по первичному ключу:

      $books = BitrixMainTestTypographyBookTable::query()
      	->addSelect('*')
      	->whereIn('ID', [2, 3, 4])
      	->fetchCollection();
      
      var_dump($books->hasByPrimary(1));
      // выведет false
      
      var_dump($books->hasByPrimary(2));
      // выведет true
      
      
      
    • add, []

      Добавление объектов реализовано методом add и интерфейсом ArrayAccess, позволяющим использовать конструкцию []:

      $book1 = BitrixMainTestTypographyBook::wakeUp(1);
      
      $books = BitrixMainTestTypographyBookTable::query()
      	->addSelect('*')
      	->whereIn('ID', [2, 3, 4])
      	->fetchCollection();
      
      $books->add($book1);
      // или
      $books[] = $book1;
    • remove, removeByPrimary

      Удалить объект из коллекции можно, задав его явно или указав первичный ключ:

      $book1 = BitrixMainTestTypographyBook::wakeUp(1);
      
      $books = BitrixMainTestTypographyBookTable::getList()
      	->fetchCollection();
      
      $books->remove($book1);
      // из коллекции удалится книга с ID=1
      
      $books->removeByPrimary(2);
      // из коллекции удалится книга с ID=2
      

    Групповые действия

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

    • save (добавление)

      Метод save() в случае с новыми объектами выполняет их первичное сохранение, формируя один групповой запрос:

      use BitrixMainTestTypographyBooks;
      use BitrixMainTestTypographyBook;
      
      $books = new Books;
      
      $books[] = (new Book)->setTitle('Title 112');
      $books[] = (new Book)->setTitle('Title 113');
      $books[] = (new Book)->setTitle('Title 114');
      
      $books->save(true);
      
      // INSERT INTO ...  (`TITLE`, `ISBN`) VALUES
      	('Title 112', DEFAULT),
      	('Title 113', DEFAULT),
      	('Title 114', '114-000')

      При этом в метод передан параметр $ignoreEvents = true, отменяющий выполнение событий ORM во время добавления записей. Дело в том, что именно при мульти-вставке с автоинкрементным полем (ID) невозможно получить множественные значения этого поля, как это возможно при вставке одной записи с помощью функций вроде mysqli_insert_id().

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

    • save (редактирование)

      Метод save() в случае с уже существующими, но измененными объектами, выполняет их сохранение одним запросом UPDATE:

      use BitrixMainTestTypographyPublisherTable;
      use BitrixMainTestTypographyBookTable;
      
      $books = BookTable::getList()->fetchCollection();
      $publisher = PublisherTable::wakeUpObject(254);
      
      foreach ($books as $book)
      {
      	$book->setPublisher($publisher);
      }
      
      $books->save();
      
      // UPDATE ... SET `PUBLISHER_ID` = '254'
      	WHERE `ID` IN ('1', '2')

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

      Как и в случае с добавлением, при обновлении можно отключать выполнение событий параметром $ignoreEvents в методе save(). По умолчанию они выполняются для каждого элемента коллекции.

    • fill

      Коллекционная операция fill является прекрасной альтернативой аналогичной операции в Объекте, выполненной в цикле. В случае с циклом количество запросов к базе данных будет равно количеству объектов:

      /** @var BitrixMainTestTypographyBook[] $books */
      $books = [
      	BitrixMainTestTypographyBook::wakeUp(1),
      	BitrixMainTestTypographyBook::wakeUp(2)
      ];
      
      foreach ($books as $book)
      {
      	$book->fill();
      	// SELECT ... WHERE ID = ...
      	// так делать не надо!
      }

      В случае же с коллекцией запрос будет только один:

      $books = new BitrixMainTestTypographyBooks;
      // или $books = BitrixMainTestTypographyBookTable::createCollection();
      
      $books[] = BitrixMainTestTypographyBook::wakeUp(1);
      $books[] = BitrixMainTestTypographyBook::wakeUp(2);
      
      $books->fill();
      // SELECT ... WHERE ID IN(1,2)
      

      Как и в случае с объектами, в качестве параметров в fill можно передавать массив имен полей для заполнения или маску типа:

      $books->fill(['TITLE', 'PUBLISHER_ID']);
      $books->fill(BitrixMainORMFieldsFieldTypeMask::FLAT);

      Более подробно возможные значения параметра описаны в fill Объектов.

    • get*List

      Не самый редкий сценарий — получить из результата запроса список значений отдельного поля. В обычном случае это может выглядеть так:

      $books = BitrixMainTestTypographyBookTable::getList()
      	->fetchCollection();
      
      $titles = [];
      
      foreach ($books as $book)
      {
      	$titles[] = $book->getTitle();
      }

      Именованный групповой «геттер» позволяет сократить такой цикл до одной строчки кода:

      $books = BitrixMainTestTypographyBookTable::getList()
      	->fetchCollection();
      
      $titles = $books->getTitleList();

      Такие «геттеры» доступны для всех полей сущности и описываются в аннотациях для IDE.

    Восстановление коллекции

    Восстановление коллекции из готовых данных работает так же, как это сделано в Объектах:

    // восстановление по первичному ключу
    $books = BitrixMainTestTypographyBooks::wakeUp([1, 2]);
    
    // восстановление по набору полей
    $books = BitrixMainTestTypographyBooks::wakeUp([
    	['ID' => 1, 'TITLE' => 'Title 1'],
    	['ID' => 2, 'TITLE' => 'Title 2']
    ]);

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

    Примечание: Если необходимо сохранение коллекции для объектов, которых не было в базе данных и восстановленных через wakeUp, то сохранятся объекты изменённые с момента wakeUp значения запросом UPDATE.

    Отношения

    На примере схемы отношений тестовых сущностей Book, Author и Publisher рассмотрим все комбинации и направления отношений: 1:N, 1:1, N:M.

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

    1:N

    В нашей тестовой вселенной книга может принадлежать и издаваться строго в одном издательстве. Получаем отношение «1 издательство — N книг».

    Книга и издательство

    В таких случаях в таблицу Книги следует добавить поле PUBLISHER_ID, значение которого будет указывать на Издательство.

    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsIntegerField;
    
    class BookTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    			(new IntegerField('PUBLISHER_ID'))
    		];
    	}
    }
    

    Но для ORM одного этого поля недостаточно для понимания связи между сущностями Book и Publisher. Чтобы такое понимание возникло, используются поля множества BitrixMainORMFieldsRelations, в данном случае для указания направленной связи «много к одному» нужно поле типа Reference:

    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsIntegerField;
    use BitrixMainORMFieldsRelationsReference;
    use BitrixMainORMQueryJoin;
    
    class BookTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    			(new IntegerField('PUBLISHER_ID')),
    
    			(new Reference(
    					'PUBLISHER',
    					PublisherTable::class,
    					Join::on('this.PUBLISHER_ID', 'ref.ID')
    				))
    				->configureJoinType('inner')
    		];
    	}
    }
    

    Параметры конструктора Reference:

    Параметр Описание
    $name Имя поля.
    $referenceEntity Класс связываемой сущности.
    $referenceFilter Условия «джойна». Ожидается объект фильтра. В отличие от регулярного использования фильтра, здесь к именам колонок нужно добавлять префиксы «this.» и «ref.», чтобы обозначить принадлежность к текущей и связываемой сущности соответственно.

    Для читаемости создан класс BitrixMainORMQueryJoin, единственный метод которого on возвращает объект фильтра BitrixMainORMQueryFilterConditionTree, задавая перед этим наиболее популярное условие whereColumn.

    Дополнительно можно сконфигурировать тип «джойна». По умолчанию это left join, в примере выше задается inner join.

    Теперь можно воспользоваться описанным отношением при выборке данных:

    $book = BitrixMainTestTypographyBookTable::getByPrimary(1, [
    	'select' => ['*', 'PUBLISHER']
    ])->fetchObject();
    
    echo $book->getPublisher()->getTitle();
    // выведет Publisher Title 253
    

    Доступ к объекту сущности Publisher реализуется через «геттер» getPublisher(). Таким образом, можно подключать более глубокие цепочки отношений, и так же по цепочкам «геттеров» добираться до конечных объектов.

    Чтобы установить связь, достаточно передать объект сущности Publisher в соответствующий «сеттер»:

    // инициализация издателя
    $publisher = BitrixMainTestTypographyPublisherTable::wakeUpObject(253);
    
    // инициализация книги
    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    // установка значения объекта
    $book->setPublisher($publisher);
    
    // сохранение
    $book->save();
    

    Значение поля PUBLISHER_ID будет заполнено автоматически из переданного объекта.

    С массивами результат выглядит не так лаконично:

    $result = BitrixMainTestTypographyBookTable::getByPrimary(1, [
    	'select' => ['*', 'PUBLISHER']
    ]);
    
    print_r($result->fetch());
    /* выведет
    Array (
    	[ID] => 1
    	[TITLE] => Title 1
    	[PUBLISHER_ID] => 253
    	[ISBN] => 978-3-16-148410-0
    	[IS_ARCHIVED] => Y
    	[MAIN_TEST_TYPOGRAPHY_BOOK_PUBLISHER_ID] => 253
    	[MAIN_TEST_TYPOGRAPHY_BOOK_PUBLISHER_TITLE] => Publisher Title 253
    )
    */
    

    Полям связанной сущности присваиваются уникальные имена, основанные на пространстве имен и имени класса. Можно воспользоваться механизмом «алиасов» для получения более коротких и практичных имен:

    $result = BitrixMainTestTypographyBookTable::getByPrimary(1, [
    	'select' => ['*', 'PUB_' => 'PUBLISHER']
    ]);
    
    print_r($result->fetch());
    /* выведет
    Array (
    	[ID] => 1
    	[TITLE] => Title 1
    	[PUBLISHER_ID] => 253
    	[ISBN] => 978-3-16-148410-0
    	[IS_ARCHIVED] => Y
    	[PUB_ID] => 253
    	[PUB_TITLE] => Publisher Title 253
    )
    */
    

    Издательство и книги

    Пока что доступ к отношению работает только по направлению «Книга» -> «Издатель». Чтобы сделать его двунаправленным, понадобится описать отношение на стороне сущности Publisher:

    namespace BitrixMainTestTypography;
    
    use BitrixMainORMDataDataManager;
    use BitrixMainORMFieldsRelationsOneToMany;
    
    class PublisherTable extends DataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    
    			(new OneToMany('BOOKS', BookTable::class, 'PUBLISHER'))->configureJoinType('inner')
    		];
    	}
    }
    

    Параметры конструктора OneToMany:

    Параметр Описание
    $name Имя поля.
    $referenceEntity Класс связываемой сущности.
    $referenceFilter Имя поля `Reference` в сущности-партнере, через которое осуществляется связь.

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

    Теперь можно воспользоваться описанным отношением при выборке данных:

    $publisher = BitrixMainTestTypographyPublisherTable::getByPrimary(253, [
    	'select' => ['*', 'BOOKS']
    ])->fetchObject();
    
    foreach ($publisher->getBooks() as $book)
    {
    	echo $book->getTitle();
    }
    
    // цикл выведет "Title 1" и "Title 2"
    

    В примере выше видно принципиальное преимущество объектной модели перед массивами. Несмотря на то, что фактически выбрано две записи (для одного Издателя нашлось две книги), по факту из результата получается только один объект. Система самостоятельно распознала этот случай и склеила все книги издателя в одну Коллекцию.

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

    $data = BitrixMainTestTypographyPublisherTable::getByPrimary(253, [
    	'select' => ['*', 'BOOK_' => 'BOOKS']
    ])->fetchAll();
    
    // вернет
    Array (
    	[0] => Array (
    		[ID] => 253
    		[TITLE] => Publisher Title 253
    		[BOOK_ID] => 2
    		[BOOK_TITLE] => Title 2
    		[BOOK_PUBLISHER_ID] => 253
    		[BOOK_ISBN] => 456-1-05-586920-1
    		[BOOK_IS_ARCHIVED] => N 
    	) 
    	[1] => Array (
    		[ID] => 253
    		[TITLE] => Publisher Title 253
    		[BOOK_ID] => 1
    		[BOOK_TITLE] => Title 1
    		[BOOK_PUBLISHER_ID] => 253
    		[BOOK_ISBN] => 978-3-16-148410-0
    		[BOOK_IS_ARCHIVED] => Y
    	)
    )

    Чтобы добавить новую Книгу Издателю, используется именованный «сеттер» addTo:

    // инициализация издателя
    $publisher = BitrixMainTestTypographyPublisherTable::getByPrimary(253)
    	->fetchObject();
    
    // инициализация книги
    $book = BitrixMainTestTypographyBookTable::getByPrimary(2)
    	->fetchObject();
    
    // добавление книги в коллекцию отношения
    $publisher->addToBooks($book);
    
    // сохранение
    $publisher->save();
    

    Для удаления связи со стороны книги достаточно установить setPublisher() другого издателя или null. А чтобы сделать это со стороны Издателя, существуют специализированные «сеттеры» removeFrom() и removeAll():

    // инициализация книги
    $book = BitrixMainTestTypographyBook::wakeUp(2);
    
    // инициализация издателя
    $publisher = BitrixMainTestTypographyPublisherTable::getByPrimary(253, [
    	'select' => ['*', 'BOOKS']
    ])->fetchObject();
    
    // удаление одной конкретной книги издателя
    $publisher->removeFromBooks($book);
    
    // или удаление всех книг издателя
    $publisher->removeAllBooks();
    
    // во время сохранения поле PUBLISHER_ID в Книгах будет обновлено на пустое значение
    // сами книги удалены не будут, удаляется именно связь
    $publisher->save();
    

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

    // инициализация книги
    $book = BitrixMainTestTypographyBookTable::wakeUpObject(2);
    
    // у издателя будет заполнен только первичный ключ
    $publisher = BitrixMainTestTypographyPublisherTable::wakeUpObject(253);
    
    // заполняем поле отношения
    $publisher->fillBooks();
    
    // удаление одной конкретной книги
    $publisher->removeFromBooks($book);
    
    // или удаление всех книг
    $publisher->removeAllBooks();
    
    // во время сохранения поле PUBLISHER_ID в Книгах будет обновлено на пустое значение
    // сами книги удалены не будут
    $publisher->save();
    

    В случае с массивами операции addTo, removeFrom и removeAll невозможны, можно создать связь только со стороны сущности Книги.

    1:1

    Отношения один-к-одному работают аналогично один-ко-многим с той лишь разницей, что в обеих сущностях вместо пары Reference + OneToMany оба поля будут Reference.

    N:M

    Примитивные отношения без вспомогательных данных

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

    //Файл bitrix/modules/main/lib/test/typography/booktable.php
    
    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsRelationsManyToMany;
    
    class BookTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    
    			(new ManyToMany('AUTHORS', AuthorTable::class))
    				->configureTableName('b_book_author')
    		];
    	}
    }
    
    //Файл bitrix/modules/main/lib/test/typography/authortable.php
    
    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsRelationsManyToMany;
    
    class AuthorTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    
    			(new ManyToMany('BOOKS', BookTable::class))
    				->configureTableName('b_book_author')
    		];
    	}
    }

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

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

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

    class ... extends BitrixMainORMDataDataManager
    {
    	public static function getTableName()
    	{
    		return 'b_book_author';
    	}
    
    	public static function getMap()
    	{
    		return [
    			(new IntegerField('BOOK_ID'))
    				->configurePrimary(true),
    
    			(new Reference('BOOK', BookTable::class,
    				Join::on('this.BOOK_ID', 'ref.ID')))
    				->configureJoinType('inner'),
    
    			(new IntegerField('AUTHOR_ID'))
    				->configurePrimary(true),
    
    			(new Reference('AUTHOR', AuthorTable::class,
    				Join::on('this.AUTHOR_ID', 'ref.ID')))
    				->configureJoinType('inner'),
    		];
    	}
    }
    

    Это не более чем типовая сущность с референсами (направленными отношениями 1:N) к исходным сущностям-партнерам. Имена полей формируются на основе имен сущностей и имен их первичных ключей:

    new IntegerField('BOOK_ID') - snake_case от Book + primary поле ID
    new Reference('BOOK') - snake_case от Book
    new IntegerField('AUTHOR_ID') - snake_case от Author + primary поле ID
    new Reference('AUTHOR') - snake_case от Author
    

    Чтобы задать имена полей явно, особенно это актуально в сущностях с составными первичными ключами для избегания путаницы, используются конфигурирующие методы:

    //Файл bitrix/modules/main/lib/test/typography/booktable.php
    
    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsRelationsManyToMany;
    
    class BookTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    
    			(new ManyToMany('AUTHORS', AuthorTable::class))
    				->configureTableName('b_book_author')
    				->configureLocalPrimary('ID', 'MY_BOOK_ID')
    				->configureLocalReference('MY_BOOK')
    				->configureRemotePrimary('ID', 'MY_AUTHOR_ID')
    				->configureRemoteReference('MY_AUTHOR')
    		];
    	}
    }
    
    //Файл bitrix/modules/main/lib/test/typography/authortable.php
    
    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsRelationsManyToMany;
    
    class AuthorTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    
    			(new ManyToMany('BOOKS', BookTable::class))
    				->configureTableName('b_book_author')
    				->configureLocalPrimary('ID', 'MY_AUTHOR_ID')
    				->configureLocalReference('MY_AUTHOR'),
    				->configureRemotePrimary('ID', 'MY_BOOK_ID')
    				->configureRemoteReference('MY_BOOK')
    		];
    	}
    }

    Метод configureLocalPrimary указывает, как будет называться привязка к полю из первичного ключа текущей сущности, аналогично configureRemotePrimary задает соответствие полей первичного ключа сущности-партнера. Методы configureLocalReference и configureRemoteReference задают имена референсов к исходным сущностям. При описанной выше конфигурации системная сущность отношений будет иметь примерно такой вид:

    class ... extends BitrixMainORMDataDataManager
    {
    	public static function getTableName()
    	{
    		return 'b_book_author';
    	}
    
    	public static function getMap()
    	{
    		return [
    			(new IntegerField('MY_BOOK_ID'))
    				->configurePrimary(true),
    
    			(new Reference('MY_BOOK', BookTable::class,
    				Join::on('this.MY_BOOK_ID', 'ref.ID')))
    				->configureJoinType('inner'),
    
    			(new IntegerField('MY_AUTHOR_ID'))
    				->configurePrimary(true),
    
    			(new Reference('MY_AUTHOR', AuthorTable::class,
    				Join::on('this.MY_AUTHOR_ID', 'ref.ID')))
    				->configureJoinType('inner'),
    		];
    	}
    }

    Как и в случае Reference с OneToMany, здесь тоже можно переопределить тип джойна методом configureJoinType (значение по умолчанию — «left»):

    (new ManyToMany('AUTHORS', AuthorTable::class))
    	->configureTableName('b_book_author')
    	->configureJoinType('inner')
    

    Чтение данных работает аналогично отношениям 1:N:

    // выборка со стороны авторов
    $author = BitrixMainTestTypographyAuthorTable::getByPrimary(18, [
    	'select' => ['*', 'BOOKS']
    ])->fetchObject();
    
    foreach ($author->getBooks() as $book)
    {
    	echo $book->getTitle();
    }
    // цикл выведет "Title 1" и "Title 2"
    
    // выборка со сороны книг
    $book = BitrixMainTestTypographyBookTable::getByPrimary(2, [
    	'select' => ['*', 'AUTHORS']
    ])->fetchObject();
    
    foreach ($book->getAuthors() as $author)
    {
    	echo $author->getLastName();
    }
    // цикл выведет "Last name 17" и "Last name 18"
    

    Выборка объектов вместо массивов вновь выгодно отличается тем, что не происходит «двоения» данных, как это происходит с массивами:

    $author = BitrixMainTestTypographyAuthorTable::getByPrimary(18, [
    	'select' => ['*', 'BOOK_' => 'BOOKS']
    ])->fetchAll();
    
    // вернет
    Array (
    	[0] => Array 
    		[ID] => 18
    		[NAME] => Name 18
    		[LAST_NAME] => Last name 18
    		[BOOK_ID] => 1
    		[BOOK_TITLE] => Title 1
    		[BOOK_PUBLISHER_ID] => 253
    		[BOOK_ISBN] => 978-3-16-148410-0
    		[BOOK_IS_ARCHIVED] => Y 
    	)
    	[1] => Array (
    		[ID] => 18
    		[NAME] => Name 18
    		[LAST_NAME] => Last name 18
    		[BOOK_ID] => 2
    		[BOOK_TITLE] => Title 2
    		[BOOK_PUBLISHER_ID] => 253
    		[BOOK_ISBN] => 456-1-05-586920-1
    		[BOOK_IS_ARCHIVED] => N
    	)
    )

    Создание связи между объектами двух сущностей происходит так же, как в случае с отношениями 1:N:

    // со стороны авторов
    $author = BitrixMainTestTypographyAuthorTable::getByPrimary(17)
    	->fetchObject();
    
    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    $author->addToBooks($book);
    
    $author->save();
    
    
    // со стороны книг
    $author = BitrixMainTestTypographyAuthorTable::getByPrimary(17)
    	->fetchObject();
    
    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    $book->addToAuthors($author);
    
    $book->save();
    

    Методы removeFrom и removeAll работают тоже аналогично.

    Для массивов подобных конструкций не предусмотрено. Как связывать сущности, используя массивы — смотрите ниже в примере отношений Книг с Магазинами.

    Отношения со вспомогательными данными

    STORE_ID BOOK_ID QUANTITY
    33 1 4
    33 2 0
    43 2 9

    Когда есть дополнительные данные (количество книг в наличие), а не только первичные ключи исходных сущностей, такое отношение следует описать отдельной сущностью:

    namespace BitrixMainTestTypography;
    
    use BitrixMainORMDataDataManager;
    use BitrixMainORMFieldsIntegerField;
    use BitrixMainORMFieldsRelationsReference;
    use BitrixMainORMQueryJoin;
    
    class StoreBookTable extends DataManager
    {
    	public static function getTableName()
    	{
    		return 'b_store_book';
    	}
    
    	public static function getMap()
    	{
    		return [
    			(new IntegerField('STORE_ID'))
    				->configurePrimary(true),
    
    			(new Reference('STORE', StoreTable::class,
    				Join::on('this.STORE_ID', 'ref.ID')))
    				->configureJoinType('inner'),
    
    			(new IntegerField('BOOK_ID'))
    				->configurePrimary(true),
    
    			(new Reference('BOOK', BookTable::class,
    				Join::on('this.BOOK_ID', 'ref.ID')))
    				->configureJoinType('inner'),
    
    			(new IntegerField('QUANTITY'))
    				->configureDefaultValue(0)
    		];
    	}
    }

    Если для простых отношений без вспомогательных данных использовались поля ManyToMany, здесь их использование будет сильно ограничено. Можно будет создавать и удалять связи, но не будет доступа к вспомогательному полю QUANTITY. С помощью removeFrom*() можно будет удалить связь, с помощью addTo*() можно будет добавить связь со значением QUANTITY только по умолчанию, и не будет возможности обновить значение QUANTITY. Поэтому в таких случаях более гибким будет использование непосредственно сущности-посредника:

    // объект книги
    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    // объект магазина
    $store = BitrixMainTestTypographyStoreTable::getByPrimary(34)
    	->fetchObject();
    
    // новый объект связи книги с магазином
    $item = BitrixMainTestTypographyStoreBookTable::createObject()
    	->setBook($book)
    	->setStore($store)
    	->setQuantity(5);
    
    // сохранение
    $item->save();

    Обновление количества книг:

    // объект существующей связи
    $item = BitrixMainTestTypographyStoreBookTable::getByPrimary([
    	'STORE_ID' => 33, 'BOOK_ID' => 2
    ])->fetchObject();
    
    // обновление количества
    $item->setQuantity(12);
    
    // сохранение
    $item->save();
    

    Удаление связи:

    // объект существующей связи
    $item = BitrixMainTestTypographyStoreBookTable::getByPrimary([
    	'STORE_ID' => 33, 'BOOK_ID' => 2
    ])->fetchObject();
    
    // удаление
    $item->delete();
    

    То есть, работа с объектом связи ведется как с объектом любой другой сущности. Для массивов тоже следует использовать стандартные подходы по работе с данными:

    // добавление
    BitrixMainTestTypographyStoreBookTable::add([
    	'STORE_ID' => 34, 'BOOK_ID' => 1, 'QUANTITY' => 5
    ]);
    
    // обновление
    BitrixMainTestTypographyStoreBookTable::update(
    	['STORE_ID' => 34, 'BOOK_ID' => 1],
    	['QUANTITY' => 12]
    );
    
    // удаление
    BitrixMainTestTypographyStoreBookTable::delete(
    	['STORE_ID' => 34, 'BOOK_ID' => 1]
    );

    Выше было упомянуто, что использовать поле ManyToMany в случае со вспомогательными данными — непродуктивно. Правильнее будет использовать тип OneToMany:

    //Файл bitrix/modules/main/lib/test/typography/booktable.php
    
    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsRelationsOneToMany;
    
    class BookTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    
    			(new OneToMany('STORE_ITEMS', StoreBookTable::class, 'BOOK'))
    		];
    	}
    }
    //Файл bitrix/modules/main/lib/test/typography/storetable.php
    
    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsRelationsOneToMany;
    
    class StoreTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    
    			(new OneToMany('BOOK_ITEMS', StoreBookTable::class, 'STORE'))
    		];
    	}
    }

    В таком случае выборка ничем не будет отличаться от отношений 1:N, только в этот раз будут возвращаться объекты отношения StoreBook, а не сущности-партнера:

    $book = BitrixMainTestTypographyBookTable::getByPrimary(1, [
    	'select' => ['*', 'STORE_ITEMS']
    ])->fetchObject();
    
    
    foreach ($book->getStoreItems() as $storeItem)
    {
    	printf(
    		'store "%s" has %s of book "%s"',
    		$storeItem->getStoreId(), $storeItem->getQuantity(), $storeItem->getBookId()
    	);
    	// выведет store "33" has 4 of book "1"
    }
    

    Аннотации классов

    Большая часть методов Объекта и Коллекции — виртуальные, обрабатываются через magic вызов __call. В то же время они сделаны для интуитивно понятных и говорящих именованных методов, и без автокомплита в IDE их ценность резко снижается.

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

    С версии 20.100.0 Главного модуля (main) файл с аннотациями ORM-классов ядра включен в поставку и расположен в /bitrix/modules/main/meta/orm.php.

    Для генерации таких аннотаций используется cli-команда orm:annotate:

    $ cd bitrix
    $ php bitrix.php orm:annotate

    Примечание: перед использованием CLI-окружения убедитесь, что установили зависимости проекта через composer.

    В процессе выполнения команды производится сканирование модулей, а именно всех файлов из папок bitrix/modules/[module]/lib. Если в файле обнаруживается «маппинг» сущности (класс Table, подкласс BitrixMainORMDataDataManager), то анализируется ее карта (список полей).

    Результатом команды является файл (по умолчанию bitrix/modules/orm_annotations.php), который содержит описания классов Объекта и Коллекции сущностей. Также в нем декларируются дубликат класса Table и несколько фактически несуществующих вспомогательных классов, помогающих сопровождать автокомплит IDE от момента запроса до использования результирующих объектов.

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

    // аннотирование сущностей произвольного модуля:
    
    $ php bitrix.php orm:annotate -m tasks
    // аннотирование нескольких модулей:
    
    $ php bitrix.php orm:annotate -m main,intranet,faceid
    // аннотирование всех модулей:
    
    $ php bitrix.php orm:annotate -m all

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

    Частично удобство есть уже в том, что при перегенерации классы заменяются точечно. Если в аннотациях уже есть описанные модули, то при повторном аннотировании одного из них обновится описание только его классов, и другие не будут удалены. Для сброса используйте параметр -c:

    $ php bitrix.php orm:annotate -c -m all

    Чтобы посмотреть все доступные параметры команды, выполните команду:

    $ php bitrix.php help orm:annotate

    Обратная совместимость

    С выходом объектов в модуле main версии 18.0.4 изменились некоторые внутренние механизмы ORM. Ситуации достаточно редкие и не совсем корректные изначально, по сути появилось больше строгости в их обработке.

    • Имена полей теперь регистронезависимы. До этого можно было описать два поля LAST_NAME и last_name, и это были бы разные поля. Теперь же такая сущность не сможет инициализироваться. Изменение связано с именованными методами в Объектах.
    • В выборке нельзя присвоить одноименный алиас в другом регистре, например, getList([‘select’ => [‘id’ => ‘ID’]]).
    • Поле BooleanField раньше допускало пустую строку в качестве значения, что могло привести к дальнейшему ошибочному трактованию значения. Теперь пустая строка недопустима, можно указывать true/false или значения values, указанные в конфигурации поля.

    В объектах пока что не поддерживаются:

    • Поля с сериализацей, поскольку в них существует противоречие: тип поля указывается как StringField или TextField, а фактически в нем хранится массив, что не соответствует заявленному типу.

    Выборка данных

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

    getList

    Чтобы новое API выглядело для разработчика менее пугающим и более знакомым, сохранено имя самого популярного метода: getList. Но если раньше каждый getList имел свой набор параметров и зашитое непрозрачное поведение, то теперь этот метод един для всех сущностей и подчиняется одним законам. Даже при желании у разработчика сущности сделать «костыль» в getList ничего не выйдет.

    Сущность BookTable, взятая в качестве примера, не исключение. Какие параметры принимает метод BookTable::getList?

    BookTable::getList(array(
    	'select'  => ... // имена полей, которые необходимо получить в результате
    	'filter'  => ... // описание фильтра для WHERE и HAVING
    	'group'   => ... // явное указание полей, по которым нужно группировать результат
    	'order'   => ... // параметры сортировки
    	'limit'   => ... // количество записей
    	'offset'  => ... // смещение для limit
    	'runtime' => ... // динамически определенные поля
    ));

    getList всегда возвращает объект DBResult, из которого можно получить данные с помощью метода fetch():

    $rows = array();
    $result = BookTable::getList(array(
    	...
    ));
    while ($row = $result->fetch())
    {
    	$rows[] = $row;
    }

    Для получения сразу всех записей можно воспользоваться методом fetchAll():

    $result = BookTable::getList($parameters);
    $rows = $result->fetchAll();
    
    // или совсем лаконично:
    $rows = BookTable::getList($parameters)->fetchAll();

    Теперь рассмотрим подробнее все параметры.

    select

    Параметр `select` определяется в виде массива с именами полей сущности:

    BookTable::getList(array(
    	'select' => array('ISBN', 'TITLE', 'PUBLISH_DATE')
    ));
    
    // SELECT ISBN, TITLE, PUBLISH_DATE FROM my_book

    Если по каким-то причинам не устраивают оригинальные названия полей в результате, можно использовать алиасы:

    BookTable::getList(array(
    	'select' => array('ISBN', 'TITLE', 'PUBLICATION' => 'PUBLISH_DATE')
    ));
    
    // SELECT ISBN, TITLE, PUBLISH_DATE AS PUBLICATION FROM my_book

    В данном примере название поля `PUBLISH_DATE` заменяется на `PUBLICATION`, именно такое название будет фигурировать в результирующем массиве.

    Если необходимо выбрать все поля, то можно воспользоваться символом ‘*’:

    BookTable::getList(array(
    	'select' => array('*')
    ));

    При этом будут выбраны только поля ScalarField, а поля-выражения ExpressionField и отношения с другими сущностями затронуты не будут — их всегда нужно указывать явно.

    Вычисляемое поле в select минуя runtime

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

    Если вычисляемое поле необходимо вам только в секции `select`, как это чаще всего бывает, то секцию `runtime` использовать необязательно: можно сэкономить время, поместив выражение сразу в select.

    Система позволяет использовать вложенные выражения, которые будут последовательно развернуты в финальном SQL коде:

      
          BookTable::getList(array(
           'select' => array(
           'runtime' => array(
                new EntityExpressionField('MAX_AGE', 'MAX(%s)', array('AGE_DAYS'))
                )
          ));
          // SELECT MAX(DATEDIFF(NOW(), PUBLISH_DATE)) AS MAX_AGE FROM my_book

    Обратите внимание, что внутри нового Expression поля MAX_AGE было использовано уже существующее другое Expression поле AGE_DAYS. Таким образом, система позволяет использовать вложенные выражения, которые будут последовательно развернуты в финальном SQL коде.

    Внутри нового Expression поля MAX_AGE было использовано уже существующее другое Expression поле AGE_DAYS.

    В секции runtime можно регистрировать не только Expression поля, но и поля любых других типов. Механизм runtime работает таким образом, что к сущности добавляется новое поле, будто оно было описано в ней изначально в методе getMap. Но такое поле находится в зоне видимости только в рамках одного запроса — в следующем вызове getList такое поле уже будет недоступно, потребуется заново его зарегистрировать.

    filter

    Параметр `filter` унаследовал формат фильтра инфоблоков:

    // WHERE ID = 1
    BookTable::getList(array(
    	'filter' => array('=ID' => 1)
    ));
    
    // WHERE TITLE LIKE 'Patterns%'
    BookTable::getList(array(
    	'filter' => array('%=TITLE' => 'Patterns%')
    ));

    Фильтр может быть многоуровневым массивом со склейкой выражений AND/OR:

    // WHERE ID = 1 AND ISBN = '9780321127426'
    BookTable::getList(array(
    	'filter' => array(
    		'=ID' => 1,
    		'=ISBN' => '9780321127426'
    	)
    ));
    
    // WHERE (ID=1 AND ISBN='9780321127426') OR (ID=2 AND ISBN='9781449314286')
    BookTable::getList(array(
    	'filter' => array(
    		'LOGIC' => 'OR',
    		array(
    			// 'LOGIC' => 'AND', // по умолчанию элементы склеиваются через AND
    			'=ID' => 1,
    			'=ISBN' => '9780321127426'
    		),
    		array(
    			'=ID' => 2,
    			'=ISBN' => '9781449314286'
    		)
    	)
    ));

    Полный список операторов сравнения, которые можно использовать в фильтре:

    • = равно (работает и с массивами)
    • % подстрока
    • > больше
    • < меньше
    • @  IN (EXPR), в качестве значения передается массив или объект DBSqlExpression
    • !@  NOT IN (EXPR), в качестве значения передается массив или объект DBSqlExpression
    • != не равно
    • !% не подстрока
    • >< между, в качестве значения передается массив array(MIN, MAX)
    • >= больше или равно
    • <= меньше или равно
    • =% LIKE
    • %= LIKE
    • == булевое выражение для ExpressionField (например, для EXISTS() или NOT EXISTS())
    • !>< не между, в качестве значения передается массив array(MIN, MAX)
    • !=% NOT LIKE
    • !%= NOT LIKE
    • ‘==ID’ => null — условие, что поле ID равно NULL (в sql-запросе будет преобразовано в ID IS NULL)
    • ‘!==NAME’ => null — условие, что поле NAME не равно NULL (в sql-запросе будет преобразовано в NAME IS NOT NULL)

    Внимание! Если не указывать явно оператор сравнения =, то по умолчанию будет выполнен LIKE. В данном случае использован код построения фильтров из модуля Инфоблоков, который по историческим причинам подразумевает такое поведение.

    Для полей типа int ставится:
    — [dw]до выхода объектного ORM[/dw][di]С версии 18.0.3 модуля main.[/di]: = (сравнение по равенству, массив разворачивается в набор условий OR =)
    — после выхода: IN().

    group

    В параметре `group` перечисляются поля для группировки:

    BookTable::getList(array(
    	'group' => array('PUBLISH_DATE')
    ));

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

    order

    Параметр `order` позволяет указать порядок сортировки:

    BookTable::getList(array(
    	'order' => array('PUBLISH_DATE' => 'DESC', 'TITLE' => 'ASC')
    ));
    
    BookTable::getList(array(
    	'order' => array('ID') // направление по умолчанию - ASC
    ));

    offset/limit

    Параметры `offset` и `limit` помогут ограничить количество выбираемых записей или реализовать постраничную выборку:

    // 10 последних записей
    BookTable::getList(array(
    	'order' => array('ID' => 'DESC')
    	'limit' => 10
    ));
    
    // 5-я страница с записями, по 20 на страницу
    BookTable::getList(array(
    	'order' => array('ID')
    	'limit' => 20,
    	'offset' => 80
    ));

    runtime

    Упоминаемые в 1-й части вычисляемые поля (ExpressionField) часто нужны не столько в описании сущности, сколько при выборке для различных вычислений с группировкой.

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

    BookTable::getList(array(
    	'select' => array('CNT'),
    	'runtime' => array(
    		new EntityExpressionField('CNT', 'COUNT(*)')
    	)
    ));
    // SELECT COUNT(*) AS CNT FROM my_book

    В данном примере вычисляемое поле не просто преобразовывает значение какого-то поля, а реализует произвольное SQL выражение с функцией COUNT.

    После регистрации поля в секции `runtime`, на него можно ссылаться не только в секции `select`, но и в других секциях:

    BookTable::getList(array(
    	'select' => array('PUBLISH_DATE'),
    	'filter' => array('>CNT' => 5),
    	'runtime' => array(
    		new EntityExpressionField('CNT', 'COUNT(*)')
    	)
    ));
    // выбрать дни, в которые выпущено более 5 книг
    // SELECT PUBLISH_DATE, COUNT(*) AS CNT FROM my_book GROUP BY PUBLISH_DATE HAVING COUNT(*) > 5

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

    Если вычисляемое поле необходимо только в секции `select` (как это чаще всего бывает), то секцию `runtime` использовать необязательно: можно сэкономить время, поместив выражение сразу в `select`.

    BookTable::getList(array(
    	'select' => array(
    		new EntityExpressionField('MAX_AGE', 'MAX(%s)', array('AGE_DAYS'))
    	)
    ));
    // SELECT MAX(DATEDIFF(NOW(), PUBLISH_DATE)) AS MAX_AGE FROM my_book

    Обратите внимание, что внутри нового Expression поля MAX_AGE было использовано уже существующее другое Expression поле AGE_DAYS. Таким образом, система позволяет использовать вложенные выражения, которые будут последовательно развернуты в финальном SQL коде.

    В секции `runtime` можно регистрировать не только Expression поля, но и поля любых других типов. Механизм `runtime` работает таким образом, что к сущности добавляется новое поле, будто оно было описано в ней изначально в методе `getMap`. Но такое поле находится в зоне видимости только в рамках одного запроса — в следующем вызове getList такое поле уже будет недоступно, потребуется заново его зарегистрировать.

    Кеширование выборки

    Доступно кеширование конкретной выборки с версии 16.5.9 . В самой сущности ничего не надо описывать. По умолчанию не кешируется.

    В getList, в параметры добавился ключ cache:

    $res = BitrixMainGroupTable::getList(array("filter"=>array("=ID"=>1), "cache"=>array("ttl"=>3600)));

    То же самое реализуется и с помощью Query:

    $query = BitrixMainGroupTable::query();
    $query->setSelect(array('*'));
    $query->setFilter(array("=ID"=>1));
    $query->setCacheTtl(150);
    $res = $query->exec();

    Возможно, что в результате кешированной выборки придет объект ArrayResult.

    По умолчанию выборки с JOIN не кешируются. Но, если вы уверены в том, что делаете, можно явно закешировать:

    "cache"=>array("ttl"=>3600, "cache_joins"=>true);
    //or
    $query->cacheJoins(true);

    Сброс кеша происходит в любом методе add/update/delete. Принудительный сброс кеша для таблицы:

    /* Пример для таблицы пользователей */
    BitrixMainUserTable::getEntity()->cleanCache();

    Администратору проекта доступен запрет кеширования или изменение TTL.

    Примечание: Если необходимо произвести форматирование данных при выборке, используйте fetch_data_modification.

    Короткие вызовы

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

    • getById($id) — производит выборку по первичному ключу;
    • getByPrimary($primary, array $parameters) — выборка по первичному ключу с возможностью указания дополнительных параметров;

      Примечание: в обоих методах мы можем как передать id в виде числа, так и явно указать какой элемент является ключом, передав массив. Массив необходимо использовать, если у вас есть несколько primary полей. Если вы передаете в массиве элемент, который не является первичным ключом, то это будет ошибкой.

      BookTable::getById(1);
      BookTable::getByPrimary(array('ID' => 1));
      
      // такие вызовы будут аналогичны следующему вызову getList:
      BookTable::getList(array(
      	'filter' => array('=ID' => 1)
      ));
    • getRowById($id) — производит выборку по первичному ключу, но возвращается массив данных;

      $row = BookTable::getRowById($id);
      
      // аналогичный результат можно получить так:
      $result = BookTable::getById($id);
      $row = $result->fetch();
      
      // или так
      $result = BookTable::getList(array(
      	'filter' => array('=ID' => $id)
      ));
      
      $row = $result->fetch();
    • getRow(array $parameters) — производит выборку не по первичному ключу, а по каким-то другим параметрам. При этом возвращается только одна запись.

      $row = BookTable::getRow(array(
      	'filter' => array('%=TITLE' => 'Patterns%'),
      	'order' => array('ID')
      ));
      
      // аналогичный результат можно получить так:
      $result = BookTable::getList(array(
      	'filter' => array('%=TITLE' => 'Patterns%'),
      	'order' => array('ID')
      	'limit' => 1
      ));
      
      $row = $result->fetch();

    Объект Query

    Все параметры getList, getRow и другие передаются вместе, при этом сразу же выполняется запрос и возвращается результат: все происходит за один вызов. Но существует и альтернативный способ конфигурации запроса и контроля за его выполнением — это объект EntityQuery:

    // получение данных через getList
    $result = BookTable::getList(array(
    	'select' => array('ISBN', 'TITLE', 'PUBLISH_DATE')
    	'filter' => array('=ID' => 1)
    ));
    
    // аналогично через EntityQuery
    $q = new EntityQuery(BookTable::getEntity());
    $q->setSelect(array('ISBN', 'TITLE', 'PUBLISH_DATE'));
    $q->setFilter(array('=ID' => 1));
    $result = $q->exec();

    Такой подход может быть удобным, когда необходима гибкость в построении запроса. Например, если параметры запроса заранее неизвестны и формируются программно, можно вместо множества разных аргументов использовать один объект Query, накапливая в нем параметры запроса:

    $query = new EntityQuery(BookTable::getEntity());
    attachSelect($query);
    attachOthers($query);
    $result = $query->exec();
    
    function attachSelect(EntityQuery $query)
    {
    	$query->addSelect('ID');
    	
    	if (...)
    	{
    		$query->addSelect('ISBN');
    	}
    }
    
    function attachOthers(EntityQuery $query)
    {
    	if (...)
    	{
    		$query->setFilter(...);
    	}
    	
    	if (...)
    	{
    		$query->setOrder(...);
    	}
    }

    Также объект EntityQuery позволяет построить запрос, но не выполнять его. Это бывает полезным для выполнения подзапросов или же просто для получения текста запроса и последующего его использования:

    $q = new EntityQuery(BookTable::getEntity());
    $q->setSelect(array('ID'));
    $q->setFilter(array('=PUBLISH_DATE' => new TypeDate('2014-12-13', 'Y-m-d')));
    $sql = $q->getQuery();
    
    file_put_contents('/tmp/today_books.sql', $sql);
    
    // таким образом, запрос "SELECT ID FROM my_book WHERE PUBLISH_DATE='2014-12-31'" будет сохранен в файл, но не будет выполнен.

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

    select, group:

    • setSelect, setGroup — устанавливает массив с именами полей
    • addSelect, addGroup — добавляет имя поля
    • getSelect, getGroup — возвращает массив с именами полей

    filter:

    • setFilter — устанавливает одно- или многомерный массив с описанием фильтра
    • addFilter — добавляет один параметр фильтра со значением
    • getFilter — возвращает текущее описание фильтра

    order:

    • setOrder — устанавливает массив с именами полей и порядком сортировки
    • addOrder — добавляет одно поле с порядком сортировки
    • getOrder — возвращает текущее описание сортировки

    limit/offset:

    • setLimit, setOffset — устанавливает значение
    • getLimit, getOffset — возвращает текущее значение

    runtime fields:

    • registerRuntimeField — регистрирует новое временное поле для исходной сущности

    Объект Query является основополагающим элементом при выборке данных, он же используется внутри стандартного getList. Именно поэтому эффективность переопределения методов getList сводится на нет: если при вызове соответствующего метода хак сработает, то при аналогичном запросе напрямую через Query уже нет.

    Предустановленные выборки

      Глобальная область данных

    При необходимости одну таблицу можно описать несколькими сущностями, разделив записи на сегменты:

        class Element4Table extends BitrixIblockElementTable
        {
            public static function getTableName()
            {
                return 'b_iblock_element';
            }
            
            public static function setDefaultScope(Query $query)
            {
                $query->where("IBLOCK_ID", 4);
            }
        }
        
        class Element5Table extends BitrixIblockElementTable
        {
            public static function getTableName()
            {
                return 'b_iblock_element';
            }
            
            public static function setDefaultScope(Query $query)
            {
                $query->where("IBLOCK_ID", 5);
            }
        }
    

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

      Локальная область данных

    Начиная с версии 20.5.500 появилась возможность задать предустановленные выборки – методы with*. Это аналог setDefaultScope, но не на глобальном уровне, а на пользовательском – вызов при необходимости. После описания метода в сущности можно вызывать его в конструкторе запросов:

        class UserTable
        {
            public static function withActive(Query $query)
            {
                $query->where('ACTIVE', true);
            }
        }
        
        $activeUsers = UserTable::query()
           ->withActive()
           ->fetchCollection();
    
        // WHERE `ACTIVE`='Y'
    

    В качестве аргумента используется объект BitrixMainORMQueryQuery, поэтому можно задавать не только фильтр, но и любые другие параметры запроса. Кроме того, можно дополнить метод своими аргументами, которые также будут переданы при вызове из конструктора запросов:

        class UserTable
        {
            public static function withActive(Query $query, $value)
            {
                $query
                    ->addSelect('LOGIN')
                    ->where('ACTIVE', $value);
            }
        }
        
        $activeUsers = UserTable::query()
            ->withActive(false)
            ->fetchCollection();
            
        // SELECT `LOGIN` ... WHERE `ACTIVE`='N'
    

    Выбор данных из хранимых процедур вместо таблиц

    ORM подходит даже для таких экзотических запросов, как выборка данных не из таблицы, а из хранимых процедур. Такие процедуры могут быть созданы в MSSQL-базе данных.

    Укажем название функции в методе getTableName:

    public static function getTableName()
    {
            // return "foo_table_name"
            return "foo_table_procedure()";
    }

    В этом виде такой код работать не будет. Дело в том, что при использовании подключения BitrixMainDBMssqlConnection все вхождения имен таблиц проходят через экранирование. Попытка сразу выполнить такой запрос приведет к выбрасыванию исключения:

    MS Sql query error: Invalid object name 'foo_table_procedure()'. (400)
    
    SELECT
    
    [base].[bar] AS [BAR],
    
    [base].[baz] AS [BAZ],
    
    FROM [foo_table_procedure()] [base]

    Получению нужного результата мешают только знаки [ и ], которыми MssqlSqlHelper защитил имя «таблицы». Проблема решается созданием собственного подключения Connection и SqlHelper.

    Вариант решения: на сервер установите расширение mssql и реализуйте следующую архитектуру:

    class MssqlSqlHelper extends BitrixMainDBSqlHelper
    {
        public function quote($identifier)
        {
            if (self::isKnowFunctionalCall($identifier))
            {
                return $identifier
            }
            else
            {
                return parent::quote($identifier);
            }
        }
    }
    

    Где self::isKnownFunctionCall — метод проверки, который возвращает true, если в $identifier находится “foo_table_procedure()”.

    Примечание: Пример разработан компанией Интерволга.

    Выборки в отношениях 1:N и N:M

    При работе с отношениями 1:N и N:M в общем случае и с множественными свойствами инфоблока в частности можно столкнуться с двумя проблемами.

    Логика LIMIT

    Интуитивное ожидание логики работы LIMIT:

       $iblockEntity = IblockTable::compileEntity(…);
       
        $query = $iblockEntity->getDataClass()::query()
            ->addSelect('NAME')
            ->addSelect('MULTI_PROP_1')
        ->setLimit(5);
    
        $elements = $query->fetchCollection();

    Ожидать в данном примере выборку 5 элементов будет ошибкой. Лимит указывается не на уровне объектов, а на уровне SQL запроса:

        SELECT ... FROM `b_iblock_element`
            LEFT JOIN `b_iblock_element_property` ...
        LIMIT 5

    Фактически будет выбрано 5 значений свойств с соответствующими элементами. Поэтому в выборке может оказаться менее 5 элементов, или вовсе 1 элемент с не полностью выбранными значениями свойства.

    Выбор полей соотношений в одном запросе

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

        $iblockEntity = IblockTable::compileEntity(…);
        
        $query = $iblockEntity->getDataClass()::query()
            ->addSelect('NAME')
            ->addSelect('MULTI_PROP_1')
            ->addSelect('MULTI_PROP_2')
            ->addSelect('MULTI_PROP_3');
        
        $elements = $query->fetchCollection();

    Выполнится запрос вида:

        SELECT ... FROM `b_iblock_element`
            LEFT JOIN `b_iblock_element_property` ... // 15 значений свойства
            LEFT JOIN `b_iblock_element_property` ... // 7 значений свойства
            LEFT JOIN `b_iblock_element_property` ... // 11 значений свойства

    И если интуитивно кажется, что будет выбрано 15 + 7 + 11 = 33 строки, то фактически будет выбрано 15 * 7 * 11 = 1155 строк. Если свойств или значений в запросе еще больше, то счет может идти на миллионы результирующих записей, и как следствие — о нехватке памяти в приложении для получения всего результата.

    Решение проблем

    Для обхода этих проблем был добавлен класс BitrixMainORMQueryQueryHelper с универсальным методом decompose:

        /**
        ** Декомпозиция запросов с 1:N и N:M отношениями
        ** 
        ** @param Query $query
        ** @param bool $fairLimit При установке этой опции сначала выбираются ID объектов, а следующим запросом остальные данные с фильтром по ID
        ** @param bool $separateRelations При установке этой опции каждое 1:N или N:M отношение выбирается отдельным запросом
        ** @return Collection
        **/
        public static function decompose(Query $query, $fairLimit = true, $separateRelations = true)

    Параметр fairLimit приводит к двум запросам: сначала выбирается primary записей с заданным Limit / Offset в запросе, и после этого для всех primary одним запросом выбираются все отношения.

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

    В качестве результата будет возвращена готовая коллекция объектов с уже объединенными данными.

    Взаимосвязи между сущностями (устаревший вариант)

    Примечание: в уроке описан вариант отношений до версии 18.0.4 модуля main. Новый вариант приведен в главе Отношения.

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

  • отношения один к одному
  • один ко многим 1:N
  • много ко многим M:N
  • Отношения 1:1

    Самый простой тип связи — это когда элемент сущности ссылается на один элемент другой или этой же сущности. В описываемых примерах рассматривается каталог книг, но до сих пор в записях книг не было какого-либо указания на авторство. Реализуем это: прежде всего, опишем сущность Автора книги:

    <?php
    
    namespace SomePartnerMyBooksCatalog;
    
    use BitrixMainEntity;
    
    class AuthorTable extends EntityDataManager
    {
    	public static function getTableName()
    	{
    		return 'my_book_author';
    	}
    
    	public static function getMap()
    	{
    		return array(
    			new EntityIntegerField('ID', array(
    				'primary' => true,
    				'autocomplete' => true
    			)),
    			new EntityStringField('NAME'),
    			new EntityStringField('LAST_NAME')
    		);
    	}
    }

    В данном примере считается, что у книги есть только один автор — это человек с именем и фамилией, запись о котором есть в сущности Author. При этом у одного автора может быть много книг. Таким образом, получается отношение «1 автор : N книг».

    Описать такое отношение можно следующим образом:

    class BookTable extends EntityDataManager
    {
    	...
    	public static function getMap()
    	{
    		return array(
    			...
    			new EntityIntegerField('AUTHOR_ID'),
    			new EntityReferenceField(
    				'AUTHOR',
    				'SomePartnerMyBooksCatalogAuthor',
    				array('=this.AUTHOR_ID' => 'ref.ID'),
    			)
    		);
    	}
    	...
    }

    В первую очередь нужно добавить числовое поле AUTHOR_ID, в котором будет храниться ID автора. На основе этого поля конфигурируется связь между сущностями через новый тип поля — ReferenceField. Это виртуальное поле, не имеющее фактического отражения в БД:

    new EntityReferenceField(
    	'AUTHOR',
    	'SomePartnerMyBooksCatalogAuthor',
    	array('=this.AUTHOR_ID' => 'ref.ID'),
    	array('join_type' => 'LEFT')
    )
    // LEFT JOIN my_book_author ON my_book.AUTHOR_ID = my_book_author.ID
    Параметры Описание
    Первый задается имя поля
    Второй название сущности-партнера, с которым формируется отношение
    Третий описывает, по каким полям связаны сущности, и задается в формате, похожем на фильтр для секции `select` в getList. Ключами и значениями являются имена полей с префиксами:

    • this. — поле текущей сущности,
    • ref. — поле сущности-партнера.
    Четвёртый, дополнительный можно указать тип подключения таблицы `join_type` — LEFT (по умолчанию), RIGHT, INNER.

    Теперь можно воспользоваться описанным отношением при выборке данных:

    BookTable::getList(array(
    	'select' => array('TITLE', 'AUTHOR.NAME', 'AUTHOR.LAST_NAME')
    ));
    
    SELECT 
    	`somepartner_mybookscatalog_book`.`TITLE` AS `TITLE`,
    	`somepartner_mybookscatalog_author`.`NAME` AS `SOMEPARTNER_MYBOOKSCATALOG_AUTHOR_NAME`,
    	`somepartner_mybookscatalog_author`.`LAST_NAME` AS `SOMEPARTNER_MYBOOKSCATALOG_AUTHOR_LAST_NAME`
    FROM `my_book`
    LEFT JOIN `my_book_author` `somepartner_mybookscatalog_author` ON `somepartner_mybookscatalog_book`.`AUTHOR_ID` = `somepartner_mybookscatalog_author`.`ID`

    Переход в сущность Автор осуществляется записью «AUTHOR.» — указывается имя Reference поля, и после точки контекст переключатся на данную сущность. Далее можно указать имя поля из этой сущности, в том числе Reference, перейдя таким образом еще дальше и образовав новое подключение таблицы:

    'select' => array('AUTHOR.CITY.COUNTRY.NAME')

    Примерно так выглядел бы запрос по выбору страны проживания автора книги, если бы была структура Страны > Города > Живущие в городах авторы книг.

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

    BookTable::getList(array(
    	'select' => array(
    		'TITLE',
    		'AUTHOR_NAME' => 'AUTHOR.NAME',
    		'AUTHOR_LAST_NAME' => 'AUTHOR.LAST_NAME'
    	)
    ));
    SELECT 
    	`somepartner_mybookscatalog_book`.`TITLE` AS `TITLE`,
    	`somepartner_mybookscatalog_author`.`NAME` AS `AUTHOR_NAME`,
    	`somepartner_mybookscatalog_author`.`LAST_NAME` AS `AUTHOR_LAST_NAME`
    FROM ...

    И, аналогично исходной сущности, можно использовать символ ‘*’ для выборки всех скалярных полей сущности, по желанию так же используя сокращение алиасов:

    BookTable::getList(array(
    	'select' => array(
    		'TITLE',
    		'AR_' => 'AUTHOR.*'
    	)
    ));
    SELECT 
    	`somepartner_mybookscatalog_author`.`ID` AS `AR_ID`,
    	`somepartner_mybookscatalog_author`.`NAME` AS `AR_NAME`,
    	`somepartner_mybookscatalog_author`.`LAST_NAME` AS `AR_LAST_NAME`
    FROM `my_book` `somepartner_mybookscatalog_book` 
    LEFT JOIN `my_book_author` `somepartner_mybookscatalog_author` ON `somepartner_mybookscatalog_book`.`AUTHOR_ID` = `somepartner_mybookscatalog_author`.`ID`

    Как было упомянуто выше, условия связывания сущностей описываются подобно фильтру. Это значит, что можно соединять таблицы по нескольким полям, а также применять SqlExpression:

    $author_type = 5;
    
    new EntityReferenceField(
    	'AUTHOR',
    	'SomePartnerMyBooksCatalogAuthor',
    	array(
    		'=this.AUTHOR_ID' => 'ref.ID',
    		'=ref.TYPE' => new DBSqlExpression('?i', $author_type)
    	)
    )
    // LEFT JOIN my_book_author ON my_book.AUTHOR_ID = my_book_author.ID AND my_book_author.TYPE = 5

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

    При частом использовании поля соседней сущности можно воспользоваться ExpressionField и определить удаленное поле как локальное:

    new EntityExpressionField('AUTHOR_NAME', '%s', 'AUTHOR.NAME')

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

    Отношение 1:N или обратный Reference

    Концепция ReferenceField подразумевает, что это поле нужно размещать в сущности N отношения 1:N. То есть, Reference должен указывать строго на одну запись: в примере выше принято, что у книги может быть только 1 автор, и таким образом ReferenceField в сущности Книг указывает на одну запись сущности Автор.

    Как выбрать автора книги понятно, поскольку в сущности Книги есть указание на связь с сущностью автора. Но как выбрать все книги автора, если в сущности Автора нет явного указания на Книги?

    Дело в том, что описанного Reference в сущности книги уже достаточно для двусторонней выборки, нужно лишь использовать специальный синтаксис:

    SomePartnerMyBooksCatalogAuthorTable::getList(array(
    	'select' => array(
    		'NAME',
    		'LAST_NAME',
    		'BOOK_TITLE' => 'SomePartnerMyBooksCatalogBookTable:AUTHOR.TITLE'
    	)
    ));
    SELECT 
    	`somepartner_mybookscatalog_author`.`NAME` AS `NAME`,
    	`somepartner_mybookscatalog_author`.`LAST_NAME` AS `LAST_NAME`,
    	`somepartner_mybookscatalog_author_book_author`.`TITLE` AS `BOOK_TITLE`
    FROM `my_book_author` `somepartner_mybookscatalog_author` 
    LEFT JOIN `my_book` `somepartner_mybookscatalog_author_book_author` ON `somepartner_mybookscatalog_author_book_author`.`AUTHOR_ID` = `somepartner_mybookscatalog_author`.`ID`

    Вместо имени Reference, нужно указать «Название сущности, у которой есть Reference на текущую сущность»:»Имя референса на текущую сущность». После такой конструкции контекст переключается на сущность Книга, и в ней уже можно выбрать поле TITLE и другие поля.

    Отношения M:N

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

    Описываем сущность тегов:

    <?php
    
    namespace SomePartnerMyBooksCatalog;
    
    use BitrixMainEntity;
    
    class TagTable extends EntityDataManager
    {
    	public static function getTableName()
    	{
    		return 'my_book_tag';
    	}
    
    	public static function getMap()
    	{
    		return array(
    			new EntityIntegerField('ID', array(
    				'primary' => true,
    				'autocomplete' => true
    			)),
    			new EntityStringField('NAME')
    		);
    	}
    }

    Чтобы связать эту сущность с Книгами по принципу N:M (у одной книги может быть несколько тегов, один тег может быть связан с несколькими книгами), необходимо создать промежуточную сущность с таблицей в БД, где будут храниться данные о связях книг с тегами.

    <?php
    
    namespace SomePartnerMyBooksCatalog;
    
    use BitrixMainEntity;
    
    class BookTagTable extends EntityDataManager
    {
    	public static function getTableName()
    	{
    		return 'my_book_to_tag';
    	}
    
    	public static function getMap()
    	{
    		return array(
    			new EntityIntegerField('BOOK_ID', array(
    				'primary' => true
    			)),
    			new EntityReferenceField(
    				'BOOK',
    				'SomePartnerMyBooksCatalogBook',
    				array('=this.BOOK_ID' => 'ref.ID')
    			),
    			new EntityIntegerField('TAG_ID', array(
    				'primary' => true
    			)),
    			new EntityReferenceField(
    				'TAG',
    				'SomePartnerMyBooksCatalogTag',
    				array('=this.TAG_ID' => 'ref.ID')
    			)
    		);
    	}
    }

    Все, что в данном случае необходимо от этой сущности, это хранить связь «ID книги — ID тега», а соответствующие ReferenceField помогут программно описать данную связь.

    Сама по себе промежуточная сущность может быть неинтересна, типовые задачи в данном случае — это получить список тегов для книги или получить список книг по тегу. Решаются с помощью описанных выше методов работы с Reference:

    // теги для книги с ID = 5
    SomePartnerMyBooksCatalogBookTable::getList(array(
    	'filter' => array('=ID' => 5),
    	'select' => array(
    		'ID',
    		'TITLE',
    		'TAG_NAME' => 'SomePartnerMyBooksCatalogBookTag:BOOK.TAG.NAME'
    	)
    ));
    SELECT 
    	`somepartner_mybookscatalog_book`.`ID` AS `ID`,
    	`somepartner_mybookscatalog_book`.`TITLE` AS `TITLE`,
    	`somepartner_mybookscatalog_book_book_tag_book_tag`.`NAME` AS `TAG_NAME`
    FROM `my_book` `somepartner_mybookscatalog_book` 
    LEFT JOIN `my_book_to_tag` `somepartner_mybookscatalog_book_book_tag_book` ON `somepartner_mybookscatalog_book_book_tag_book`.`BOOK_ID` = `somepartner_mybookscatalog_book`.`ID`
    LEFT JOIN `my_book_tag` `somepartner_mybookscatalog_book_book_tag_book_tag` ON `somepartner_mybookscatalog_book_book_tag_book`.`TAG_ID` = `somepartner_mybookscatalog_book_book_tag_book_tag`.`ID`
    WHERE `somepartner_mybookscatalog_book`.`ID` = 5

    Запись `SomePartnerMyBooksCatalogBookTag:BOOK.TAG.NAME` может показаться сложной, но при рассмотрении ее поэтапно все должно проясниться:

    Параметры Описание
    BookTable::getList исходная сущность — BookTable
    SomePartnerMyBooksCatalogBookTag:BOOK переход в сущность BookTag через ее референс BOOK, текущая сущность — BookTag
    TAG переход по референсу TAG из BookTag, текущая сущность — Tag
    NAME поле из текущей сущности Tag

    Для получения книг с определенным тегом цепочка вызова будет очень похожей:

    // книги для тега с ID = 11
    SomePartnerMyBooksCatalogTagTable::getList(array(
    	'filter' => array('=ID' => 11),
    	'select' => array(
    		'ID',
    		'NAME',
    		'BOOK_TITLE' => 'SomePartnerMyBooksCatalogBookTag:TAG.BOOK.TITLE'
    	)
    ));

    Таким образом, используя два вида перехода между сущностями — REFERENCE и Entity:REFERENCE — можно достаточно легко добраться до нужной связанной информации.

    Фильтр ORM

    В обновлении main 17.5.2 в ORM появился новый фильтр.

    Одиночные условия

    Пример простейшего запроса:

    BitrixMainUserTable::query()
       ->where("ID", 1)
       ->exec();
    
    // WHERE `main_user`.`ID` = 1

    Если нужен другой оператор сравнения, то он указывается явно:

    BitrixMainUserTable::query()
       ->where("ID", "<", 10)
       ->exec();
    
    // WHERE `main_user`.`ID` < 10

    Пример с использованием IS NULL:

    BitrixMainUserTable::query()
       ->whereNull("ID")
       ->exec();
    
    // WHERE `main_user`.`ID` IS NULL

    Для всех where* методов есть whereNot* аналоги. Пример:

    BitrixMainUserTable::query()
       ->whereNotNull("ID")
       ->exec();
    
    // WHERE `main_user`.`ID` IS NOT NULL

    Помимо общего where, можно использовать следующие операторные методы:

    whereNull($column) / whereNotNull($column)
    
    whereIn($column, $values|Query|SqlExpression) / whereNotIn($column, $values|Query|SqlExpression)
    
    whereBetween($column, $valueMin, $valueMax) / whereNotBetween($column, $valueMin, $valueMax)
    
    whereLike($column, $value) / whereNotLike($column, $value)
    
    whereExists($query|SqlExpression) / whereNotExists($query|SqlExpression)

    Для произвольного выражения с привязкой к полям сущности следует использовать метод [dw]whereExpr[/dw][di]Доступно с версии 20.400.0 модуля main.[/di]:

    BitrixMainUserTable::query()
    ->whereExpr('JSON_CONTAINS(%s, 4)', ['SOME_JSON_FIELD'])
    ->exec();
    
    // WHERE JSON_CONTAINS(`main_user`.`SOME_JSON_FIELD`, 4)
    

    Аргументы выражения и составных полей аналогичны конструктору поля ExpressionField и подставляются через функцию [ds]sprintf[/ds][di]sprintf — Возвращает отформатированную строку.

    Подробнее в документации по PHP.[/di].

    Список операторов находится в BitrixMainORMQueryFilterOperator::$operators (см. ключи массива):

    = , <> , != , < , <= , > , >= , in , between , like , exists

    Сравнение с другим полем

    Отдельный метод whereColumn упрощает сравнение полей друг с другом:

    BitrixMainUserTable::query()
       ->whereColumn('NAME', 'LOGIN')
       ->exec();
    
    // WHERE `main_user`.`NAME` = `main_user`.`LOGIN`

    Этот метод мало чем отличается от where, и формально это тот же самый вызов с небольшой оберткой:

    BitrixMainUserTable::query()
       ->where('NAME', new QueryFilterExpressionColumn('LOGIN'))
       ->exec();
    
    // WHERE `main_user`.`NAME` = `main_user`.`LOGIN`

    whereColumn обеспечивает особую гибкость использования колонок в фильтре, например:

    BitrixMainUserTable::query()
       ->whereIn('LOGIN', [
          new Column('NAME'),
          new Column('LAST_NAME')
       ])
       ->exec();
    
    // WHERE `main_user`.`LOGIN` IN (`main_user`.`NAME`, `main_user`.`LAST_NAME`)

    Колонки можно использовать в любом операторе. И они будут восприняты именно как поля конкретных сущностей, а не просто произвольное SQL выражение.

    Множественные условия

    Для нескольких условий предполагается такая запись:

    BitrixMainUserTable::query()
       ->where('ID', '>', 1)
       ->where('ACTIVE', true)
       ->whereNotNull('PERSONAL_BIRTHDAY')
       ->whereLike('NAME', 'A%')
       ->exec();
    
    // WHERE `main_user`.`ID` > 1 AND `main_user`.`ACTIVE` = 'Y' AND `main_user`.`PERSONAL_BIRTHDAY` IS NOT NULL AND `main_user`.`NAME` LIKE 'A%'

    Примечание: для boolean полей со значениями Y/N, 1/0 и т.п. можно использовать true и false.

    Если необходимо указать несколько условий в одном вызове, то использовать такой формат: (операторные методы можно заменять кодами операторов)

    BitrixMainUserTable::query()
       ->where([
          ['ID', '>', 1],
          ['ACTIVE', true],
          ['PERSONAL_BIRTHDAY', '<>', null],
          ['NAME', 'like', 'A%']
       ])
       ->exec();
    
    // WHERE `main_user`.`ID` > 1 AND `main_user`.`ACTIVE` = 'Y' AND `main_user`.`PERSONAL_BIRTHDAY` IS NOT NULL AND `main_user`.`NAME` LIKE 'A%'

    OR и вложенные фильтры

    Для хранения всех условий фильтра в Query используется контейнер условий BitrixMainEntityQueryFilterConditionTree. Помимо стандартных условий, в него допускается добавление других экземпляров ConditionTree, таким образом создавая любой уровень ветвления и вложенности.

    Все приведенные выше вызовы where — проксирование к базовому контейнеру. Следующие два вызова приведут к совершенно одинаковому результату:

    BitrixMainUserTable::query()
       ->where([
          ['ID', '>', 1],
          ['ACTIVE', true]
       ])
       ->exec();
    
    BitrixMainUserTable::query()
       ->where(Query::filter()->where([
          ["ID", '>', 1],
          ['ACTIVE', true]
       ]))->exec();
    
    // WHERE `main_user`.`ID` > 1 AND `main_user`.`ACTIVE` = 'Y'

    Вместо массива использовался объект фильтра. Это позволяет создавать субфильтры и менять логику с AND на OR:

    BitrixMainUserTable::query()
       ->where('ACTIVE', true)
       ->where(Query::filter()
          ->logic('or')
          ->where([
             ['ID', 1],
             ['LOGIN', 'admin']
          ])
       )->exec();
    
    // WHERE `main_user`.`ACTIVE` = 'Y' AND (`main_user`.`ID` = 1 OR `main_user`.`LOGIN` = 'admin')

    Допускается использование цепочки вызовов:

    BitrixMainUserTable::query()
       ->where('ACTIVE', true)
       ->where(Query::filter()
          ->logic('or')
          ->where('ID', 1)
          ->where('LOGIN', 'admin')
       )
       ->exec();
    
    // WHERE `main_user`.`ACTIVE` = 'Y' AND (`main_user`.`ID` = 1 OR `main_user`.`LOGIN` = 'admin')

    Выражения

    В фильтре в качестве имен полей допустимо задание ExpressionField, которые автоматически регистрируются как runtime поля сущности.

    BitrixMainUserTable::query()
       ->where(new ExpressionField('LNG', 'LENGTH(%s)', 'LAST_NAME'), '>', 10)
       ->exec();
    
    // WHERE LENGTH(`main_user`.`LAST_NAME`) > '10'

    Для упрощения подобных конструкций добавлен хелпер, строящий вычисляемые поля:

    BitrixMainUserTable::query()
       ->where(Query::expr()->length("LAST_NAME"), '>', 10)
       ->exec();
    
    // WHERE LENGTH(`main_user`.`LAST_NAME`) > '10'
    
    BitrixMainUserTable::query()
       ->addSelect(Query::expr()->count("ID"), 'CNT')
       ->exec();
    
    // SELECT COUNT(`main_user`.`ID`) AS `CNT` FROM `b_user` `main_user`

    В хелпере заложены наиболее популярные SQL выражения:

    • count
    • countDistinct
    • sum
    • min
    • avg
    • max
    • length
    • lower
    • upper
    • concat

    Совместимость с getList

    Если вместо цепочки вызовов Query использовать getList, то фильтр вставляется в него вместо массива:

    BitrixMainUserTable::getList([
       'filter' => ['=ID' => 1]
    ]);
    
    BitrixMainUserTable::getList([
       'filter' => Query::filter()
          ->where('ID', 1)
    ]);
    
    // WHERE `main_user`.`ID` = 1

    Условия JOIN

    Описания референсов представлено в таком формате:

    new EntityReferenceField('GROUP', GroupTable::class,
        Join::on('this.GROUP_ID', 'ref.ID')
    )

    Метод `on` — короткая и более семантически уместная запись Query::filter() с предустановленным условием по колонкам. Возвращает инстанс фильтра, и можно строить какие угодно условия JOIN:

    new EntityReferenceField('GROUP', GroupTable::class,
        Join::on('this.GROUP_ID', 'ref.ID')
            ->where('ref.TYPE', 'admin')
            ->whereIn('ref.OPTION', [
                new Column('this.OPTION1'),
                new Column('this.OPTION2'),
                new Column('this.OPTION3')
            ]
    )
    

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

    ->whereColumn('this.AUTHOR.UserGroup:USER.GROUP.OWNER.ID', 'ref.ID');

    Формат массива

    Для использования фильтра в виде массива существует метод конвертации из массива в объект BitrixMainORMQueryFilterConditionTree::createFromArray. Формат массива имеет общий вид:

    $filter = [
        ['FIELD', '>', 2],
        [
            'logic' => 'or',
            ['FIELD', '<', 8],
            ['SOME', 9]
        ],
        ['FIELD', 'in', [5, 7, 11]],
        ['FIELD', '=', ['column' => 'FIELD2']],
        ['FIELD', 'in', [
            ['column' => 'FIELD1'],
            ['value' => 'FIELD2'],
            'FIELD3']
        ],
        [
            'negative' => true,
            ['FIELD', '>', 19]
        ],
     ];
    

    При обычном сравнении значение передается либо непосредственно:

    ['FIELD', '>', 2]
    

    либо в виде массива с ключом value:

    ['FIELD', '>', ['value' => 2]]
    

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

    ['FIELD1', '>', ['column' => 'FIELD2']]
    

    Вложенные фильтры передаются в качестве аналогичных вложенных массивов. В качестве замены методов объекта для отрицания negative() и изменения логики logic() используются одноименные ключи:

    $filter = [
        ['FIELD', '>', 2],
        [
            'logic' => 'or',
            ['FIELD', '<', 8],
            ['SOME', 9]
        ],
        [
            'negative' => true,
            ['FIELD', '>', 19]
        ]
    ]
    

    Остальные методы where* заменяются соответствующими операторами сравнения in, between, like и т.д.

    ['FIELD', 'in', [5, 7, 11]]
    

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

    Автоматическая генерация ORM-классов

    Автоматическая генерация

    Для использования генератора ORM классов перейдите на страницу Настройки > Настройки продукта > Настройки модулей > Монитор производительности (модуль Монитор производительности (perfmon) должен быть установлен) и на вкладке [dw]Генератор таблетов[/dw][di]Вкладка доступна с версии 20.0.100 модуля Монитор производительности (perfmon).

    [/di] отметьте поле Разрешить генерацию таблетов для ORM.

    У генератора имеются следующие параметры:

    Параметр Описание
    Использовать короткие алиасы классов При включенной опции вызовы классов будут вида:

    new IntegerField('ИМЯ_ПОЛЯ')
    new LengthValidator(null, 255)
    new Reference()
    

    При выключенной:

    new FieldsIntegerField('ИМЯ_ПОЛЯ')
    new FieldsValidatorsLengthValidator(null, 255)
    new FieldsRelationsReference()
    
    Описание параметров полей таблета через методы При включенной опции практически все параметры поля задаются через вызовы соответствующих методов:

    (new StringField('API_CODE'))->configureSize(50)
    

    При выключенной:

    new StringField('API_CODE', ['size' => 50])
    
    Имена полей в карте При включенной опции метод getMap таблета будет возвращать массив, где индексами являются имена полей. При выключенной — нумерованный массив.

    Использование генератора ORM классов до версии perfmon 20.0.100

    После включения генератора таблетов на странице Настройки > Производительность > Таблицы
    в меню действий станет доступен пункт ORM.

    Примечание: Имена файлов таблетов предлагаются с суффиксом table (Например: было product.php, стало — producttable.php).

    Примечание: Описания полей создаются в виде создания объектов-наследников BitrixMainORMDataField (а не в виде ассоциативных массивов как было до версии 20.0.100 модуля perfmon )

    Для полей типа timestamp / date / datetime анализируется дефолтное значение (sql). Если это функция, возвращающая текущее время, то в таблете создается функция для получения текущего времени/даты:

    'default' => function()
    {
       return new DateTime();
    }
    

    Другие особенности для полей:

    • Поле помечается как mandatory только в случае, когда поле является обязательным и не имеет значения по-умолчанию;
    • Поля типа text / mediumtext не помечаются как строка.

      Пример

    Пример автоматического создания ORM-класса

    Рассмотрим пример автоматического создания ORM-класса для штатной таблицы шаблонов сайта b_site_template. Воспользуемся пунктом ORM и получим следующий код:

    <?php
    namespace BitrixSite;
    
    use BitrixMainLocalizationLoc,
    	BitrixMainORMDataDataManager,
    	BitrixMainORMFieldsIntegerField,
    	BitrixMainORMFieldsStringField,
    	BitrixMainORMFieldsValidatorsLengthValidator;
    
    Loc::loadMessages(__FILE__);
    
    /**
     * Class TemplateTable
     * 
     * Fields:
     * <ul>
     * <li> ID int mandatory
     * <li> SITE_ID string(2) mandatory
     * <li> CONDITION string(255) optional
     * <li> SORT int optional default 500
     * <li> TEMPLATE string(255) mandatory
     * </ul>
     *
     * @package BitrixSite
     **/
    
    class TemplateTable extends DataManager
    {
    	/**
    	 * Returns DB table name for entity.
    	 *
    	 * @return string
    	 */
    	public static function getTableName()
    	{
    		return 'b_site_template';
    	}
    
    	/**
    	 * Returns entity map definition.
    	 *
    	 * @return array
    	 */
    	public static function getMap()
    	{
    		return [
    			new IntegerField(
    				'ID',
    				[
    					'primary' => true,
    					'autocomplete' => true,
    					'title' => Loc::getMessage('TEMPLATE_ENTITY_ID_FIELD')
    				]
    			),
    			new StringField(
    				'SITE_ID',
    				[
    					'required' => true,
    					'validation' => [__CLASS__, 'validateSiteId'],
    					'title' => Loc::getMessage('TEMPLATE_ENTITY_SITE_ID_FIELD')
    				]
    			),
    			new StringField(
    				'CONDITION',
    				[
    					'validation' => [__CLASS__, 'validateCondition'],
    					'title' => Loc::getMessage('TEMPLATE_ENTITY_CONDITION_FIELD')
    				]
    			),
    			new IntegerField(
    				'SORT',
    				[
    					'default' => 500,
    					'title' => Loc::getMessage('TEMPLATE_ENTITY_SORT_FIELD')
    				]
    			),
    			new StringField(
    				'TEMPLATE',
    				[
    					'required' => true,
    					'validation' => [__CLASS__, 'validateTemplate'],
    					'title' => Loc::getMessage('TEMPLATE_ENTITY_TEMPLATE_FIELD')
    				]
    			),
    		];
    	}
    
    	/**
    	 * Returns validators for SITE_ID field.
    	 *
    	 * @return array
    	 */
    	public static function validateSiteId()
    	{
    		return [
    			new LengthValidator(null, 2),
    		];
    	}
    
    	/**
    	 * Returns validators for CONDITION field.
    	 *
    	 * @return array
    	 */
    	public static function validateCondition()
    	{
    		return [
    			new LengthValidator(null, 255),
    		];
    	}
    
    	/**
    	 * Returns validators for TEMPLATE field.
    	 *
    	 * @return array
    	 */
    	public static function validateTemplate()
    	{
    		return [
    			new LengthValidator(null, 255),
    		];
    	}
    }
    File: /bitrix/modules/site/lang/ru/lib/templatetable.php
    <?
    $MESS["TEMPLATE_ENTITY_ID_FIELD"] = "";
    $MESS["TEMPLATE_ENTITY_SITE_ID_FIELD"] = "";
    $MESS["TEMPLATE_ENTITY_CONDITION_FIELD"] = "";
    $MESS["TEMPLATE_ENTITY_SORT_FIELD"] = "";
    $MESS["TEMPLATE_ENTITY_TEMPLATE_FIELD"] = "";
    ?>
    Таблицы базы данных

    Преобразовав этот результат нужным образом получим настоящий класс для работы с шаблонами, расположенный по пути /bitrix/modules/site/lib/templatetable.php.

    Использование ORM или почему поля из SELECT и ORDER BY автоматически попадают в GROUP BY

    При использовании ORM, а именно в процессе выборки данных, у разработчиков часто возникает вопрос: «Почему в GROUP BY автоматически попадают некоторые поля? Я не указывал этого явно в вызове getList/Query«. В уроке будет рассмотрено, что это за явление, и почему так и должно быть.

    Коробочные продукты 1С-Битрикс ранее поддерживали работу с тремя СУБД: MySQL, Oracle, SQL Server. И если MySQL всем хорошо знаком и в интернет-проектах его выбирают чаще других, то Oracle и SQL Server — выбор более серьезных и масштабных проектов. Enterprise сегмент обязывает СУБД отвечать более высоким требованиям, в том числе соблюдать стандарты.

    Рассмотрим пошагово, как работает выборка данных в вышеперечисленных системах:

    SELECT t.* FROM a_city t

    Это обычная выборка всех данных из таблицы: у нас есть 7 населенных городов, привязанных к федеральным округам.

    Зададим условия выборки — только Центральный и Северо-Западный округа:

    SELECT NAME, REGION FROM a_city t WHERE t.REGION IN ('ЦФО','СЗФО') 

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

    Теперь сгруппируем получившуюся выборку — посчитаем, сколько городов в каждом округе:

    SELECT COUNT(*), t.REGION FROM a_city t WHERE t.REGION IN ('ЦФО','СЗФО') GROUP BY t.REGION 

    Предыдущая выборка была «схлопнута» по уникальным значениям REGION, и для каждого такого значения было подсчитано количество «схлопнувшихся записей»:

    Группируем выборку SELECT NAME, REGION FROM a_city t WHERE t.REGION IN ('ЦФО','СЗФО') по региону.

    До этого момента ни у кого, как правило, вопросов не возникает.

    А теперь довольно распространенный случай — разработчик решает, что в сгруппированной выборке ему не хватает количества жителей из городов:

    SELECT COUNT(*), t.REGION, POPULATION FROM a_city t WHERE t.REGION IN ('ЦФО','СЗФО') GROUP BY t.REGION 

    Отлично, MySQL успешно отработал запрос и вернул жителей (на самом деле — нет), разработчик доволен (а зря). Задача, казалось бы, решена, но почему такой же запрос на Oracle выдаст ошибку

    ORA-00979: not a GROUP BY expression

    а SQL Server ответит

    Column ‘a_city.POPULATION’ is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.

    Давайте разберемся, что за числа вернул нам MySQL вместо ошибки:

    SELECT NAME, REGION, POPULATION FROM a_city t WHERE t.REGION IN ('ЦФО','СЗФО') 

    Именно так выглядела выборка перед тем, как началась группировка. Похоже, MySQL просто взял первые попавшиеся значения для каждого округа. Что это дает разработчику? Совершенно ничего — эти числа не имеют никакого смысла, их значения непредсказуемы.

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

    SELECT COUNT(*), t.REGION, SUM(POPULATION) FROM a_city t WHERE t.REGION IN ('ЦФО','СЗФО') GROUP BY t.REGION  

    или среднее количество жителей в городах для каждого региона:

    SELECT COUNT(*), t.REGION, ROUND(AVG(POPULATION),0) FROM a_city t WHERE t.REGION IN ('ЦФО','СЗФО') GROUP BY t.REGION  

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

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

    Возвращаясь к построителю запросов в Bitrix Framework — он следит за соблюдением данного правила и при обнаружении неагрегированных полей добавляет их в секцию GROUP BY.

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

    SELECT COUNT(*), t.REGION, t.POPULATION FROM a_city t WHERE t.REGION IN ('ЦФО','СЗФО') GROUP BY t.REGION, t.POPULATION  

    Результат показывает, сколько в регионе городов с определенным количеством жителей. Только так СУБД понимает, что от нее хочет разработчик.

    Вроде бы разобрались: нужно либо указывать агрегацию, либо группировать по полю, иначе его значение не имеет смысла. Вдруг разработчик решает добавить сортировку по ID, и вновь видит, как поле ID автоматически попадает в GROUP BY и «ломает» результат:

    SELECT COUNT(*), t.REGION, SUM(t.POPULATION) FROM a_city t WHERE t.REGION IN ('ЦФО','СЗФО') GROUP BY t.REGION, t.ID ORDER BY ID DESC  

    Как уже можно было догадаться, если не добавить ID в группировку, то Oracle и SQL Server вновь откажутся выполнять запрос, ссылаясь на неопределенность при агрегации данных. Что на этот раз?

    Все дело в том, что сортировка происходит уже после группировки/агрегации данных. Неважно, что поле ID есть в исходной таблице — после группировки мы получаем новую виртуальную таблицу с агрегированными данными.

    Получается, нужно добавить поле ID в промежуточный сгруппированный результат — только тогда сортировка по этому полю будет возможна. И тут мы возвращаемся к прежнему правилу, уточняя его:

    Важное правило: Если в выборке есть агрегация или группировка (агрегация по уникальному значению) хотя бы для одной колонки, то все остальные колонки из SELECT и ORDER BY должны быть так же агрегированы или сгруппированы.

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

    Примечание: На WHERE это правило не распространяется — данная фильтрация производится как раз ДО группировки, там нужны именно оригинальные значения колонок. Фильтрация по агрегированным значениям происходит в секции HAVING, и если там окажется колонка без агрегации — вновь для осмысленного результата будет необходима предварительная группировка значений этой колонки. При этом построитель запросов ORM сам позаботится о распределении фильтра в WHERE и HAVING — вам не нужно забивать этим голову, как и в случае с автоматической группировкой.

    Заключение

    Если в конкретном запросе автоматическое добавление полей в GROUP BY стало для вас неприятным сюрпризом, то:

    • Вы добавили поле в выборку по привычке или случайно, на самом деле его значение вам не нужно
    • или

    • Вы забыли указать агрегацию (MAX, MIN, SUM, AVG и т.д.)

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

    ORM в Bitrix Framework сама исправляет подобные неточности. В случае же прямых запросов отключить такое поведение и заставить MySQL следовать стандартам и здравому смыслу поможет настройка ONLY_FULL_GROUP_BY.

    Постраничная навигация

    Как реализовать постраничную навигацию в ядре D7 для выборок из ORM. (Доступно с версии 16.0.0)

    Принципы

    Постраничная навигация — вспомогательный объект пользовательского интерфейса. В старом ядре постраничка была в объекте результата запроса, а чтобы выбрать с лимитами, приходилось вызывать специальную функцию CDBResult::NavQuery(), в которую передавался текст запроса. В D7 постраничная навигация — это просто вспомогательный объект пользовательского интерфейса. Первичным считается запрос, а постраничка просто помогает подставить правильные limit и offset.

    Обязательность использования limit в запросах. Некорректно оставлять на долю php всю потенциально большую выборку без ограничений. Если выборка все-таки без лимитов, то возможно использование новой навигации, но разработчик это делает на свой страх и риск, без рекомендаций вендора. При этом необходимо решать вопросы использования разных БД и другие задачи.

    Для постраничной навигации нужно знать количество записей. Как и в старом ядре для этого необходимо сделать отдельный запрос с COUNT. На большом количестве записей это эффективнее полной выборки, а на маленьком — не имеет значения. Но если имеется не просто большая, а гигантская выборка, то постраничка на текущем ядре вполне справится и без COUNT.

    Инструменты для постраничной навигации

    • класс BitrixMainUIPageNavigation для простой навигации;
    • класс BitrixMainUIReversePageNavigation для обратной навигации;
    • класс BitrixMainUIAdminPageNavigation для навигации в админке;
    • компонент main.pagenavigation с шаблонами .default (основан на round старого компонента), admin (для админки), modern (для гридов).

    Постраничка поддерживает как параметры в GET, так и ЧПУ. Вместо глобальной переменной $NavNum теперь нужно явно указывать идентификатор навигации. Поддерживаются URL следующего вида:

    /page.php?nav-cars=page-5&nav-books=page-2&other=params
    /page.php?nav-cars=page-5-size-20&nav-books=page-2
    /page.php?nav-cars=page-all&nav-books=page-2
    /dir/nav-cars/page-2/size-20/
    /dir/nav-cars/page-all/?other=params
    /dir/nav-cars/page-5/nav-books/page-2/size-10

    где nav-cars и nav-books — идентификаторы двух разных постраничек на одной странице.

    Постраничная навигация в административном разделе

    $nav = new BitrixMainUIAdminPageNavigation("nav-culture");
    
    $cultureList = CultureTable::getList(array(
       'order' => array(strtoupper($by) => $order),
       'count_total' => true,
       'offset' => $nav->getOffset(),
       'limit' => $nav->getLimit(),
    ));
    
    $nav->setRecordCount($cultureList->getCount());
    
    $adminList->setNavigation($nav, Loc::getMessage("PAGES"));
    
    while($culture = $cultureList->fetch())
    {
    }

    'count_total' => true — новый параметр, который заставляет ORM выполнить отдельный запрос COUNT, результат которого оказывается в объекте результата выборки и может быть получен через $cultureList->getCount(). Без этого параметра выполнять getCount() бесполезно и даже вредно (получите исключение).

    $nav->getOffset() возвращает позицию первой записи для текущей страницы навигации.
    $nav->getLimit() возвращает количество записей на странице или 0, если выбрано «все записи».
    $nav->setRecordCount() устанавливает количество записей для навигации.

    Для удобства в административном отделе добавлена функция $adminList->setNavigation(), которая просто подключает компонент main.pagenavigation с шаблоном admin.

    Прямая постраничная навигация в Публичном разделе

    <?
    $filter = array("=IBLOCK_ID"=>6);
    
    $nav = new BitrixMainUIPageNavigation("nav-more-news");
    $nav->allowAllRecords(true)
       ->setPageSize(5)
       ->initFromUri();
    
    $newsList = BitrixIblockElementTable::getList(
       array(
          "filter" => $filter,
          "count_total" => true,
          "offset" => $nav->getOffset(),
          "limit" => $nav->getLimit(),
       )
    );
    
    $nav->setRecordCount($newsList->getCount());
    
    while($news = $newsList->fetch())
    {
    }
    ?>
    
    <?
    $APPLICATION->IncludeComponent(
       "bitrix:main.pagenavigation",
       "",
       array(
          "NAV_OBJECT" => $nav,
          "SEF_MODE" => "Y",
       ),
       false
    );
    ?>

    Добавилась инициализация навигации:

    $nav->allowAllRecords(true) ->setPageSize(5) ->initFromUri();

    В публичном разделе больше настроек типа «разрешить все записи», а это все нужно знать до того, как будет проинициализирован объект. (В административном разделе это уже переопределено классом-наследником.) Кроме того, необязательно получать текущую страницу из URL так как её можно взять где угодно и установить.

    У компонента есть важный параметр "SEF_MODE" => "Y", по которому он генерирует ЧПУ постраничной навигации.

    Обратная постраничная навигация в Публичном разделе

    <?
    $filter = array("=IBLOCK_ID"=>6);
    
    $cnt = BitrixIblockElementTable::getCount($filter);
    
    $nav = new BitrixMainUIReversePageNavigation("nav-news", $cnt);
    $nav->allowAllRecords(true)
       ->setPageSize(5)
       ->initFromUri();
    
    $newsList = BitrixIblockElementTable::getList(
       array(
          "filter" => $filter,
          "offset" => $nav->getOffset(),
          "limit" => $nav->getLimit(),
       )
    );
    
    while($news = $newsList->fetch())
    {
    }
    ?>
    
    <?
    $APPLICATION->IncludeComponent(
       "bitrix:main.pagenavigation",
       "",
       array(
          "NAV_OBJECT" => $nav,
          "SEF_MODE" => "Y",
       ),
       false
    );
    ?>

    Здесь конструируется класс-наследник ReversePageNavigation и обязательно передаётся ему количество записей уже в конструкторе, т.к. обратная навигация математически не может работать без количества записей. Для этого в ORM обновился метод getCount(), теперь он принимает на вход параметр фильтра.

    Постраничная навигация в гриде

    <?
    $filter = array("=IBLOCK_ID"=>6);
    
    $nav = new BitrixMainUIPageNavigation("nav-grid-news");
    $nav->allowAllRecords(true)
       ->setPageSize(5)
       ->initFromUri();
    
    $newsList = BitrixIblockElementTable::getList(
       array(
          "filter" => $filter,
          "count_total" => true,
          "offset" => $nav->getOffset(),
          "limit" => $nav->getLimit(),
       )
    );
    
    $rows = array();
    while($news = $newsList->fetch())
    {
       $cols = array(
          "ID" => $news["ID"],
          "NAME" => $news["NAME"],
       );
    
       $rows[] = array(
          "columns"=>$cols,
       );
    }
    
    $nav->setRecordCount($newsList->getCount());
    ?>
    
    <?
    $APPLICATION->IncludeComponent(
       "bitrix:main.interface.grid",
       "",
       array(
          "GRID_ID"=>"news_grid",
          "HEADERS"=>array(
             array("id"=>"ID", "name"=>"ID", "default"=>true),
             array("id"=>"NAME", "name"=>"Название", "default"=>true),
          ),
          "ROWS"=>$rows,
          "FOOTER"=>array(array("title"=>"Всего", "value"=>$newsList->getCount())),
          "NAV_OBJECT"=>$nav,
          "NAV_PARAMS"=>array(
             "SEF_MODE" => "Y"
          ),
       )
    );
    ?>

    "NAV_PARAMS"=>array( "SEF_MODE" => "Y" ) — новый параметр, который по сути передаёт параметры в компонент навигации.

    Постраничная навигация без COUNT

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

    <?
    $filter = array("=IBLOCK_ID"=>6);
    
    $nav = new BitrixMainUIPageNavigation("nav-less-news");
    $nav->allowAllRecords(true)
       ->setPageSize(5)
       ->initFromUri();
    
    $newsList = BitrixIblockElementTable::getList(
       array(
          "filter" => $filter,
          "offset" => ($offset = $nav->getOffset()),
          "limit" => (($limit = $nav->getLimit()) > 0? $limit + 1 : 0),
       )
    );
    
    $n = 0;
    while($news = $newsList->fetch())
    {
       $n++;
       if($limit > 0 && $n > $limit)
       {
          break;
       }
    }
    
    $nav->setRecordCount($offset + $n);
    ?>
    
    <?
    $APPLICATION->IncludeComponent(
       "bitrix:main.pagenavigation",
       "",
       array(
          "NAV_OBJECT" => $nav,
          "SEF_MODE" => "Y",
          "SHOW_COUNT" => "N",
       ),
       false
    );
    ?>

    Обратите внимание: выставляется «уже известное» количество записей: $nav->setRecordCount($offset + $n);

    Это делается для того, чтобы компонент навигации понял, что есть еще страницы. Но для шаблонов, которые выводят количество записей, мы передаем "SHOW_COUNT" => "N", чтобы они не пытались выводить неверную цифру (она будет верна только на последней странице).

    Внимание! Если осуществляется навигация без COUNT, то имеется в виду, что записей очень много. Поэтому не стоит разрешать все записи методом $nav->allowAllRecords(true).

    Файл с примерами.

    Интеграция ORM в информационных блоках

    С версии 19.0.0 модуля iblock добавлена поддержка ORM при работе с элементами инфоблоков.

    Обратная совместимость

    События старого ядра модуля инфоблоков не поддерживаются. Штатные возможности других модулей, опирающиеся на вызовы этих событий, недоступны.

    В настоящий момент не реализована поддержка функциональных блоков:

      Для API работы с элементами (добавление, изменение, удаление)

    • ресайз изображений PREVIEW_PICTURE, DETAIL_PICTURE;
    • обновление фасетного индекса инфоблока (при условии использования);
    • обновление seo-параметров элемента;
    • сброс тегированного кеша;
    • установка прав доступа;
    • поддержка документооборота;
    • проверка дисковой квоты для файловых свойств;
    • пересчет доступности товаров с SKU;
    • пересчет цен для сортировки товаров с SKU;
    • индексация модулем поиска;
    • логирование операций с элементами.

      Для API работы с разделами (добавление, изменение, удаление).

    • автопересчет значений полей LEFT_MARGIN, RIGHT_MARGIN, GLOBAL_ACTIVE, DEPTH_LEVEL;
    • ресайз изображений PREVIEW_PICTURE, DETAIL_PICTURE;
    • обновление фасетного индекса инфоблока (при условии использования);
    • обновление seo-параметров раздела и дочерних сущностей (подразделов и элементов);
    • сброс тегированного кеша;
    • установка прав доступа на раздел и дочерние сущности;
    • проверка дисковой квоты для файловых полей;
    • индексация модулем поиска;
    • привязка свойств к разделам;
    • логирование операций с разделами.

    Внимание! Перечисленный функционал необходимо реализовывать самостоятельно.

    Список ссылок по теме:

    • [ds]REST ORM API для инфоблоков[/ds][di]
      REST API для инфоблоков доступен с версии 20.5.0 модуля Информационные блоки.

      Для доступа к данным инфоблоков через REST за основу взята интеграция с ORM. А именно концепция, когда один инфоблок — это одна самостоятельная сущность ORM, а элемент инфоблока — это запись (объект) сущности.

      Подробнее…[/di]

    Концепция и архитектура

    Каждый инфоблок является самостоятельным типом данных со своим собственным набором свойств. В ORM он представляется отдельной сущностью:

    Имя классов сущности включает в себя значение нового поля из настроек инфоблока Символьный код API. За счет этого кода обеспечивается уникальность классов вне зависимости от ID и среды окружения.

    Важно! Чтобы начать использовать ORM для конкретного инфоблока, ему необходимо задать через административный интерфейс Символьный код API (поле API_CODE). Это строка от 1 до 50 символов, начинающаяся с буквы и состоящая из латинских букв и цифр.

    Свойства — это не просто скалярные значения, а отношения с отдельными мини-сущностями с двумя ключевыми полями: VALUE и DESCRIPTION. Единичные свойства представляются в элементе инфоблока полем Reference, множественные — OneToMany:

    В сущности некоторых типов свойств могут быть добавлены дополнительные поля, например ссылка на привязанный элемент инфоблока:

    Подробнее о базовых типах свойств

    Ориентироваться среди большого количества свойств поможет [ds]механизм аннотаций[/ds][di]Большая часть методов Объекта и Коллекции — виртуальные, обрабатываются через magic вызов __call. В то же время они сделаны для интуитивно понятных и говорящих именованных методов, и без автокомплита в IDE их ценность резко снижается.
    Чтобы IDE все же знала об их существовании и помогала ориентироваться в большом количестве классов и методов, мы сделали для нее специальный служебный файл с аннотациями всех сущностей.

    Подробнее …[/di]. При индексации модуля iblock все инфоблоки будут описаны в виде сущностей ORM. Для получения подсказок в коде необходимо явно обозначить класс инфоблока:

    // подключение модуля инфоблоков
    BitrixMainLoader::includeModule('iblock');
    
    // вводные данные
    $iblockId = 32;
    $iblockElementId = 678;
    
    // объект инфоблока
    $iblock = BitrixIblockIblock::wakeUp($iblockId);
    
    // объект элемента
    /** @var BitrixIblockElementsEO_ElementLink $element */
    $element = $iblock->getEntityDataClass()::getByPrimary($iblockElementId)
        ->fetchObject();
    
    // получение свойства SOME_STRING
    $element->getSomeString();
    

    Автолоадинг классов автоматически обработает вызов getEntityDataClass(), т.е. вам не придется предварительно компилировать сущность инфоблока.

    Если вы хотите использовать подсказки IDE по типизации элемента инфоблока, необходимо явно задать его класс аннотацией вида:

    • для элемента;
      /** @var BitrixIblockElementsEO_ElementLink $element */
      
    • для коллекции,
      /** @var BitrixIblockElementsEO_ElementLink_Collection $element */
      

    где Link в имени класса — символьный код API инфоблока.

    Внимание! Символьный код свойства не должен совпадать с названием поля элемента инфоблока. В противном случае будет невозможно работать со свойством в объектном ORM по имени (символьному коду).

    Чтение и запись

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

    $elements = $iblock->getEntityDataClass()::getList([
        'select' => ['ID', 'SOME_FIELD', 'ANOTHER_FIELD.ELEMENT']
    ])->fetchCollection();
    
    foreach ($elements as $element)
    {
        echo $element->getSomeField()->getValue();
        echo $element->getAnotherField()->getElement()->getTitle();
    }
    

    Поддерживаются стандартные механики отношений ORM.

    При фильтрации следует помнить о структуре свойств. Поле свойства является ссылкой (Reference или OneToMany) на целую сущность, поэтому указание в фильтре имени свойства ни к чему не приведет:

    // неправильный пример
    $element = $iblock->getEntityDataClass()::query()
        ->where('SOME_FIELD', 'some value')
        ->fetchObject();
    

    Значение свойства хранится в поле сущности, и это поле всегда называется VALUE. Поэтому корректно указывать именно его в фильтре:

    // правильный пример
    $element = $iblock->getEntityDataClass()::query()
        ->where('SOME_FIELD.VALUE', 'some value')
        ->fetchObject();
    

    Перебор по классике в while

    Для создания нового объекта можно использовать как конструктор соответствующего класса,

    $newElement = new BitrixIblockElementsEO_ElementLink;
    

    так и фабрику сущности.

    $newElement = $iblock->getEntityDataClass()::createObject();
    

    Изменение значений и описаний свойств происходит непосредственно через объект свойства:

    // установка строкового значения
    $element->getSomeString()->setValue('new value');
    
    // установка описания
    $element->getSomeString()->setDescription('new descr');
    
    // установка привязки к элементу
    $element->getSomeElement()->setElement($anotherElement);
    

    Кроме этого, можно поставить значение напрямую в поле свойства:

    $element->setSomeString('new value');
    

    А также можно воспользоваться псевдо объектом значения свойства BitrixIblockORMPropertyValue:

    use BitrixIblockORMPropertyValue;
    
    // только значение
    $value = new PropertyValue('new value');
    
    // значение и описание
    $value = new PropertyValue('new value', 'new descr');
    
    // установка значения/описания
    $element->setSomeString($value);  
    

    Установка значений для множественных свойств работает аналогично с той лишь разницей, что речь идет не о Reference, а об отношении OneToMany:

    use BitrixIblockORMPropertyValue;
    
    foreach ($element->getOtherField() as $value)
    {
        $value->setValue('new value');
        $value->setDescription('new descr');
    }
    
    $element->addToOtherField(new PropertyValue('new value'));
    $element->addToOtherField(new PropertyValue('new value', 'new descr'));
    

    Объект элемента сохраняется так же, как и любой другой объект ORM:

    $element->save();
    

    Несмотря на то, что значения свойств фактически хранятся в разных таблицах в виде отношений с объектом, при сохранении внутри объекта все будет разложено по своим местам.

    Удалить элемент можно через метод объекта delete:

    $element->delete();
    

    При удалении так же, как и при сохранении, значения свойств обрабатываются автоматически. Удалятся и привязки к секциям.

    События и кастомные типы свойств

    События

    Для подписи на события сущности инфоблоков можно использовать штатные механизмы ORM:

    use BitrixMainORMDataDataManager;
    		
    // ID инфоблока
    $iblockId = 32;
    
    // объект инфоблока
    $iblock = BitrixIblockIblock::wakeUp($iblockId);
    
    // диспетчер событий
    $em = BitrixMainORMEventManager::getInstance();
    
    $em->registerEventHandler(
        $iblock->getEntityDataClass(),
        DataManager::EVENT_ON_BEFORE_ADD,
        'mymodule',
        'MyClass',
        'method'
    );
    

    Внимание! Поддержка действующих событий инфоблоков в данный момент не реализована.

    Кастомные типы свойств

    Чтобы добавить свои поля в сущность свойства, при описании свойства нужно задать отдельный коллбэк GetORMFields:

    public static function GetUserTypeDescription()
    {
        return [
            ...
            "GetORMFields" => array(__CLASS__, "GetORMFields"),
        ];
    }
    
    /**
     * @param BitrixMainORMEntity $valueEntity
     * @param BitrixIblockProperty $property
     */
    public static function GetORMFields($valueEntity, $property)
    {
        $valueEntity->addField(
            ...
        );
    }
    

    Наследование

    ORM API инфоблоков позволяет [dw]наследовать[/dw][di]
    Возможность наследования от ORM сущностей элементов появилась с версии 21.500.0 модуля iblock.
    [/di] сущность конкретного инфоблока, а также дополнить или переопределить его поведение. Предусмотрено наследование классов самой сущности и её объекта. Для наследования сущности достаточно указать Table класс инфоблока:

    class MyExtTable extends BitrixIblockElementsElement{API_CODE}Table
    {
    }
    

    В имени родительского класса задействован API CODE из настроек инфоблока. Описание класса объекта задается по [ds]общим правилам ORM[/ds][di]
    Все объекты сущностей являются наследниками класса BitrixMainORMObjectifyEntityObject, при этом у каждой сущности — свой собственный класс для объектов. По умолчанию, такой класс создается автоматически, на лету.

    Подробнее…[/di], включая конфигурирование Table класса:

    class MyExtTable extends BitrixIblockElementsElement{API_CODE}Table
    {
        public static function getObjectClass()
        {
            return MyExt::class;
        }
    }
    
    class MyExt extends EO_MyExt
    {
    }
    

    Работа с компонентами

    Цитатник веб-разработчиков.

    Антон Долганин: Строжайше запрещено писать любую PHP-логику на обычной странице сайта. Любой php-код должен инкапсулироваться в компоненты. Максимум, что позволяется — использовать подключение скриптов с помощью IncludeFile, но это если разработчик полностью отдает себе отчет в том, что этого делать нельзя и это будет исправлено в ближайшее время, или того действительно требует логика проекта (единичные случаи).

    Компонент – основной способ вывода информации в Bitrix Framework. Соответственно именно работа с ним дает максимальные возможности по изменению условий вывода данных и изменению (добавлению) функционала системы.

    Рекомендуемое соотношение задач и способов их решений:

    • Для решения задач изменения формы вывода данных модифицируйте шаблон компонента.
    • Для изменения и дополнения кешируемых данных, выводимых компонентом, используйте возможности файла result_modifier.php.
    • Для реализации логики, отрабатывающей при каждом вызове компонента независимо от кеширования, используйте возможности файла component_epilog.php.
    • Для дополнения и неявного изменения (без вмешательства в код) логики работы компонента можно использовать технологию Событий.
    • Для дополнения логики работы компонента копируйте компонент в свое пространство имен и изменяйте его.
    • Для создания новой логики и новых возможностей создавайте компонент заново.

    Достаточно часто задачу приходится решать комбинацией методов. То есть, например, редактировать шаблон и добавлять код в result_modifier.php.

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

    Внимание! Осуществляя любые действия по работе с компонентами надо также учитывать кеширование. Внедрение тяжелых кодов в component_epilog.php под кеширование не попадает. И бывают случаи, когда все же правильнее кастомизировать компонент, что дает выигрыш в производительности (особенно, если какой-то тяжелый код используется на главной или наиболее часто посещаемой странице).

    Переменные в компоненте 2.0

    Переменная, описание Использование в файлах *.php
    component template result_modifier component_epilog class
    arParams
    Параметры, чтение/изменение, затрагивает одноименный член класса компонента.
    да да да,
    изменения отразятся на arParams в файле template.php
    да [dw]да[/dw][di]Доступно как $this->arParams[/di]
    arResult
    Результат, чтение/изменение, затрагивает одноименный член класса компонента
    да [dw]да[/dw][di]затрагивает ссылку на поле компонента[/di] да да,
    изменение не затрагивает одноименный член класса компонента
    [dw]да[/dw][di]Доступен как $this->arResult[/di]
    APPLICATION
    доступна, можно не объявлять как global
    да да да да
    USER
    доступна, можно не объявлять как global
    да да да да
    DB
    доступна, можно не объявлять как global
    да да да да
    this
    ссылка на текущий:
    [dw]компонент[/dw][di]Объект класса CBitrixComponent, можно использовать все методы этого класса.[/di] [dw]шаблон[/dw][di]Объект, описывающий шаблон, тип CBitrixComponentTemplate[/di] [dw]шаблон[/dw][di]Объект, описывающий шаблон, тип CBitrixComponentTemplate[/di] [dw]компонент[/dw][di]Объект класса CBitrixComponent, можно использовать все методы этого класса.[/di] [dw]Объект
    компонента[/dw][di] Наследуется от CBitrixComponent[/di]
    componentPath
    путь к вызванному компоненту от DOCUMENT_ROOT
    да да да [dw]да[/dw][di]Доступен через $this->getPath()[/di]
    componentName
    имя вызванного компонента
    да [dw]да[/dw][di]Доступен через $this->getName()[/di]
    componentTemplate
    шаблон вызванного компонента
    да [dw]да[/dw][di]Доступен через $this->getTemplate()[/di]
    parentComponentPath
    если компонент вызван в составе другого компонента, идут отсылки на родительский компонент
    да [dw]да[/dw][di]Получется через
    $this->getParent()->getPath()[/di]
    parentComponentName
    если компонент вызван в составе другого компонента, идут отсылки на родительский компонент
    да [dw]да[/dw][di]Получается через
    $this->getParent()->getName()[/di]
    parentComponentTemplate
    если компонент вызван в составе другого компонента, идут отсылки на родительский компонент
    да [dw]да[/dw][di]Получается через
    $this->getParent()->getTemplate()[/di]
    templateName
    имя шаблона компонента
    да да
    templateFile
    путь к файлу шаблона от DOCUMENT_ROOT
    да да
    templateFolder
    путь к папке с шаблоном от DOCUMENT_ROOT
    да да
    templateData
    массив для записи, передающий данные из template.php в файл component_epilog.php, данные кешируются, т.к. файл component_epilog.php исполняется на каждом хите.
    да да
    component
    ссылка на:
    да,
    текущий [dw]компонент[/dw][di]Объект класса CBitrixComponent, можно использовать все методы этого класса.[/di]
    да,
    текущий [dw]шаблон[/dw][di]Объект, описывающий шаблон, тип CBitrixComponentTemplate[/di]
    да,
    $this
    да,
    $this

    Примечание: Компонент получает все параметры вызова следующим образом:

    1. В ключах, начинающихся с ~, данные содержатся в исходном виде (т.е. без всякой обработки).
      Если это комплексный компонент или в шаблоне компонента вызывается другой и часть параметров передается ему, то необходимо передавать значение ключей с ~.
    2. В ключах без ~ данные приведены к безопасному виду с помощью метода htmlspecialcharsEx. Если ключ содержит массив, то будут обработаны строковые ключи массива (тоже с помощью htmlspecialcharsEx).

    Классы компонентов

    Поддержка классов

    Зайцев Артемий:
    Сейчас думаю, что большие компоненты — это зло, даже с классами. Выношу классы в отдельные файлы и автолоадом подключаю.
    Но бывает так, что нужна в компоненте пара функций. И тогда такой класс будет очень полезным. А еще статические переменные в классе.

    Максим Смирнов:

    Проектировалось именно с прицелом на это.
    Тяжелую бизнес логику лучше держать в такой сущности как «модуль».

    Поддержка классов компонентов реализована в виде файла /component_name/class.php. Class.php — это зарезервированное имя файла, и этот файл автоматически подключается при вызове:

    $APPLICATION->IncludeComponent()

    При этом происходит вызов final метода initComponent в котором и подключается class.php (если он есть) и из него берется самый последний класс наследник от CBitrixComponent.

    Действия вида:

    class CDemoTest extends CBitrixComponent{}
    class CDemoTestDecorator1 extends CDemoTest {}
    class CDemoTestDecorator2 extends CDemoTest {}

    не будут иметь успеха. В итоге будет использоваться CDemoTestDecorator2.

    Учтите что при изменении базового класса компонента нужно будет учитывать поведение всех его потомков (других компонентов).

      Пример

    Рассмотрим простейший компонент возводящий параметр в квадрат.

    Файл /bitrix/components/demo/sqr/component.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    $arParams["X"] = intval($arParams["X"]);
    if($this->startResultCache()) //startResultCache используется не для кеширования html, а для кеширования arResult
    {
        $arResult["Y"] = $arParams["X"] * $arParams["X"];
    }
    $this->includeComponentTemplate();
    ?>

    Файл /bitrix/components/demo/sqr/templates/.default/template.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
    <div class="equation">
    <?echo $arParams["X"];?> в квадрате равно <?echo $arResult["Y"];?>
    </div>

    В реальных компонентах вместо операции умножения может быть три десятка строк и таких операций может быть 5-6. В результате файл component.php превращается в тяжело понимаемую «вещь в себе».

    Выделяем логику компонента в класс.

    Файл /bitrix/components/demo/sqr/class.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    class CDemoSqr extends CBitrixComponent
    {
        //Родительский метод проходит по всем параметрам переданным в $APPLICATION->IncludeComponent
        //и применяет к ним функцию htmlspecialcharsex. В данном случае такая обработка избыточна.
        //Переопределяем.
        public function onPrepareComponentParams($arParams)
        {
            $result = array(
                "CACHE_TYPE" => $arParams["CACHE_TYPE"],
                "CACHE_TIME" => isset($arParams["CACHE_TIME"]) ?$arParams["CACHE_TIME"]: 36000000,
                "X" => intval($arParams["X"]),
            );
            return $result;
        }
    
        public function sqr($x)
        {
            return $x * $x;
        }
    }?>

    Файл /bitrix/components/demo/sqr/component.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    if($this->startResultCache())//startResultCache используется не для кеширования html, а для кеширования arResult
    {
        //$this - экземпляр CDemoSqr
        $arResult["Y"] = $this->sqr($arParams["X"]);
    }
    $this->includeComponentTemplate();
    ?>

    Теперь код в файле component.php стал управляемым.

      Наследование

    Например:

    Файл /bitrix/components/demo/double_sqr/class.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    //Необходимо для корректного поиска класса CDemoSqr
    CBitrixComponent::includeComponentClass("demo:sqr");
    //Наследник расширяющий функциональность:
    class CDemoDoubleSqr extends CDemoSqr
    {
        public function sqr($x)
        {
            return parent::sqr($x)*2;
        }
    }?>

    Файл /bitrix/components/demo/double_sqr/component.php идентичен файлу /bitrix/components/demo/sqr/component.php, можно просто скопировать содержание.

    Файл /bitrix/components/demo/double_sqr/templates/.default/template.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
    <div class="equation">
    <?echo $arParams["X"];?> в квадрате умноженное на два равно <?echo $arResult["Y"];?>
    </div>

      Компонент без component.php

    Можно создать и компонент без файла component.php

    Для этого достаточно перекрыть метод executeComponent. Например:

    class CDemoSqr extends CBitrixComponent
    {
    ...
        public function executeComponent()
        {
            if($this->startResultCache())  
            {
                $this->arResult["Y"] = $this->sqr($this->arParams["X"]);
                $this->includeComponentTemplate();
            }
            return $this->arResult["Y"];
        }
    };

    Теперь из обоих компонентов можно удалить файлы component.php.

    Файл result_modifier.php

    Файл result_modifier.php — инструмент для модификации данных работы компонента произвольным образом. Создается разработчиком самостоятельно. Работает при выключенном кешировании.

    Если в папке шаблона есть файл result_modifier.php, то он вызывается перед подключением шаблона.

    Схема работы компонента с файлом result_modifier.php:

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

    Примечание: файл result_modifier.php запускается только перед подключением шаблона. При использовании кеширования шаблон не подключается. И через него не получится устанавливать динамические свойства типа: title, keywords, description.

    В файле доступны языковые фразы шаблона компонента и следующие переменные:

    • $arParams — параметры, чтение, изменение. Не затрагивает одноименный член компонента, но изменения тут влияют на $arParams в файле template.php.
    • $arResult — результат, чтение/изменение. Затрагивает одноименный член класса компонента.
    • $APPLICATION, $USER, $DB — объявлять их как global избыточно, они уже доступны по-умолчанию.
    • $this — ссылка на текущий шаблон (объект, описывающий шаблон, тип CBitrixComponentTemplate)

    Важно! Наиболее правильный вариант кастомизации компонента — скопировать его в отдельное пространство имен и работать уже с копией компонента. При этом нужно учитывать последствия:

    • Увеличивается общее количество компонентов, соответственно растет и количество выделяемых на их поддержку ресурсов.
    • Сложность в освоении новым разработчиком: сначала ему нужно будет найти в чем отличие от уже существующего стандартного компонента.

    Примеры решения задач

    Несколько примеров решения задач с помощью файла result_modifier.

    Ограничение срока показа новых сотрудников

    Задача: показывать новых сотрудников на корпоративном портале только небольшой период времени, допустим, месяц с момента их приема на работу, а не до тех пор, пока не будет принят кто-то еще.

    Чтобы решить задачу можно слегка изменить результаты работы компонента intranet.structure.informer.new, на основе которого работает гаджет Новые сотрудники.

    • Копируем шаблон .default компонента в шаблон портала. В файл result_modifier.php добавляем код:
      //show only users hired in the last N days
      $period_days = 30;
      foreach ($arResult['ENTRIES'] as $key => $arEntry) {
        $user_reg_timestamp = MakeTimeStamp($arEntry["DATE_ACTIVE_FROM"] , "MM/DD/YYYY HH:MI:SS");
        $from = strtotime("-".$period_days." days");
        if ($user_reg_timestamp < $from) {
          unset($arResult['ENTRIES'][$key]);
        } 
      }

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

    • Нужно предусмотреть ситуацию, если новых сотрудников за данный период нет. В таком случае можно выводить сообщение (добавляется в код шаблона):
      <?
      if (count($arResult['ENTRIES']) == 0) {
        echo GetMessage('INTR_NO_ENTRIES');
      }
      ?>

    Само текстовое сообщение будет находиться в файле шаблона /lang/ru/template.php

    $MESS['INTR_NO_ENTRIES'] = "Новых сотрудников в последнее время не было";

    После изменений гаджет Новые сотрудники сможет показывать новых сотрудников в соответствии с выбранными параметрами.

    Показ описания файлов

    Примечание: добавляемый в примере функционал уже есть в продукте с версии 10.5. Описание оставлено для примера работы с файлом result_modifier.php.

    На тех страницах корпоративного портала, где используется комплексный компонент Библиотека (bitrix:webdav), можно настроить список полей для показа. Но на вкладке Файлы на персональной странице или на вкладке Файлы в рабочих группах штатно список полей для показа настроить нельзя.

    Если надо показывать описание файла на персональной странице или в рабочих группах, то сделайте следующее. Скопируйте компонент в собственное пространство имён. Далее в каталог /local/components/_ваше_пространство_имен_/webdav.section.list/templates/.default/ добавьте файл result_modifier.php со следующим кодом:

    <?
    if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED!==true)die();
    $arParams['COLUMNS'][] = 'PREVIEW_TEXT';
    ?>

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

    Рекламный баннер внутри текста

    Для размещения рекламного баннера внутри текста новости используйте разделитель #BANNER_BOTTOM#, где BOTTOM — тип баннера, который будет показан.

    В режиме визуального редактора код может выглядеть следующим образом:

    Используйте result_modifier.php, который следует поместить рядом с соответствующим шаблоном показа новости или статьи. Шаблон компонента предварительно скопирован в текущий шаблон сайта:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    $arResult["DETAIL_TEXT"] = preg_replace(
       "/#BANNER_([A-Za-z-0-9]+)#/e",
       'CAdvBanner::GetHTML(CAdvBanner::GetRandom("1"))',
       $arResult["DETAIL_TEXT"]
    );
    ?>
    

    В итоге на сайте это будет выглядеть следующим образом:

    Ресайз изображения в списочном компоненте

    foreach ($arResult['ITEMS'] as $key => $item) {
    
        if (!empty($item['PREVIEW_PICTURE']['SRC'])) {
    
            $resizeImg = CFile::ResizeImageGet(
                $item['PREVIEW_PICTURE'],
                [
                    'width'  => 300,
                    'height' => 300,
                ]
            );
    
            if (!empty($resizeImg['src'])) {
                $resizeImg = array_change_key_case($resizeImg, CASE_UPPER);
                $arResult['ITEMS'][$key]['PREVIEW_PICTURE'] = $resizeImg;
            }
        }
    }

    Еще примеры:

    • Иерархичное меню и result_modifier

    Пример. Выборка из Информационного блока

    Типичная ошибка

    Вот как выглядит типичная попытка начинающего разработчика осуществить выборку из Информационного блока в явном виде в теле страницы:

    <?
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
    $APPLICATION->SetTitle("Прямой вызов API Битрикса на странице");
    ?>
    <h1><?=$APPLICATION->ShowTitle()?></h1>
    <div class="box">
    <?
    $IBLOCK_ID = intval($arParams['IBLOCK_ID']);
    if ($IBLOCK_ID <=0) $IBLOCK_ID = 1;
     
    if(!CModule::IncludeModule("iblock"))
    	die('iblock module is not included!');
    //делаем выборку из Инфоблока
    $arSort = Array("SORT"=>"ASC", "NAME"=>"ASC");
    $arFilter = Array("IBLOCK_ID"=>$IBLOCK_ID,"ACTIVE"=>"Y");
    $obIBlockResult = CIBlockElement::GetList($arSort, $arFilter);
     
    //выводим результат выборки в виде списка	
    echo "<ol>";	
    while($arFields = $obIBlockResult->GetNext())
    {
    	echo "<li>{$arFields["NAME"]}</li>";
    }
    echo "</ol>";
    ?>
    </div>
    <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>
    

    Чем же плохо такое решение?

    Основных проблем несколько:

    • отсутствие кеширования на такой странице (в отличие от компонента);
    • отсутствие внятной и явной передачи параметров (в отличие от компонента);
    • риск открытия страницы визуальным редактором с заменой спецсимволов, приводящей к неработающему коду на странице.

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

    Переход на использование компонента

    Предлагается использовать result_modifier.php, который находится в папке шаблона компонента и изначально предназначен для модификации массива $arResult перед его выводом в файле шаблона template.php.

    Рассмотрим классическую схему работы компонента с result_modifier.php в шаблоне, представленную на рисунке:

    В данном случае, в result_modifier.php будет помещен непосредственно код, который до этого планировалось разместить прямо на странице.

    Общая схема решения выглядит следующим образом:

    1. На странице вместо кода с вызовами API помещается вызов пустого компонента system.empty со специальным шаблоном (например, get_list)
    2. В этом шаблоне компонента создается файл result_modifier.php, в который помещается необходимый код с вызовами API. Файл template.php оставляется пустым. (Но в дальнейшем возможно его использование стандартным образом – для этого в result_modifier.php нужно будет сформировать массив $arResult, который и будет выводиться в шаблоне.)
    3. В результате, на странице остается только вызов компонента, параметры которому можно передавать через массив параметров функции IncludeComponent()

    Примечание: компонент system.empty отсутствует в штатной поставке. Его нужно создавать самостоятельно.

    Пример кода страницы:

    <?
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
    $APPLICATION->SetTitle("Компонентный подход: вызов API Битрикса в шаблоне компонента");
    ?>
    <h1><?=$APPLICATION->ShowTitle()?></h1>
    <div class="box">
    <?
    //задаем Инфоблок
    $IBLOCK_ID = 1;
     
    //подключаем пустой компонент, где логика лежит в шаблоне get_list в файле result_modifier.php
    $APPLICATION->IncludeComponent(
    	"bitrixonrails:system.empty",
    	"get_list",
    	Array(
    		"IBLOCK_ID" => $IBLOCK_ID,
    		"CACHE_TYPE" => "A",
    		"CACHE_TIME" => "3600"
    	),
    false
    );?>
     
    </div>
    <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>
    

    Перенесенный в result_modifier.php исходный код:

    <?
    //получаем идентификатор Инфоблока из параметров компонента
    $IBLOCK_ID = intval($arParams['IBLOCK_ID']);
    if ($IBLOCK_ID <=0) $IBLOCK_ID = 1;
     
    if(!CModule::IncludeModule("iblock"))
    	die('iblock module is not included!');
    //делаем выборку из Инфоблока
    $arSort = Array("SORT"=>"ASC", "NAME"=>"ASC");
    $arFilter = Array("IBLOCK_ID"=>$IBLOCK_ID,"ACTIVE"=>"Y");
    $obIBlockResult = CIBlockElement::GetList($arSort, $arFilter);
     
    //выводим результат выборки в виде списка	
    echo "<ol>";	
    while($arFields = $obIBlockResult->GetNext())
    {
    	echo "<li>{$arFields["NAME"]}</li>";
    }
    echo "</ol>";
    ?>
    

    Если необходимы и другие вставки кода, то просто создается новый шаблон (можно просто копированием шаблона get_list) и в его result_modifier.php вставляется нужный код. В результате получается множество шаблонов («квази-компонентов»), каждый из которых соответствует исходной странице.

    Модификация шаблона или создание result_modifier?

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

    Как сделать возможность вставки видео при публикации новостей на сайте? На первый взгляд это может показаться сложно. Но фактически все легко реализуемо. Идея состоит в том, чтобы для прикреплённого к новости файла подключать компонент Медиа проигрыватель (bitrix:player). Для отображения новости будет использоваться компонент Новость детально (bitrix:news.detail).

    Какой бы способ вы не использовали, необходимо создать свойство типа Файл в инфоблоке новостей.

    Решение с редактированием шаблона

    • Скопируйте шаблон компонента news.detail в шаблон сайта. Сам компонент менять не придётся.
    • Создайте новую страницу в визуальном редакторе и разместите на ней компонент Медиа проигрыватель (bitrix:player). Укажите базовые настройки (путь к видео файлу пока не заполняйте). Скопируйте полученный код вызова компонента. Например такой:
      <?$APPLICATION->IncludeComponent(
         "bitrix:player",
         "",
         Array(
            "PLAYER_TYPE" => "auto", 
            "USE_PLAYLIST" => "N", 
            "PATH" => "",
            "WIDTH" => "400", 
            "HEIGHT" => "300", 
            "FULLSCREEN" => "Y", 
            "SKIN_PATH" => "/bitrix/components/bitrix/player/mediaplayer/skins", 
            "SKIN" => "bitrix.swf", 
            "CONTROLBAR" => "bottom", 
            "WMODE" => "transparent", 
            "HIDE_MENU" => "N", 
            "SHOW_CONTROLS" => "Y", 
            "SHOW_STOP" => "N", 
            "SHOW_DIGITS" => "Y", 
            "CONTROLS_BGCOLOR" => "FFFFFF", 
            "CONTROLS_COLOR" => "000000", 
            "CONTROLS_OVER_COLOR" => "000000", 
            "SCREEN_COLOR" => "000000", 
            "AUTOSTART" => "N", 
            "REPEAT" => "N", 
            "VOLUME" => "90", 
            "DISPLAY_CLICK" => "play", 
            "MUTE" => "N", 
            "HIGH_QUALITY" => "Y", 
            "ADVANCED_MODE_SETTINGS" => "N", 
            "BUFFER_LENGTH" => "10", 
            "DOWNLOAD_LINK_TARGET" => "_self" 
         )
      );?>
    • В шаблоне компонента вместо свойства movie настройте подключение медиаплеера. Найдите строки вывода свойств:
      30         <?foreach($arResult["DISPLAY_PROPERTIES"] as $pid=>$arProperty):?>
       31
       32                 <?=$arProperty["NAME"]?>: 
       33                 <?if(is_array($arProperty["DISPLAY_VALUE"])):?>
       34                         <?=implode(" / ", $arProperty["DISPLAY_VALUE"]);?>
       35                 <?else:?>
       36                         <?=$arProperty["DISPLAY_VALUE"];?>
       37                 <?endif?>
       38                 <br />
       39         <?endforeach;?>
    • Вставьте проверку и замену, получается:
      <?foreach($arResult["DISPLAY_PROPERTIES"] as $pid=>$arProperty):?>
      <?if ($arProperty["CODE"]=='movie' && $arProperty["DISPLAY_VALUE"]) {?>
      
      <?$APPLICATION->IncludeComponent(
         "bitrix:player",
         "",
         Array(
            "PLAYER_TYPE" => "auto", 
            "USE_PLAYLIST" => "N", 
            "PATH" => CFile::GetPath($arProperty["VALUE"]),
            "WIDTH" => "400", 
            "HEIGHT" => "300", 
            "FULLSCREEN" => "Y", 
            "SKIN_PATH" => "/bitrix/components/bitrix/player/mediaplayer/skins", 
            "SKIN" => "bitrix.swf", 
            "CONTROLBAR" => "bottom", 
            "WMODE" => "transparent", 
            "HIDE_MENU" => "N", 
            "SHOW_CONTROLS" => "Y", 
            "SHOW_STOP" => "N", 
            "SHOW_DIGITS" => "Y", 
            "CONTROLS_BGCOLOR" => "FFFFFF", 
            "CONTROLS_COLOR" => "000000", 
            "CONTROLS_OVER_COLOR" => "000000", 
            "SCREEN_COLOR" => "000000", 
            "AUTOSTART" => "N", 
            "REPEAT" => "N", 
            "VOLUME" => "90", 
            "DISPLAY_CLICK" => "play", 
            "MUTE" => "N", 
            "HIGH_QUALITY" => "Y", 
            "ADVANCED_MODE_SETTINGS" => "N", 
            "BUFFER_LENGTH" => "10", 
            "DOWNLOAD_LINK_TARGET" => "_self" 
         ),
         $component   
      );?> 
      <? } else {?>
            <?=$arProperty["NAME"]?>: 
            <?if(is_array($arProperty["DISPLAY_VALUE"])):?>
               <?=implode(" / ", $arProperty["DISPLAY_VALUE"]);?>
            <?else:?>
               <?=$arProperty["DISPLAY_VALUE"];?>
            <?endif?>
      <?}?>
            <br />
         <?endforeach;?>

    Примечание: Здесь следует обратить внимание на следующие моменты:

    • Для получения пути к файлу из ID используется системный вызов CFile::GetPath.
    • При подключении компонентов указан четвёртый параметр $component для того чтобы из публичной части случайно не изменить его параметры (см. класс CMain::IncludeComponent).

    Решение с помощью result_modifier.php

    Если вы хотите, чтобы решение не было похоже на «костыли», необходимо вынести замену свойства в result_modifier.php. Тогда шаблон компонента будет стандартный.

    • Создайте файл result_modifier.php с кодом:
      <?
      // передадим значение свойства по ссылке:
      $arProperty = &$arResult['DISPLAY_PROPERTIES'][$arParams['PROPERTY_VIDEO']];
      
      if ($arProperty['DISPLAY_VALUE']) // проверим, установлено ли свойство
      {
         global $APPLICATION;
         ob_start(); // включим буферизацию чтобы отловить вывод компонента
         $APPLICATION->IncludeComponent(
            "bitrix:player",
            "",
            Array(
               "PLAYER_TYPE" => "auto", 
               "USE_PLAYLIST" => "N", 
               "PATH" => CFile::GetPath($arProperty["VALUE"]),
               "WIDTH" => "400", 
               "HEIGHT" => "300", 
               "FULLSCREEN" => "Y", 
               "SKIN_PATH" => "/bitrix/components/bitrix/player/mediaplayer/skins", 
               "SKIN" => "bitrix.swf", 
               "CONTROLBAR" => "bottom", 
               "WMODE" => "transparent", 
               "HIDE_MENU" => "N", 
               "SHOW_CONTROLS" => "Y", 
               "SHOW_STOP" => "N", 
               "SHOW_DIGITS" => "Y", 
               "CONTROLS_BGCOLOR" => "FFFFFF", 
               "CONTROLS_COLOR" => "000000", 
               "CONTROLS_OVER_COLOR" => "000000", 
               "SCREEN_COLOR" => "000000", 
               "AUTOSTART" => "N", 
               "REPEAT" => "N", 
               "VOLUME" => "90", 
               "DISPLAY_CLICK" => "play", 
               "MUTE" => "N", 
               "HIGH_QUALITY" => "Y", 
               "ADVANCED_MODE_SETTINGS" => "N", 
               "BUFFER_LENGTH" => "10", 
               "DOWNLOAD_LINK_TARGET" => "_self" 
            )
         ); 
         $arProperty['DISPLAY_VALUE'] = ob_get_contents(); // подменим $arResult
         ob_clean(); // очистим наш буфер чтобы плеер не появился дважды
         ob_end_clean(); // закроем буфер
      }
      ?>

      Символьный код свойства можно сделать параметром компонента, чтобы не привязываться жёстко к конкретному инфоблоку. Для этого нужно доработать файл .parameters.php компонента Новость детально, расположенный в скопированном шаблоне компонента.

    • Измените код файла .parameters.php:
      <?
      if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
      $arProps = array(); 
      $rs=CIBlockProperty::GetList(array(),array("IBLOCK_ID"=>$arCurrentValues['IBLOCK_ID'],"ACTIVE"=>"Y"));
      while($f = $rs->Fetch())
         $arProps[$f['CODE']] = $f['NAME'];
      
      $arTemplateParameters = array(
         "DISPLAY_DATE" => Array(
            "NAME" => GetMessage("T_IBLOCK_DESC_NEWS_DATE"),
            "TYPE" => "CHECKBOX",
            "DEFAULT" => "Y",
         ),
         "DISPLAY_NAME" => Array(
            "NAME" => GetMessage("T_IBLOCK_DESC_NEWS_NAME"),
            "TYPE" => "CHECKBOX",
            "DEFAULT" => "Y",
         ),
         "DISPLAY_PICTURE" => Array(
            "NAME" => GetMessage("T_IBLOCK_DESC_NEWS_PICTURE"),
            "TYPE" => "CHECKBOX",
            "DEFAULT" => "Y",
         ),
         "DISPLAY_PREVIEW_TEXT" => Array(
            "NAME" => GetMessage("T_IBLOCK_DESC_NEWS_TEXT"),
            "TYPE" => "CHECKBOX",
            "DEFAULT" => "Y",
         ),
         "PROPERTY_VIDEO" => Array(
            "NAME" => "Свойство, в котором хранится видео",
            "TYPE" => "LIST",
            "VALUES" => $arProps
         ),
      );
      ?>

    В результате в настройках параметра появляется новое поле: Свойство, в котором хранится видео.

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

    Файл component_epilog.php

    Файл component_epilog.php — инструмент для модификации данных работы компонента с включенным кешированием. Создается разработчиком самостоятельно. (Доступен с версии 9.0.)

    Схема работы компонента с файлами result_modifier.php и component_epilog.php:

    Этот файл подключается после исполнения шаблона. Аналогично файлам стилей, родительский компонент сохраняет в своем кеше список файлов эпилогов всех шаблонов дочерних компонентов (возможно вложенных), а при хите в кеш подключает эти файлы в том же порядке, как они исполнялись без кеширования. Точно так же при вызове дочерних компонентов в шаблоне нужно передавать значение $component.

    В файле component_epilog.php доступны $arParams, $arResult, но эти значения берутся из кеша. Набор ключей массива $arResult, попадающих в кеш, определяется в component.php вызовом вида:

                    $this->SetResultCacheKeys(array(
                            "ID",
                            "IBLOCK_TYPE_ID",
                            "LIST_PAGE_URL",
                            "NAV_CACHED_DATA",
                            "NAME",
                            "SECTION",
                            "ELEMENTS",
                    ));

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

    Примечание В файле component_epilog.php может быть любой код — только следует учитывать, что исполняться он будет независимо от наличия кеша на каждом хите. До версии главного модуля 10.0 в шаблон компонента передавалась копия массива arResult. В силу этого модификация этого массива в файле result_modifier.php не давала никаких результатов. Чтобы стало возможным изменение результатов вывода закешированных данных нужно разместить в файле result_modifier.php следующий код:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    global $APPLICATION;
    
    $cp = $this->__component; // объект компонента
    
    if (is_object($cp))
    {
    	// добавим в arResult компонента два поля - MY_TITLE и IS_OBJECT
    	$cp->arResult['MY_TITLE'] = 'Мое название';
    	$cp->arResult['IS_OBJECT'] = 'Y';
    	$cp->SetResultCacheKeys(array('MY_TITLE','IS_OBJECT'));
    
    	$APPLICATION->SetTitle($cp->arResult['MY_TITLE']); // не будет работать на каждом хите: 
    //отработает только первый раз, затем будет все браться из кеша и вызова $APPLICATION->SetTitle()
    // не будет. Поэтому изменение title делается в component_epilog, который выполняется на каждом хите.
    
    }
    ?>

    После чего изменения arResult, совершенные в шаблоне, станут доступны в component_epilog.php.

    Пример файла component_epilog.php

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    global $APPLICATION;
    
    if (isset($arResult['MY_TITLE']))
    	$APPLICATION->SetTitle($arResult['MY_TITLE']);
    ?>

    Особенность использования

    Файл component_epilog.php подключается непосредственно после подключения и исполнения шаблона. Таким образом, если в компоненте идёт подключение шаблона, а затем в коде компонента следуют ещё операции, то они будут выполнены уже после выполнения файла component_epilog.php.

    Соответственно, в случае совпадения изменяемых данных в component_epilog.php и в коде компонента после подключения шаблона выведены будут только последние данные, то есть из кода компонента.

    Пример такой ситуации

    Используем файл component_epilog.php из примера выше. А в коде компонента (файл component.php) есть такой код:

    <?
    $this->IncludeComponentTemplate();
    if($arParams["SET_TITLE"])
    {
    	$APPLICATION->SetTitle($arResult["NAME"]);
    }
    ?>

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

    В файле component_epilog.php доступны:

    • $arParams — параметры, чтение/изменение не затрагивает одноименный член компонента.
    • $arResult — результат, чтение/изменение не затрагивает одноименный член класса компонента.
    • $componentPath — путь к папке с компонентом от DOCUMENT_ROOT (например /bitrix/components/bitrix/iblock.list).
    • $component — ссылка на $this.
    • $this — ссылка на текущий вызванный компонент (объект класса CBitrixComponent), можно использовать все методы класса.
    • $epilogFile — путь к файлу component_epilog.php относительно DOCUMENT_ROOT
    • $templateName — имя шаблона компонента (например: .dеfault)
    • $templateFile — путь к файлу шаблона от DOCUMENT_ROOT (напр. /bitrix/components/bitrix/iblock.list/templates/.default/template.php)
    • $templateFolder — путь к папке с шаблоном от DOCUMENT_ROOT (напр. /bitrix/components/bitrix/iblock.list/templates/.default)
    • $templateData — обратите внимание, таким образом можно передать данные из template.php в файл component_epilog.php, причем эти данные закешируются и будут доступны в component_epilog.php на каждом хите/
    • $APPLICATION, $USER, $DB — глобальные переменные.

    Важно! Наиболее правильный вариант кастомизации компонента — скопировать его в отдельное пространство имен и работать уже с копией компонента. При этом нужно учитывать последствия:

    • Увеличивается общее количество компонентов, соответственно растет и количество выделяемых на их поддержку ресурсов.
    • Сложность в освоении новым разработчиком: сначала ему нужно будет найти в чем отличие от уже существующего стандартного компонента.

    Особенности подключения языковых файлов

    Задача: языковые фразы компонента по результатам работы файла component_epilog.php должны быть иными, чем в самом компоненте. В этом случае надо учитывать что размещать текстовые фразы в /lang/ru/template.php для файла component_epilog.php нет смысла: при попадании в кеш языковый файл не подключается.

    Решение: создавать файл /lang/ru/component_epilog.php и самостоятельно подключить его кодом:

    use BitrixMainLocalizationLoc; 
    
    Loc::loadLanguageFile(__FILE__); 
    Loc::getMessage("MY_MESS");

    Пример. Компонент в элементе ИБ

    Задача не частая, но встречающаяся: разместить в теле элемента информационного блока какой-либо компонент. Например, опрос. Попробуем реализовать эту задачу.

    • Обозначьте маркер для замены на текущий опрос. Пусть этот маркер будет #VOTE_ID_XX#, где XX это ID нужного нам опроса. Этот маркер вставьте в тело новости в нужное место:

    • Настройте компонент (в нашем случае это bitrix:voting.current) на отдельной странице так, как вам нужно. Деталь: отключите работу в AJAX-режиме.
    • Скопируйте шаблон новости в свой шаблон для редактирования. Откройте шаблон для детального просмотра и создайте два файла: result_modifier.php и component_epilog.php:

      result_modifier.php такого содержания:

      <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
      <?$this->__component->SetResultCacheKeys(array("CACHED_TPL"));?>

      component_epilog.php такого:

      <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
      <?
      echo preg_replace_callback(
               "/#VOTE_ID_([d]+)#/is".BX_UTF_PCRE_MODIFIER,
              function ($matches) {
                 ob_start();
                 /*component here*/
                 $retrunStr = @ob_get_contents();
                 ob_get_clean();
                 return $retrunStr;
              },
              $arResult["CACHED_TPL"]);
      ?>
    • Вместо /*component here*/ в component_epilog.php вставьте вызов нашего компонента (для большей наглядности этот код пишем отдельно от общего кода):
      $GLOBALS["APPLICATION"]->IncludeComponent(
         "bitrix:voting.current",
         "main_page",
         Array(
            "CHANNEL_SID" => "ANKETA",
            "VOTE_ID" => $matches[1],
            "CACHE_TYPE"   =>   "A",
            "CACHE_TIME"   =>   "3600",
            "AJAX_MODE" => "N",
            "AJAX_OPTION_SHADOW" => "Y",
            "AJAX_OPTION_JUMP" => "Y",
            "AJAX_OPTION_STYLE" => "Y",
            "AJAX_OPTION_HISTORY" => "N",
         )
      );

      Примечание: Обратите внимание, что вместо обычного $APPLICATION написано $GLOBALS["APPLICATION"]. Так надо для видимости объекта внутри временной функции. В остальном это полностью код компонента bitrix:voting.current.

      И обратите внимание на $matches[1]. Это единственный динамический параметр в вызываемом компоненте. Динамический в том плане, что он будет зависеть от того какой маркер мы меняем. Для #VOTE_ID_1# он будет равен 1, для #VOTE_ID_2# 2 и так далее.

    • Теперь надо изменить template.php. На второй строчке впишите конструкцию:
      <?ob_start();?>

      а в самом конце:

      <?
      $this->__component->arResult["CACHED_TPL"] = @ob_get_contents();
      ob_get_clean();
      ?>

    Манипуляции с component_epilog.php сделаны чтобы обойти кеширование.

    Что получили в итоге:

    В этом примере от файла result_modifier.php можно избавиться совсем (лишняя файловая операция все же). То есть $component->SetResultCacheKeys(array("CACHED_TPL")); можно добавить прямо в файл template.php. Здесь result_modifier.php был создан только для следования академическим правилам написания кода в Bitrix Framework.

    Примечание: Указанный способ не подходит для случая, когда внутренним компонентом выступает, к примеру, catalog.smart.filter, у которого ajax-режим подразумевает очистку буфера вывода. В результате, из-за вложенности буфера вывода, вы можете получить неадекватный вывод компонента.

    Пример. Исключение шаблона компонента из кэша

    Универсальный способ исключения шаблона компонента из кэша

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

    Идея решения проста: переместить шаблон компонента в эпилог компонента:

    1. Скопировать шаблон компонента в адресное пространство шаблона сайта.
    2. В папке шаблона компонента создать файл component_epilog.php и полностью скопировать в него код из файла шаблона template.php. Затем в самом верху component_epilog.php сразу после проверки подключения эпилога ядра <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?> перед началом непосредственно самого кода шаблона нужно добавить такой код:
      <?
      // заменяем $arResult эпилога значением, сохраненным в шаблоне
      if(isset($arResult['arResult'])) {
         $arResult =& $arResult['arResult'];
               // подключаем языковой файл
         global $MESS;
         include_once(GetLangFileName(dirname(__FILE__).'/lang/', '/template.php'));
      } else {
         return;
      }
      ?>
      
    3. Полностью очистить файл template.php и добавить такой код:
      <?if(!defined('B_PROLOG_INCLUDED')||B_PROLOG_INCLUDED!==true)die();
      ?>
      // добавляем к кэшируемому результату $arResult
      if(property_exists($component, 'arResultCacheKeys')) {
         if(!is_array($component->arResultCacheKeys)) {
            $component->arResultCacheKeys = array();
         }
         $sVarName = 'arResult';
         $component->arResultCacheKeys[] = $sVarName;
         $component->arResult[$sVarName] = $$sVarName;
      }

    Теперь результат компонента кэшируется, а шаблон – нет.

    Недостатки этого способа:

    1. Увеличение размера кэша. С размером кэша можно бороться путем включения в кэш только необходимых данных
    2. Шаблон все же постоянно генерирует html-код, что медленнее, чем вывод уже готового, ранее сформированного html-кода.

    Кеширование компонентов

    Автокеширование

    Автокеширование может выключаться глобально на весь сайт одной кнопкой в административной части на странице Автокеширование Настройки > Настройки продукта > Автокеширование.

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

    Устройство и место хранения

    Кеш компонентов хранится в файлах в папке /bitrix/cache.

    Идентификатор кеша компонента формируется на основе следующих параметров:

    • ID текущего сайта, который определяет путь к файлу с кешем. (Есть возможность использовать альтернативные способы хранения, но от этого не зависит работа с компонентами).
    • имени компонента,
    • имени шаблона компонента,
    • параметров компонента,
    • внешних условий, которые определяются в компоненте (например, список групп, к которым привязан текущий пользователь).

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

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

    Непосредственно после добавления/изменения элемента инфоблока в административной части кеш публичных компонентов не сбрасывается. Образно это объясняется тем, что инфоблок Новости «не знает», где выводятся новости в публичной части и сколько компонентов их отображает. Это не проблема если время кеширования выставлено правильно.

    Структурная схема

    Структурная схема работы компонента

    В $arParams содержится набор параметров компонента, component.php работает с входными параметрами запроса и базой данных, формирует результат в массив $arResult. Шаблон компонента преобразует результат в текст HTML.

    При первом хите сформированный HTML попадает в кеш. При последующих хитах запросов в базу не делается (или делается мало), данные читаются из кеша.

    Внимание! Учтите порядок выполнения, в этом случае код шаблона компонента и result_modifier.php не исполняются.

    Типичная ошибка: в шаблоне компонента вызываются отложенные функции: $APPLICATION->SetTitle(), $APPLICATION->AddChainItem() и т.д. В этом случае они работают только при выключенном кешировании.

    При разработке шаблонов собственных компонентов надо следовать простому правилу: задача шаблона — на основе входного массива $arResult сформировать текст HTML на выходе.

    В собственных компонентах надо формировать такой ID кеша, который однозначно определяет результирующий html. Но не допускайте попадание избыточных данных в ID кеша: это приводит к расходу места на диске и снижает попадания в кеш (а значит эффективность кеширования).

    Пример

    Пример кеширования классом CPHPCache

    $cntIBLOCK_List = 10;
    $cache = new CPHPCache();
    $cache_time = 3600;
    $cache_id = 'arIBlockListID'.$cntIBLOCK_List;
    $cache_path = 'arIBlockListID';
    if ($cache_time > 0 && $cache->InitCache($cache_time, $cache_id, $cache_path))
    {
       $res = $cache->GetVars();
       if (is_array($res["arIBlockListID"]) && (count($res["arIBlockListID"]) > 0))
          $arIBlockListID = $res["arIBlockListID"];
    }
    if (!is_array($arIBlockListID))
    {
       $res = CIBlock::GetList(
          Array(), 
          Array(
             'TYPE' => 'catalog', 
             'SITE_ID' => SITE_ID, 
             'ACTIVE' => 'Y', 
             "CNT_ACTIVE" => "Y", 
             "!CODE" => 'test%'
          ), true
       );
       while($ar_res = $res->Fetch())
       {
          if($ar_res['ELEMENT_CNT'] > 0)
          $arIBlockListID[] = $ar_res['ID'];
       }
       //////////// end cache /////////
       if ($cache_time > 0)
       {
             $cache->StartDataCache($cache_time, $cache_id, $cache_path);
             $cache->EndDataCache(array("arIBlockListID"=>$arIBlockListID));
       }
    }

    Список ссылок по теме:

    • Настройки кеширования в курсе Администратор. Базовый.
    • Класс CPHPCache в Документации для разработчика
    • Класс CPageCache в Документации для разработчика
    • Класс Cache в Документации D7

    Сache Dependencies (тегированный кеш)

    Теги кеша

    Главный модуль поддерживает [dw]теги кеша[/dw][di]Тегируется не выборка, а файл кеша.[/di]. Кеш можно помечать тегами и сбрасывать по тегам же. Сброс кеша компонентов инфоблоков происходит при изменении информации в них.

    Внимание! Для часто обновляемого большого массива данных использование тегированного кеша не оправданно.

    Базовый код тегирования кеша:

    01: $cache_id = md5(serialize($arParams));
    02: $cache_dir = "/tagged_getlist";
    03:
    04: $obCache = new CPHPCache;
    05: if($obCache->InitCache(36000, $cache_id, $cache_dir))
    06: {
    07:     $arElements = $obCache->GetVars();
    08: }
    09: elseif(CModule::IncludeModule("iblock") && $obCache->StartDataCache())
    10: {
    11:     $arElements = array();
    12:     $rsElements = CIBlockElement::GetList($arParams["order"], $arParams["filter"]);
    13:
    14:     global $CACHE_MANAGER;
    15:     $CACHE_MANAGER->StartTagCache($cache_dir);
    16:     while($arElement = $rsElements->Fetch())
    17:     {
    18:         $CACHE_MANAGER->RegisterTag("iblock_id_".$arElement["IBLOCK_ID"]);
    19:         $arElements[] = $arElement;
    20:     }
    21:     $CACHE_MANAGER->RegisterTag("iblock_id_new");
    22:     $CACHE_MANAGER->EndTagCache();
    23:
    24:     $obCache->EndDataCache($arElements);
    25: }
    26: else
    27: {
    28:     $arElements = array();
    29: }

    В строке 01 инициализируется уникальный идентификатор файла кеша. Далее определяется каталог относительно /bitrix/cache, в котором будут сохранятся файлы кеша с разными значениями $arParams. Важно, что этот путь начинается со слеша и им не заканчивается. При использовании в качестве кеша memcached или APC это будет критичным при сбросе кеша.

    В строках 04-05 инициализируется объект кеша. Если время кеширования не истекло, то будет выполнена строка 07 и мы получим данные из файла кеша.

    Условие в строке 09 фактически всегда будет true. Это подключение модуля и начало кеширования.

    В строке 12 происходит обращение к базе данных. Важно, чтобы все параметры от которых зависит результат выборки «поучаствовали» в идентификаторе кеша ($cache_id).

    В 14-й строчке объявляется доступ к переменной $CACHE_MANAGER. Этот объект будет управлять тегами.

    Строка 15 — все последующие назначаемые теги будут привязаны к каталогу $cache_dir. При сбросе кеша по одному из них содержимое этого каталога будет удалено. StartTagCache — может использоваться вложено. Например:

    $CACHE_MANAGER->StartTagCache($cache_dir1);
        $CACHE_MANAGER->StartTagCache($cache_dir2);
            $CACHE_MANAGER->StartTagCache($cache_dir3);
            $CACHE_MANAGER->EndTagCache();
        $CACHE_MANAGER->EndTagCache();
    $CACHE_MANAGER->EndTagCache();

    Важно чтобы вызовы StartTagCache и EndTagCache были сбалансированы. Объект $CACHE_MANAGER создает и отслеживает стек каталогов кеша. При этом теги назначенные на каталог $cache_dir3 будут также связаны и с $cache_dir2 и $cache_dir1. Теги назначенные на cache_dir2 будут связаны и с $cache_dir1.

    В строке 18 происходит отметка кеша тегом с помощью метода RegisterTag. Длина тела может быть до 100 символов. В методе RegisterTag автоматически удаляются дубликаты тегов.

    Строка 21 — необходима для сброса кеша при добавлении нового инфоблока, ведь у него на момент создания кеша тег не записывается (инфоблока ещё нет).

    Строка 22 — запись тегов каталога в таблицу базы данных. Можно считать по одному insert’у на тег.

    Сброс кеша:

    $CACHE_MANAGER->ClearByTag("iblock_id_7");

    или (с 15.0.7):

    CIBlock::clearIblockTagCache( 7 );

    Для отключения тегирования конкретных инфоблоков используйте:

    CIBlock::DisableTagCache($iblock_id)

    Компоненты инфоблоков

    Для запуска механизма необходимо определить константу в файле dbconn.php. (Это можно сделать простым включением Управляемого кеша на закладке Управляемый кеш на странице Настройки > Настройки продукта > Автокеширование.)

    define("BX_COMP_MANAGED_CACHE", true);

    При этом в методе StartResultCache компонента будет вызываться StartTagCache с путем к кешу компонента (с учетом страницы). А в методе EndResultCache (который в свою очередь вызывается из IncludeComponentTemplate) — EndTagCache.

    В модуле инфоблоков CIBlockElement::GetList и CIBlockSection::GetList возвращают объект класса CIBlockResult.

    В методе Fetch/GetNext этого объекта будут вызываться $CACHE_MANAGER->RegisterTag("iblock_id_".$res["IBLOCK_ID"]);.
    Если выборка не содержит элементов, то значение идентификатора инфоблока будет взято из фильтра.
    Если выборка НЕ пустая, в arSelect не запрашивается IBLOCK_ID, но есть фильтрация по IBLOCK_ID, то сброс кеша произойдёт.

    Сброс кеша вызывается из методов Add/Update/Delete для элементов, разделов и инфоблоков.

    Не стоит использовать этот механизм при частом обновлении элементов или разделов. С другой стороны это должно быть удобным для редакторов сайтов: изменения моментально отображаются на сайте. Еще один плюс — можно задавать практически бесконечное время кеширования.

    Пример. Добавление своего тега

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

    Способ 1

    В тело компонента добавьте следующий код:

    if ($this->StartResultCache(......))
    {
       if (defined('BX_COMP_MANAGED_CACHE') && is_object($GLOBALS['CACHE_MANAGER']))
       {
                $GLOBALS['CACHE_MANAGER']->RegisterTag('my_custom_tag');   
       }
    
       // do something
    
       $this->IncludeComponentTemplate();
    }
    else
    {
       $this->AbortResultCache();
    }
    

    Способ 2

    В шаблон компонента (в result_modifier.php) добавьте следующий код:

    <?
    if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    if (defined('BX_COMP_MANAGED_CACHE') && is_object($GLOBALS['CACHE_MANAGER']))
    {
       $cp =& $this->__component;
       if (strlen($cp->getCachePath()))
       {      
          $GLOBALS['CACHE_MANAGER']->RegisterTag('my_custom_tag');
       }
    }
    ?>
    

    Чтобы сбросить все кеши, помеченные вашим тегом, выполните следующий код:

    if (defined('BX_COMP_MANAGED_CACHE') && is_object($GLOBALS['CACHE_MANAGER']))
       $GLOBALS['CACHE_MANAGER']->ClearByTag('my_custom_tag');
    

    Примечание: один и тот же кеш может быть помечен несколькими тегами. Например, если вы пометите своим тегом кеш компонента bitrix:news.list, то у кеша будет два тега: штатный «iblock_id_XX» и ваш «my_custom_tag». Соответственно, кеш будет сбрасываться и при добавлении/изменении элемента в инфоблоке XX (штатный функционал), и при сбросе кеша вручную через ClearByTag('my_custom_tag').

    Пример. Сортировка в компонентах

    Для выполнения сортировки в компоненте news.list или catalog.section.list компоненту необходимо передать параметры ELEMENT_SORT_FIELD и ELEMENT_SORT_ORDER.

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

    • id — ID элемента;
    • sort — индекс сортировки;
    • timestamp_x — дата изменения;
    • name — название;
    • active_from или date_active_from — начало периода действия элемента;
    • active_to или date_active_to — окончание периода действия элемента;
    • status — код статуса элемента в документообороте;
    • code — мнемонический код элемента;
    • iblock_id — числовой код информационного блока;
    • modified_by — код последнего изменившего пользователя;
    • active — признак активности элемента;
    • show_counter — количество показов элемента (учитывается функцией CIBlockElement::CounterInc);
    • show_counter_start — время первого показа элемента (учитывается функцией CIBlockElement::CounterInc);
    • shows — усредненное количество показов (количество показов / продолжительность показа);
    • rand — случайный порядок;
    • xml_id или external_id — внешний код;
    • tags — теги;
    • created — время создания;
    • created_date — дата создания без учета времени;
    • cnt — количество элементов (только при заданной группировке).

    Примечание: Поля active_from и active_to — устаревшие.

    Также сортировать можно по созданным вами свойствам элемента информационного блока:

    • property_<PROPERTY_CODE> — по значению свойства с числовым или мнемоническим кодом PROPERTY_CODE (например, PROPERTY_123 или PROPERTY_NEWS_SOURCE).
    • propertysort_<PROPERTY_CODE> — по индексу сортировки варианта значения свойства. Только для свойств типа Список.
    • catalog_<CATALOG_FIELD>_<PRICE_TYPE> — по полю CATALOG_FIELD (может быть PRICE — цена или CURRENCY — валюта) из цены с типом PRICE_TYPE (например, catalog_PRICE_1 или CATALOG_CURRENCY_3). Сортировка должна иметь формат: CATALOG_(PRICE или CURRENCY)_ID-типа-цены.
    • catalog_QUANTITY — сортировка по количеству.
    • PROPERTY_<PROPERTY_CODE>.<FIELD> — по значению поля элемента указанного в качестве привязки. PROPERTY_CODE — мнемонический или символьный код свойства типа привязка к элементам. FIELD может принимать значения:
      • ID
      • TIMESTAMP_X
      • MODIFIED_BY
      • CREATED
      • CREATED_DATE
      • CREATED_BY
      • IBLOCK_ID
      • ACTIVE
      • ACTIVE_FROM
      • ACTIVE_TO
      • SORT
      • NAME
      • SHOW_COUNTER
      • SHOW_COUNTER_START
      • CODE
      • TAGS
      • XML_ID
      • STATUS
    • PROPERTY_<PROPERTY_CODE>.PROPERTY_<PROPERTY_CODE2> — по значению свойства элемента указанного в качестве привязки. PROPERTY_CODE — мнемонический или символьный код свойства типа привязки к элементам. PROPERTY_CODE2 — код свойства связанных элементов.
    • HAS_PREVIEW_PICTURE и HAS_DETAIL_PICTURE — сортировка по наличию и отсутствию картинок.

    Примечание: Свойства catalog_*** доступны только при наличии модуля Торговый каталог.

    Тип сортировки указывается в соответствии со списком:

    • asc — по возрастанию;
    • nulls,asc — по возрастанию с пустыми значениями в начале выборки;
    • asc,nulls — по возрастанию с пустыми значениями в конце выборки;
    • desc — по убыванию;
    • nulls,desc — по убыванию с пустыми значениями в начале выборки;
    • desc,nulls — по убыванию с пустыми значениями в конце выборки.

    Самый простой способ передать новые параметры для сортировки в компонент — это использовать $_GET запрос и передать соответствующие переменные.

    Также можно воспользоваться $_SESSION и записать переменные в массив переменных сессии. Предположим нам необходимо сделать ссылки или кнопки(название, цена, лидер продаж, дата поступления) для сортировки товаров в разделе каталога (используем комплексный компонент catalog). После того как мы скопировали шаблон, необходимо открыть файл section.php и внести в него следующие модификации перед подключением компонента bitrix:catalog.section.list:

    <?if (
        isset($_GET["sort"]) && isset($_GET["method"]) && (
    $_GET["sort"] == "name" || 
                  $_GET["sort"] == "catalog_PRICE_3" ||
                 $_GET["sort"] == "property_PRODUCT_TYPE" ||
                 $_GET["sort"] == "timestamp_x")){
           $arParams["ELEMENT_SORT_FIELD"] = $_GET["sort"];
           $arParams["ELEMENT_SORT_ORDER"] = $_GET["method"];
       }?>
    

    Этот код необходим для изменения параметров сортировки в компоненте. Далее откроем файл template.php компонента catalog.section и добавим ссылки управления сортировками:

    <p class="sort">Сортировка:
    	<a <?if ($_GET["sort"] == "name"):?> class="active" <?endif;?>
           href="<?=$arResult["SECTION_PAGE_URL"]?>?sort=name&method=asc">название</a> 
    	<a <?if ($_GET["sort"] == "catalog_PRICE_3"):?> class="active" <?endif;?>
           href="<?=$arResult["SECTION_PAGE_URL"]?>?sort=catalog_PRICE_3&method=asc">цена</a> 
    	<a <?if ($_GET["sort"] == "property_PRODUCT_TYPE"):?> class="active" <?endif;?>
           href="<?=$arResult["SECTION_PAGE_URL"]?>?sort=property_PRODUCT_TYPE&method=desc">лидер продаж</a> 
    	<a <?if ($_GET["sort"] == "timestamp_x"):?> class="active" <?endif;?>
           href="<?=$arResult["SECTION_PAGE_URL"]?>?sort=timestamp_x&method=desc">дата поступления</a>
    </p>
    

    Данную сортировку можно выполнить без перезагрузки страницы с использованием jQuery либо JS-библиотеки Bitrix Framework.

    В случае использования отдельного компонента catalog.section.list или news.list необходимо четко понимать, что редактировать [dw]сортировку данных в компоненте[/dw][di][/di] через визуальный редактор будет нельзя.

    Перед вызовом компонента разместить код:

    <?
    $sortField = 'ID'; // поле сортировки по умолчанию
    $sortOrder = 'ASC'; // направление сортировки по умолчанию
    
    if (
        isset($_GET["sort"]) && isset($_GET["method"]) && (
    $_GET["sort"] == "name" || 
                 $_GET["sort"] == "catalog_PRICE_3" ||
                $_GET["sort"] == "property_PRODUCT_TYPE" ||
                $_GET["sort"] == "timestamp_x")){
          $sortField = $_GET["sort"];
          $sortOrder = $_GET["method"];
      }
    
    ?>
    <?$APPLICATION->IncludeComponent(
        "bitrix:catalog.section",
        "",
        array(
            ... // остальные настройки компонента
            "ELEMENT_SORT_FIELD" => $sortField,
            "ELEMENT_SORT_ORDER" => $sortOrder,
            ... // еще настройки компонента
        );
    );?>

    Пример. Использование событий

    Для дополнения и неявного изменения (без вмешательства в код компонента) логики работы используйте технологию Событий. Рассмотрим пример использования событий.

    Прямое назначение ответственного в Техподдержке

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

    Есть два варианта решения данной задачи:

    • Модифицируется код стандартного компонента support.wizard в файлах component.php и .description.php. Также, для большей гибкости, можно установить переменную-флаг в файле .parameters.php.

      Такой подход для непосвященного в такие понятия, как События в Bitrix Framework наиболее очевиден. В результате, задача резко усложняется — нужно разбираться в логике кода компонента, чтобы понять, где нужно добавить свой функционал, а также, не стоит забывать о том, что Bitrix Framework постоянно обновляется. Решение довольно трудоёмкое.

    • Достаточно будет использовать События и модифицировать шаблон.

    Теория Событий описана ниже. Вкратце: в код ядра, обычно в начало и конец вызова системной функции, разработчики уже вставили вызов системной функции, в нашем случае это: CTicket::ExecuteEvents. Если мы хотим дополнить эту функцию своей, то в файле /bitrix/php_interface/init.php пишем:

    AddEventHandler("support", "OnAfterTicketAdd", "MyHandler");

    Если вызываемая функция принадлежит классу, то нужно вместо MyHandler написать array("<имя_класса>","<имя_функции>");. Код файла init.php:

    <?
    // В массиве $arFields передаются все параметры, которые были переданы созданному тикету + ID и MID тикета
    // $_REQUEST["PERSONAL"] - Значение, которое мы получаем из нашего шаблона (тега select)    
    function AfterTicketAdd($arFields) 
    {
       if ($_REQUEST["PERSONAL"]>0) 
       {
          //Добавляем к уже созданому тикету свойство "RESPONSIBLE_USER_ID"=>    
          CTicket::Set(array(
              "RESPONSIBLE_USER_ID"=>$_REQUEST["PERSONAL"]),
             $intMessage,
             $arFields['ID'],
             "N"
          ); 
       }
    }
    ?>

    $intMessage — ID нового сообщения, нам не нужно, "N" — здесь указывается, что права на добавление нам проверять не надо.

    Модификация шаблона

    • Скопируйте шаблон системного компонента
    • Добавьте в шаблон следующий код:
      <?
      use BitrixMainLocalizationLoc;
      
      // список всех сотрудников техподдержки
      // полученный через CTicket::GetSupportTeamList()
      $teamList = $arResult['TEAM_LIST'];
      
      ?>
      <select name="PERSONAL" id="PERSONAL">
         <option><?= Loc::getMessage('select_any') ?></option>
         <? foreach ($teamList as $item): ?>
              <option value="<?= $item['REFERENCE_ID'] ?>"><?= $item['REFERENCE'] ?></option>
          <? endforeach ?>
      </select>
    • При необходимости допишите локализацию на вашем родном языке.
    • Добавьте шаблон в файл ticket_edit.php.

    Кастомизация компонентов

    Кастомизация стандартного компонента — копирование стандартного компонента в собственное пространство имён и изменение логики его работы с целью изменения/добавления функционала.

    Большинство задач в Bitrix Framework реализуется через компоненты, и в шаблоне компонента вы оперируете массивами $arResult — это результат работы компонента (данные) и $arParams — это входные параметры.

    Чтобы кастомизировать стандартный компонент необходимо:

    • Создать новое пространство имён компонентов в папке /local/components/, например создать директорию /local/components/my_components/.
    • В созданную папку необходимо скопировать папку с компонентом, который хотите изменить (копировать из папки /bitrix/components/bitrix/).
    • Изменить компонент под текущие задачи.
      • изменить описание компонента на свое в файлах .description.php и /lang/ru/.description.php;
      • исправить файлы .parameters.php и component.php, модифицировав (добавив необходимый) функционал с помощью API продукта;
    • Отредактировать шаблон компонента под текущие задачи.
    • [dw]Очистите кеш[/dw][di][/di] визуального редактора. В результате в визуальном редакторе отобразится кастомизированный компонент.

      Важно! У работы с копией компонента есть определенные недостатки, которые нужно учитывать:

      • Увеличивается общее количество компонентов, соответственно растет и количество выделяемых на их поддержку ресурсов.
      • Сложность в освоении новым разработчиком: сначала ему нужно будет найти, в чем отличие кастомизированного компонента от уже существующего стандартного.

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

    Простой пример кастомизации компонента

    Компонент news.list при большом числе элементов может существенно тормозить генерацию страницы. Задача – оптимизировать работу компонента. Одним из вариантов оптимизации может стать удаление ссылки на детальный текст новости в виде части текста (останется ссылка в виде названия новости).

    • Скопируйте компонент в свое пространство имен.
    • В коде скопированного компонента удалите строку:
      "DETAIL_TEXT",
      "DETAIL_TEXT_TYPE",
    • Сохраните внесенные изменения.
    • Примените вместо стандартного свой собственный компонент.

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

    Модификация простого компонента в составе сложного

    При работе с комплексным компонентом можно модифицировать один или несколько простых компонентов, а остальные остаются стандартные.

    Например, необходимо в компоненте socialnetwork.user_groups, который в составе комплексного компонента socialnetwork_group выводит список групп, увеличить длину выводимого описания группы с 50 до 100 символов (специально выберем простую модификацию чтобы акцентировать внимание на самом процессе).

    • Скопируйте шаблон комплексного компонента.

      Теперь в шаблоне сайта имеем шаблон комплексного компонента, перейдя в который увидим большой набор файлов в папке /local/templates/<шаблон сайта>/components/bitrix/socialnetwork_group/.default.

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

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

    • Теперь в оставшемся файле заменяем
      $APPLICATION->IncludeComponent(
                  "bitrix:socialnetwork.user_groups",

      на

      $APPLICATION->IncludeComponent(
                  "custom:socialnetwork.user_groups",
    • Копируем папку /bitrix/components/bitrix/socialnetwork.user_groups в /local/components/custom/socialnetwork.user_groups.
    • в файле /local/components/custom/socialnetwork.user_groups/component.php заменяем
      "GROUP_DESCRIPTION" => (strlen($arGroup["DESCRIPTION"]) > 50 ? substr($arGroup["DESCRIPTION"], 0, 50)."..." : $arGroup["DESCRIPTION"]), 

      на

      "GROUP_DESCRIPTION" => (strlen($arGroup["DESCRIPTION"]) > 100 ? substr($arGroup["DESCRIPTION"], 0, 100)."..." : $arGroup["DESCRIPTION"]), 

    Теперь весь функционал социальной сети остаётся стандартный, кроме компонента socialnetwork.user_groups.

    Тип параметров CUSTOM

    Тип параметров CUSTOM предоставляет разработчику полную свободу кастомизации. Например, есть какой-то системный или сторонний компонент. В зависимости от шаблона возникает необходимость добавить к компоненту какие-то свои настройки.

    Реализуется это с помощью:

    Параметр Описание
    JS_FILE файл с JS кодом ответственным за отображение кастомной опции.
    JS_EVENT callback функция которая будет вызвана после загрузки JS_FILE
    JS_DATA дополнительные данные, передаваемые в JS_EVENT

    Пример JS_DATA:

    {
    data:JS_DATA, //JS_DATA из .parameters.php
    oCont: td,    /* контейнер, в котором предлагается размещать кастомный контрол  управления параметром */
    oInput: input,//input в котором и будет предаваться значение параметра на сервер при сохранении
    propertyID:"MAP_DATA",//название параметра
    propertyParams: { /*...*/ },//Объект содержащий всё то же, что и массив параметра в .parameters.php
    fChange:function(){ /*...*/ },//callback для вызова, при изменении параметра
    getElements:function(){ /*...*/ }//возвращает объект со всеми параметрами компонента
    }

    Реализация в штатном компоненте

    Рассмотрим пример использования типа параметров CUSTOM в штатном компоненте map.google.view.

    Примечание: для использования компонентов Google необходимо иметь ключ доступа. Инструкция по получению ключа находится в документации.

    В файле .parameters.php видим:

    $arComponentParameters = array(
    //...
    'MAP_DATA' => array(
                'NAME' => GetMessage('MYMS_PARAM_DATA'),
                'TYPE' => 'CUSTOM',
                'JS_FILE' => '/bitrix/components/bitrix/map.google.view/settings/settings.js',
                'JS_EVENT' => 'OnGoogleMapSettingsEdit',
                'JS_DATA' => LANGUAGE_ID.'||'.GetMessage('MYMS_PARAM_DATA_SET'),
                'DEFAULT' => serialize(array(
                    'google_lat' => GetMessage('MYMS_PARAM_DATA_DEFAULT_LAT'),
                    'google_lon' => GetMessage('MYMS_PARAM_DATA_DEFAULT_LON'),
                    'google_scale' => 13
                )),
                'PARENT' => 'BASE',
            )
    //...
    );

    В файле /bitrix/components/bitrix/map.google.view/settings/settings.js:

    function JCEditorOpener(arParams)
    {
        this.jsOptions = arParams.data.split('||');
        this.arParams = arParams;
    
        var obButton = document.createElement('BUTTON');//создаём кнопку
        this.arParams.oCont.appendChild(obButton);// добавляем в контейнер
       
        obButton.innerHTML = this.jsOptions[1];//текст из JS_DATA
       
        obButton.onclick = BX.delegate(this.btnClick, this);//навешиваем callback'и
        this.saveData = BX.delegate(this.__saveData, this);
    }

    По нажатию кнопки открывается диалог, который генерируется в /bitrix/components/bitrix/map.google.view/settings/settings.php. В запросе к settings.php передаётся текущее значение MAP_DATA.

    Заголовок

    $obJSPopup->ShowTitlebar();
    $obJSPopup->StartDescription('bx-edit-menu');
    <p><b><? echo GetMessage('MYMV_SET_POPUP_WINDOW_TITLE')?></b></p><!-- Заголовок диалогового окна-->
    <p class="note"><? echo GetMessage('MYMV_SET_POPUP_WINDOW_DESCRIPTION')?></p><!-- Описание -->

    Блок контента

    $obJSPopup->StartContent();

    Блок кнопок

    $obJSPopup->StartButtons();

    Кнопка сохранения

    <input type="submit" value="<?echo GetMessage('MYMV_SET_SUBMIT')?/>" onclick="return jsGoogleCE.__saveChanges();"/>
    $obJSPopup->ShowStandardButtons(array('cancel'));//кнопка отмены
    $obJSPopup->EndButtons();

    В __saveChanges() данные сериализуются в строку и записываются в oInput, функцию сериализации на js в формат php можно посмотреть в bitrix/components/bitrix/map.google.view/settings/settings_load.js. В компоненте десериализация проводится из $arParam[~MAP_DATA].

    Локализация

    Языковой файл находится в lang/ru/.parameters.php. При использовании типа параметров CUSTOM не забывайте добавлять сообщения в этот файл.

    Список ссылок по теме:

    • Пример подключения календаря в настройках компонента.
    • Пример подключения форумов для механизма комментариев в каталоге.

    Ещё пара примеров работы

    Каким образом можно вывести капчу в своём компоненте?

    Пример использования CAPTCHA на странице. Ну а дальше — смотрите сами что куда вставлять: что в компонент (например, проверку), что в шаблон (например, вывод картинки)

    <?
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
    $APPLICATION->SetTitle("Title");
    ?>
    <?
    if (isset($submit)) {
      echo 'сабмит прошел...<br>';
      echo $myname.'<br>';
      echo $cap.'<br>';
       if (!$GLOBALS["APPLICATION"]->CaptchaCheckCode($cap, $captcha_sid))
          {
             $error=true;
             echo 'error captcha
    '; } } ?> <form id="linkForm" name="mailForm" action="test.php" method="post"> <table cellspacing="3" cellpadding="0" width="100%" bgcolor="#eeeeee" border="0"> <tbody> <tr><td valign="top" align="right">Моё имя *</td><td><input size="40" value="" name="myname" /></td></tr> <tr><td valign="top" align="right">CAPTCHA *</td><td><? $capCode = $GLOBALS["APPLICATION"]->CaptchaGetCode(); ?> <input type="hidden" name="captcha_sid" value="<?= htmlspecialchars($capCode) ?>"> <img src="/bitrix/tools/captcha.php?captcha_sid=<?= htmlspecialchars($capCode) ?>" width="180" height="40"><br> <input size="40" value="" name="cap" /></td></tr> <tr><td valign="top" align="right"> </td> <td><input type="submit" value="Отправить" name="submit" />  <input type="reset" value="Сбросить" name="Reset" /></td></tr> </tbody> </table> </form><?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>

    Как сделать чтобы компонент на главной не виден, а на других был виден?

    Решение (добавить в шаблон компонента):

    if ($curPage = $APPLICATION->GetCurPage(true))
    {
       if (($curPage != "/index.php"))
            {
                ....
            }
    } 

    Инфоблоки, работа с ними

    Цитатник веб-разработчиков.

    Ban Dmitry: Ну да, при работе с битриксом часто надо думать, в какие запросы трансформируется твой вызов API. Но, думается мне, в любых других высокоуровневых средах разработки то же самое.

    Начальные сведения об инфоблоках.

    Потребности заказчиков сайтов очень разнообразны. Штатный функционал Bitrix Framework не может решать всех задач, которые могут быть поставлены перед разработчиком при создании интернет-проектов. Для реализации нестандартных задач необходимо использовать API. API инфоблоков рекомендуется особенно внимательно изучить. Они чаще всего используются при программировании.

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

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

    Перед использованием модуля необходимо проверить, установлен ли он, и подключить его при помощи конструкции:

    <?
    if(CModule::IncludeModule("iblock"))
    {
       //здесь можно использовать функции и классы модуля
    }
    ?>

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

    Вся работа с датами через API (вставка, выборка, фильтры и т.п.) производится в формате текущего сайта или, если в административной части, в формате текущего языка.

    Ряд функций API доступен всегда, т.е. описан в главном модуле, а ряд функций зависит от используемого модуля, и может присутствовать или отсутствовать в различных редакциях продукта. Например, функции для работы с социальной сетью присутствуют в редакциях «1С-Битрикс: Управление сайтом — Бизнес» и выше, а также в «1С-Битрикс: Корпоративный портал».

    Для большинства классов Bitrix Framework доступны функции:

    • Выборка данных (GetList).
    • Занесение нового элемента (Add).
    • Обновление и удаление элемента (Update).
    • Удаление элемента (Delete).
    • И другие функции.

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

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

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

    Совет от веб-разработчиков

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

    Нельзя:

    foreach($arResult["ORDERS"] as $key => $val)
    {
       foreach($val["BASKET_ITEMS"] as $vvval)
       {
          $rsEls = CIBlockElement::GetByID();
       }
    }

    Следует:

    foreach($arResult["ORDERS"] as $key => $val)
    {
       foreach($val["BASKET_ITEMS"] as $vvval)
       {
          $arIDs[] = $vvval["PRODUCT_ID"];
       }
    }
    
    $rsEls = CIBlockElement::GetList(array(), array("ID" => $arIDs));
    ....
    
    foreach($arResult["ORDERS"] as $key => $val)
    {
       foreach($val["BASKET_ITEMS"] as $vvval)
       {
          //наполняем данные, налаживая соответствие ID-ков
       }
    } 

    Фактически, вы сводите порой десятки, если не сотни, запросов к одному. Разве это не круто?

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

    В главе приведены примеры работы с инфоблоками через API. Кроме этого советуем познакомиться со следующими статьями:

    • Товары и CIBlockElement::GetList
    • Выводим свойства элемента инфоблока правильно
    • Бесплатный модуль для расширения возможностей работы с инфоблоками

    Инфоблоки 2.0

    При создании информационных блоков рекомендуется хранить свойства инфоблока в отдельной таблице, причем все значения свойств одного элемента хранятся в одной строке. Эта технология называется [dw]Инфоблоки 2.0[/dw][di]В документации к Bitrix Framework, в сообщениях форума на сайте компании и в других местах могут встречаться прежние названия технологии: инфоблоки +.[/di] и позволяет существенно ускорить работу системы, а также снять ряд ограничений в предыдущей версии инфоблоков. Например, теперь нет необходимости в дополнительном запросе CIBlockElement::GetProperty при выборе значений свойств функцией CIBlockElement::GetList.

    Возможности инфоблоков 2.0:

    • При выборке элементов можно сразу получать значения свойств, т.к. количество присоединяемых таблиц в запросе не увеличивается с каждым свойством, а всегда равно единице.
    • Фильтрация по значениям свойств происходит аналогично инфоблокам 1.0 (за исключением множественных).
    • Выборка значений множественных свойств не приводит к декартовому произведению результата запроса — значения свойств передаются в виде массива.
    • Для комбинированных фильтров по немножественным (единичным) свойствам появилась возможность ручного создания составных индексов БД для ускорения операций выборки.
    • Для инфоблоков 2.0 нет возможности «сквозной» выборки элементов, когда в фильтре указывается тип инфоблока и символьный код свойства. В фильтре необходимо указывать IBLOCK_ID.
    • В инфоблоках 2.0 таблица со связанными элементами будет присоединяться (join) только один раз.

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

    Для разработчика очень удобно и гибко то, что свойства хранятся в одной общей таблице и управляются информацией метаданных из IBLOCK_PROPERTY_ID. Так как всегда можно поправить какую-то метаинформацию и никакая другая информация при этом у вас не пропадет. Это свойственно простым инфоблокам.

    Если информация хранится в инфоблоках 2.0 и у свойства меняется его тип (например, с Числа на Строку), то изменяется и тип хранения в самой базе данных. То есть меняется не логика интерпретации продуктом значения этого свойства, а меняется само значение. То есть нельзя «играться» с данными.

    С точки зрения производительности инфоблоки 2.0 выигрывают на небольших справочниках с небольшим количеством (20-30) редко изменяемых свойств. Ленту новостей нет никакого смысла переводить на этот вид инфоблоков. Вы выиграете в числе запросов, но проиграете во времени их исполнения.

    В инфоблоках 2.0 существует физическое ограничение БД на количество свойств инфоблока. На данный момент это не отслеживается в системе, так как зависит от множества непрогнозируемых факторов: типа свойств, конфигурации MySQL и других. При превышении этого физического ограничения вы получите редкую для Bitrix Framework ошибку MySQL. Данные при этом потеряны не будут.

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

    Уровень информационного блока.

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

    При редактировании инфоблока доступна ссылка на «конвертер» между типами хранения. Пользоваться им надо с большой осторожностью, т.к. продолжительность процесса конвертации зависит от общего объема значений свойств инфоблока. В течение всего процесса инфоблок находится в несогласованном состоянии (часть значений перенесена, а часть нет). На тестовой конфигурации для MySQL версии скорость была порядка 1500 элементов за 20-ти секундный шаг.

    Внимание! Перевод из обычных инфоблоков в инфоблоки 2.0 невозможен при числе свойств более 50.

    В классе CIBlockResult переопределён метод Fetch. В нем происходит кеширование значений множественных свойств элемента, участвующих в выборке. Для этих же свойств типа список выбираются пары ID=>VALUE из справочника списков.

    В API предусмотрен параметр VERSION в массиве полей $arFields метода CIBlock::Add. Его значения: 1 — для общего хранения и 2 — для выделенного (нового).

    Уровень свойств информационного блока

    При редактировании свойств (смена признака множественности или типа свойства) для свойств в выделенном хранилище выполняются дополнительные операции по управлению таблицами. Такие как удаление/добавление колонок, вставка/обновление или удаление большого количества записей. Без настоятельной необходимости лучше избегать этого. Наилучшей методикой будет менять тип или множественность одного свойства за один раз. Причем для единичных свойств предпочтительнее сначала сделать его множественным а потом сменить тип, а для множественных наоборот — сначала тип, и уже потом делать его единичным.

    Уровень элементов информационного блока

    При вставке элемента вставляется и соответствующая запись в таблицу хранения значений свойств элемента.

    Уровень значений свойств элементов информационного блока

    Значения единичных свойств инфоблока с выделенным хранилищем ID — составные и состоят из ID элемента и ID свойства, разделенных двоеточием. При обновлении множественных свойств происходит сброс кеша этих значений.

    Значения свойств хранятся в 2-х таблицах (описание таблиц и их структуры имеет справочный характер и могут меняться в следующих версиях):

    • b_iblock_element_prop_mNN — для множественных. Имеет ту же самую структуру, что и b_iblock_element_property;
    • b_iblock_element_prop_sNN — для единичных. Имеет поле IBLOCK_ELEMENT_ID — ID элемента инфоблока которому принадлежат свойства:
      • PROPERTY_XX — хранит значения единичного свойства XX или кеш значений для множественного свойства;
      • DESCRIPTION_XX — хранит описание для единичного свойства.

    Как достичь наибольшей эффективности при использовании Инфоблоков 2.0?

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

    Например: если раньше шаблон кода был примерно таким:

    <?
    //Определяем массив нужных полей элемента
    $arSelect = array(
        "ID",
        "IBLOCK_ID",
        "IBLOCK_SECTION_ID",
        "NAME",
        "PREVIEW_PICTURE",
        "DETAIL_PICTURE",
        "DETAIL_PAGE_URL",
    );
    //Получаем список элементов. (+1 запрос)
    if($rsElements = GetIBlockElementListEx($IBLOCK_TYPE, false, false, array($ELEMENT_SORT_FIELD => $ELEMENT_SORT_ORDER), 
    $ELEMENT_COUNT, $arFilter, $arSelect))
    {
        //Инициализация постраничного вывода.
        $rsElements->NavStart($ELEMENT_COUNT);
        $count = intval($rsElements->SelectedRowsCount());
        if ($count>0)
        {
            //Для каждого элемента:
            while ($obElement = $rsElements->GetNextElement())
            {
                $arElement = $obElement->GetFields();
                //Получаем его свойства. (+1 запрос)
                $arProperty = $obElement->GetProperties();
                //Ниже можно пользоваться значениями свойств.
                //Например:
                echo $arProperty["SOURCE"],"
    "; //и т.д. и т.п. } } } ?>

    Теперь, после преобразования в новый тип хранения, стало возможным избавится от запросов в цикле:

    <?
    //Определяем массив нужных полей элемента
    $arSelect = array(
        "ID",
        "IBLOCK_ID",
        "IBLOCK_SECTION_ID",
        "NAME",
        "PREVIEW_PICTURE",
        "DETAIL_PICTURE",
        "DETAIL_PAGE_URL",
        "PROPERTY_SOURCE", //Выбираем нужное нам свойство
        // И все другие какие могут понадобится
        // непосредственно в списке
    );
    //Получаем список элементов. (+1 запрос)
    if($rsElements = GetIBlockElementListEx($IBLOCK_TYPE, false, false, array($ELEMENT_SORT_FIELD => $ELEMENT_SORT_ORDER), 
    Array("nPageSize"=>$ELEMENT_COUNT), $arFilter, $arSelect))
    {
        //Инициализация постраничного вывода.
        $rsElements->NavStart($ELEMENT_COUNT);
        if($obElement = $rsElements->GetNextElement())
        {
            //Для каждого элемента:
            do
            {
                $arElement = $obElement->GetFields();
                //Ниже можно пользоваться значениями свойств.
                //Например:
                echo $arElement["PROPERTY_SOURCE"],"
    "; //и т.д. и т.п. } while ($obElement = $rsElements->GetNextElement()) } } ?>

    Инфоблоки в Документообороте

    При работе в режиме документооборота создаётся два элемента инфоблока: один «конечный» с пустым WF_PARENT_ELEMENT_ID (который мы видим в административной части), второй — «промежуточный» с WF_PARENT_ELEMENT_ID равным ID только что созданного конечного элемента. «Промежуточный» превращается в «конечный» когда в рамках документооборота он достигнет финального статуса Опубликован. Либо любого другого статуса с поднятым флагом IS_FINAL. Этот флаг нельзя выставить методами API Bitrix Framework, только правкой БД. Соответственно до этого момента записи в инфоблок заноситься будут, однако для стандартных методов API с параметрами по умолчанию они будут не доступны.

    Основным отличием «промежуточных» элементов от «конечных» является поле WF_PARENT_ELEMENT_ID, которое пусто для «конечных» элементов и содержит идентификатор «конечного» элемента для промежуточных элементов. В случае создания нового элемента в документообороте (при условии, что начальный статус документооборота не является финальным) будет создан элемент, в поле WF_PARENT_ELEMENT_ID будет записан его собственный идентификатор. При последующем переводе элемента в любой другой статус документооборота, в том числе и в финальный, в инфоблоке будут создаваться новые элементы с полем WF_PARENT_ELEMENT_ID.

    При переводе элемента в финальный статус элемент, с которого всё началось, будет обновлён так, что поле WF_PARENT_ELEMENT_ID станет пустым, а поле WF_STATUS_ID станет равным значению финального статуса (зачастую это 1). При последующем переводе элемента в какой-либо промежуточный статус круг повторится с той небольшой разницей, что в качестве отправной точки будет использоваться текущий опубликованный элемент.

    По умолчанию API позволяет работать только с опубликованными элементами. Причём если перевести опубликованный элемент в какой-либо другой статус, то API будет возвращать именно ту версию элемента, которая соответствует опубликованной.

    Чтобы получить список всех элементов (в том числе и не опубликованных) у метода CIBlockElement::GetList в свойствах фильтра необходимо задать параметр "SHOW_HISTORY" => "Y".

    Кроме того, существует возможность: зная ID элемента получить его последнюю версию в документообороте с помощью функции CIBlockElement::WF_GetLast, и наоборот — зная последнюю версию элемента получить его оригинальный ID с помощью функции CIBlockElement::GetRealElement.

    Отдельно стоит упомянуть обработчики событий, в том числе OnAfterIBlockElementUpdate. Так как в случае работы с документооборотом непосредственный Update происходит только в случае перевода элемента в финальный статус, не следует ожидать вызова обработчиков этого события при переводе элемента в «промежуточные» статусы. Вместо этого стоит добавить обработчик на событие OnAfterIBlockElementAdd и ориентироваться на поля "WF" = "Y" (признак того, что элемент участвует в документообороте) и WF_PARENT_ELEMENT_ID (идентификатор «конечного» элемента).

    Выбрать всю историю изменений элемента можно с помощью функции CIBlockElement::WF_GetHistoryList. Для получения детальной информации о промежуточных элементах полученных с помощью этой функции рекомендуется использовать функции CIBlockElement::GetByID и CIBlockElement::GetProperty.

    Фильтрация

    Фильтрация

    Цитатник веб-разработчиков.

    Nikolay Ryzhonin: Все-таки более правильный и эффективный путь — это анализ с explain медленных запросов и анализ кода, их вызвавшего. А уже по результатам — добавление необходимых индексов или изменение кода приложения. Так как не всегда медленные запросы — следствие проблем в API, часто встречаются случаи его неправильного использования.

    Кеширование иногда — это зло. Если кешировать каталог с многовариативным фильтром при большой посещаемости, то генерация кеша может превышать 200 мегабайт в минуту. Любая квота на диске заполнится достаточно быстро. Возникают и проблемы с очисткой этого кеша. Отключение такого кеша снизит количество операций на запись.

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

    Один из часто используемых индексов — индекс по списочному свойству. Это индекс для простых инфоблоков. b_iblock_element_property — в этой таблице хранятся значения свойств. Её и индексируем: значение свойства, его ID, ID элемента. Создание такого индекса при фильтре по списковому свойству фактически исключает обращение MySQL к указанной таблице, так как все имеющиеся в запросе поля присутствуют в индексе.

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

    Совет от веб-разработчиков.

    Антон Долганин: Если требуется обновить большое количество элементов, то имеет смысл обновление производить в обход индексации поиском:

    $el->Update($arEls['ID'], array('PREVIEW_TEXT' => $preview, 'DETAIL_TEXT' => $detail), false, false);

    4-й параметр в false. А уже после обработки запустить переиндексацию инфоблоков сайта. Запись в модуль поиска увеличивает конечное время исполнения в десятки-сотни раз.

    Типы фильтрации

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

    Типы:

    • (пустой) — для строковых полей означает поиск по маске (в маске: «%» — произвольное число любых символов, «_» — один произвольный символ), для полей с нестроковыми типами поиск «равно».
    <?
    // найти элементы у которых название начинается на "#"
    $res = CIBlockElement::GetList(Array(), Array("NAME"=>"#%"));
    
    // найти элементы с идентификатором 100
    $res = CIBlockElement::GetList(Array(), Array("ID"=>"100"));
    ?>
    • «!» — для строк выражение не попадающее под маску, или не равно (для остальных типов полей).
    <?
    // найти элементы у которых название не начинается на "#"
    $res = CIBlockElement::GetList(Array(), Array("!NAME"=>"#%"));
    ?>
    • «?» — с применением логики, работает только для строковых свойств.
    <?
    // найти элементы у которых название содержит "One" или "Two"
    $res = CIBlockElement::GetList(Array(), Array("?NAME"=>"One | Two"));
    ?>
    • «<» — меньше;
    • «<=» — меньше либо равно;
    • «>» — больше;
    • «>=» — больше либо равно.
    <?
    // найти элементы у которых название начинается на "A"
    $res = CIBlockElement::GetList(Array(), Array(">=NAME"=>"A", "<NAME"=>"B"));
    
    // найти элементы с идентификатором большим 100
    $res = CIBlockElement::GetList(Array(), Array(">ID"=>"100"));
    ?>
    • «=» — равно;
    • «!=» — не равно.
    <?
    // найти элементы у которых название равно "ELEMENT%1"
    $res = CIBlockElement::GetList(Array(), Array("=NAME"=>"ELEMENT%1"));
    ?>
    • «%» — подстрока;
    • «!%» — не подстрока.
    <?
    // найти элементы у которых в названии есть символ процента "%"
    $res = CIBlockElement::GetList(Array(), Array("%NAME"=>"%"));
    ?>
    • «><» — между;
    • «!><» — не между.

    В качестве аргумента данные типы фильтров принимают массив вида Array("значение ОТ", "значение ДО").

    Указанные операторы отображаются в sql BETWEEN, то есть граничные значения попадают в результат, используется выражение (min <= expr AND expr <= max).

    <?
    // найти элементы у которых название начинается между "A" и "B" или между "D" и "E"
    $res = CIBlockElement::GetList(Array(), Array("><NAME"=>Array(Array("A", "B"), Array("D", "E"))));
    
    // найти элементы, у которых дата начала активности не в периоде 2003 года
    $res = CIBlockElement::GetList(Array(),
                                   Array("!><DATE_ACTIVE_FROM"=>
                                         Array(date($DB->DateFormatToPHP(CLang::GetDateFormat("FULL")), 
                                                    mktime(0,0,0,1,1,2003)),
                                               date($DB->DateFormatToPHP(CLang::GetDateFormat("FULL")), 
                                                    mktime(0,0,0,1,1,2004)-1))));
    ?>

    Примеры

    Несколько частных случаев фильтрации

    $arFilter = array("PROPERTY_CML2_SCAN_CODE"=>false ) - используется, чтобы выбрать все элементы с незаполненным свойством; 
    $arFilter = array("PROPERTY_CML2_SCAN_CODE"=>"" ) - используется, чтобы выбрать все элементы;  
    $arFilter = array("PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - при фильтрации элементов проверяется точное совпадение свойства с заданной строкой; 
    $arFilter = array("?PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - при фильтрации элементов проверяется наличие заданной подстроки в свойстве. 
    $arFilter = array("!PROPERTY_CML2_SCAN_CODE"=>false ) - используется, чтобы выбрать только элементы с заполненным свойством; 
    $arFilter = array("!PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - при фильтрации элементов проверяется отсутствие точного совпадения с заданной строкой ; 
    $arFilter = array("!?PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - при фильтрации элементов проверяется отсутствие заданной подстроки в свойстве. 
    

    Настройка фильтрации для отображения связанных элементов

    Задача: допустим, что есть 2 инфоблока, связанные между собой по одному из свойств. Как настроить фильтрацию, чтобы среди элементов инфоблока показывались только связанные?

    Решение:

    <? 
      $arrFilter = array(); 
      $arrFilter['!PROPERTY_<код свойства>'] = false; 
    ?>

    Настройка фильтрации по свойству типа «Дата/время»

    Свойство типа «Дата/время» хранится как строковое в формате YYYY-MM-DD HH:MI:SS, поэтому значение для фильтрации формируется следующим образом:

    $cat_filter[">"."PROPERTY_available"] = date("Y-m-d");

    Список ссылок по теме.

    • Основные функции, в которых можно использовать описанную фильтрацию:
      • CIBlockElement::GetList
      • CIBlockSection::GetList
      • CIBlockSection::GetMixedList
    • Типы фильтров

    Фильтрация элементов инфоблока без компонента фильтра

    Если для публикации информационного блока используются простые компоненты, то можно сделать фильтрацию элементов без использования компонента Фильтр и без кастомизации компонента, с помощью которого выводится список элементов. Такая фильтрация основана на использовании параметра Имя массива со значениями фильтра для фильтрации (FILTER_NAME) и доступна в следующих компонентах: bitrix:catalog.section, bitrix:catalog.sections.top и bitrix:news.list.

    Массив фильтра можно определить прямо на странице, где подключается компонент, перед его подключением. Однако, в таком случае, придется создавать несколько страниц, на каждой помещать компонент и т.д. Можно сделать гораздо проще — передавать переменные массива фильтра в ссылке.

    В нашем примере будет происходить переход с другой страницы, переменные для фильтрации можно передать в ссылке методом GET, а на странице определять фильтр $arrFilter из массива $_GET. Публикацию элементов инфоблока будем выполнять с помощью компонента Элементы раздела (bitrix:catalog.section).

    Допустим, что у нас имеется инфоблок Книги, фильтровать элементы которого мы будем по свойству Год выпуска (YEAR):

    Создадим стартовую страницу с набором ссылок (в нашем случае со списком годов выпуска книг):

    Код страницы будет следующим:

    <?
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
    $APPLICATION->SetTitle("Поиск книги по году выпуска");
    ?>
    <p>Выберите год:</p>
    
    <ul>
    <li><a href="/content/filter/section.php?SECTION_ID=10&YEAR=2000">2000</a></li>
    <li><a href="/content/filter/section.php?SECTION_ID=10&YEAR=2001">2001</a></li>
    <li><a href="/content/filter/section.php?SECTION_ID=10&YEAR=2002">2002</a></li>
    <li><a href="/content/filter/section.php?SECTION_ID=10&YEAR=2003">2003</a></li>
    <li><a href="/content/filter/section.php?SECTION_ID=10&YEAR=2004">2004</a></li>
    <li><a href="/content/filter/section.php?SECTION_ID=10&YEAR=2005">2005</a></li>
    </ul>
    
    <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>
    

    Теперь создаем страницу section.php и размещаем на ней компонент (bitrix:catalog.section), в настройках которого задаем необходимый инфоблок, а также заполняем поле Имя массива со значениями фильтра для фильтрации элементов значением arrFilter.

    Перед подключением компонента добавляем следующий код:

    $year = intval($_GET["YEAR"]); 
    if ($year >= 1970 && $year <= 2015) 
    { 
     $arrFilter=array("PROPERTY"=>array("YEAR"=>"$year")); 
    }
    

    В результате при переходе со стартовой страницы (например, по ссылке 2002) откроется список книг раздела с идентификатором 10, у которых выбранный год выпуска:

    Вычисляемые свойства SEO

    Начиная с версии 14.0.0, в форме редактирования инфоблока, его разделов и элементов доступна вкладка SEO. Данный функционал основан на паре следующих технологий:

    • хранение — под хранением понимается механизм наследуемых свойств (значения свойств распространяются сверху вниз по иерархии инфоблоков: от инфоблока через разделы до элемента);
    • шаблонизатор — построитель шаблона с использованием подстановок и функций.

    Рассмотрим детально каждую из этих технологий.

    Хранение

    Все шаблоны, которые наследуются для вычисляемых наследуемых свойств, хранятся в таблице b_iblock_iproperty. Таблица одна, но в ней хранятся шаблоны для трех сущностей: для элементов, для разделов и для инфоблоков.

    Шаблоны привязываются к инфоблоку, разделу или элементу через пару полей: ENTITY_TYPE и ENTITY_ID. Чтобы определить какие шаблоны для какой сущности находятся, происходит внутренний поиск, используя существующие таблицы инфоблоков. Вычисленные значения хранятся в трех разных табличках: отдельно для элементов, отдельно для разделов и отдельно для инфоблоков.

    При манипуляциях с данными таблички b_iblock_iproperty (когда мы шаблон изменяем, удаляем, добавляем) никаких вычислений не производится, выполняется только сброс вычисленных ранее дочерних значений. Операция вычисления откладывается до момента востребования (чтения) значений. В этот момент происходит поиск шаблонов снизу вверх по иерархии инфоблоков (для элемента это будут шаблоны собственно элемента, его разделов вверх до корневого и шаблоны инфоблока). Затем шаблоны вычисляются и полученные значения сохраняются в таблицы кеша, откуда и будут браться при последующих операциях чтения.

    Классы наследуемых свойств используют всю мощь ООП и принадлежат новому ядру D7. Они лежат в пространстве имён BitrixIblockInheritedProperty. Пользоваться ими достаточно просто:

    use BitrixIblockInheritedProperty; 
    
    //ООП  ElementTemplates или SectionTemplates или IblockTemplates )) 
    $ipropTemplates = new InheritedPropertyElementTemplates($IBLOCK_ID, $ELEMENT_ID);
    //Установить шаблон для элемента 
    $ipropTemplates->set(array(
             "MY_PROP_CODE" => "{=this.Name}",
             "SOME_CODE" => "", //Удалить шаблон
    ));
    //Получить шаблоны для "редактирования" 
    $templates = $ipropTemplates->findTemplates();
    //Удалить все собственные шаблоны элемента 
    $ipropTemplates->delete();
    
    //ООП  ElementValues или SectionValues или IblockValues )) 
    $ipropValues = new InheritedPropertyElementValues($IBLOCK_ID, $ELEMENT_ID);
    //Получить значения 
    $values = $ipropValues->getValues();
    echo $values [" MY_PROP_CODE "]; 
    //Сбросить кеш 
    $ipropValues->clearValues(); 
    
    • Создаем экземпляр класса в зависимости от типа сущности (для элементов это будет ElementTemplates, для разделов — SectionTemplates и для инфоблока — IblockTemplates).
    • Используем метод set для манипуляций шаблонами.

      Примечание: на текущий момент метод set используется в методах Add и Update классов CIBlock, CIBlockSection и CIBlockElement (обрабатывается поле IPROPERTY_TEMPLATES).

    • Чтобы выбирать данные, которые вычисляются в процессе выборки по заданным шаблонам, используем метод getValues (его можно встретить в компонентах инфоблоков, чтобы SEO свойства выбрать и поставить их на странице).
    • Метод clearValues позволяет сбросить закешированные значения и пересчитать.

    Шаблоны

    Шаблоны строятся независимо от механизма хранения, что позволяет использовать динамичные формы. Для построения шаблона используются следующие составляющие:

    • Во-первых, это просто текст, который вычисляется в такой же простой текст.
    • Во-вторых, это подстановки, которые начинаются внутри фигурных скобок знаком равно (например, {=this.Name}). Такой псевдообъектный синтаксис позволил реализовать экономную модель с отложенными запросами данных. В шаблоне могут использоваться следующие области: this, parent, sections, iblock, property или catalog. Поля могут быть самыми разными: name, code, previewtext, detailtext, property_CODE и т.д. (см. файлы с классами в папке /bitrix/modules/iblock/lib). Количество запросов к БД напрямую зависит от количества областей, использованных в шаблоне.
    • В-третьих, это функции (например, {=concat " " "!" iblock.name sections.name this.name}). Есть набор встроенных функций (upper, lower, translit, concat, limit, contrast, min, max и distinct) и событие OnTemplateGetFunctionClass, которое позволяет написать собственную функцию (см. пример функции).

    Шаблоны могут иметь модификаторы: приведение к нижнему регистру (/l) и транслитерация (/t-). В интерфейсе вкладки SEO они представлены отдельными чекбоксами.

    Кроме того, все шаблоны поддерживают вложенность. Например:

    //Для элемента берутся анонсовый и детальный тексты его раздела, соединяются вместе, затем выбираются 
    //первые 50 слов. После чего они соединяются с первыми 50 словами текста анонса элемента. 
    //Из них выбирается 20 самых контрастных и все они приводятся к нижнему регистру.
    
    
    {=lower {=contrast 20 " .,?!" {=limit 50 " .,?!" this.previewtext} {=limit 50 " .,?!" parent.previewtext parent.detailtext}}}
    

    Рассмотрим пример кода шаблона:

    use BitrixIblockTemplate;
    //Подключение модуля инфоблоков.
    if (BitrixMainLoader::includeModule('iblock'))
    {
          //Задаём шаблон.
          $template = "Name: {=this.Name}. Code:{=this.code}";
          //Исходные данные будем брать из элемента.
          $entity = new TemplateEntityElement($ELEMENT_ID);
          //Не забываем про безопасность.
          echo BitrixMainTextHtmlFilter::encode(
                  //Вычисляем значение по шаблону.
                  TemplateEngine::process($entity, $template) 
         );
    }
    

    Должен быть создан объект entity. Парсинг и вычисление шаблона обернуто статическим методом process, в который передаются entity и template. Кроме того, метод process можно использовать в цикле с одним entity по разным template, причем данные будут «переиспользованы», т.е. лишних запросов не будет. Также обратите внимание на метод htmlEncode, который используется для формирования безопасного html.

    Фасетный поиск

    Умный фильтр

    Компонент Умный фильтр доступен в продукте, начиная с версии 12.0 модуля Информационные блоки. При всех своих достоинствах он обладал существенным недостатком — в каталогах больших размеров фильтрация элементов занимала продолжительное время. Для решения этой проблемы в версии 15.0 была добавлена технология фасетного поиска.

    Фасетная классификация – это совокупность нескольких независимых классификаций, осуществляемых одновременно по различным основаниям. Применительно к информационным блокам термин фасета означает фильтрацию по свойствам и ценам.

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

    Use BitrixIblockPropertyIndex;
    PropertyIndexManager::updateElementIndex($iblockId, $elementId);
    PropertyIndexManager::deleteElementIndex($iblockId, $elementId);
    PropertyIndexManager::markAsInvalid($iblockId);
    

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

    CRE ATE   TABLE b_iblock_X_index (
      SECTION_ID INT NOT NULL,
      ELEMENT_ID INT NOT NULL,
      FACET_ID   INT NOT NULL,
      VALUE      INT NOT NULL,
      VALUE_NUM  FLOAT NOT NULL,
      INCLUDE_SUBSECTIONS VARCHAR(1) NOT NULL,
      KEY IX_b_iblock_X_index_0 (SECTION_ID,ELEMENT_ID,FACET_ID, VALUE_NUM, VALUE, ELEMENT_ID),
      KEY IX_b_iblock_X_index_1 (ELEMENT_ID, SECTION_ID, FACET_ID)
     );
    CRE ATE   TABLE b_iblock_X_index_val (
      ID    INT NOT NULL AUTO_INCREMENT,
      VALUE VARCHAR(2000),
      PRIMARY KEY (ID)
    );
    

    Рассмотрим детальнее первую таблицу. Для этого в административном разделе сайта перейдите на страницу Настройки > Производительность > Таблицы и выберите таблицу b_iblock_X_index (где Х — идентификатор вашего каталога). Данные рассмотрим на примере конкретного элемента каталога (в нашем случае для элемента с идентификатором 36):

    По колонке SECTION_ID можно видеть, что данные сохранены не только для раздела элемента (в колонке INCLUDE_SUBSECTIONS стоит 1), но и всех его родителей (в колонке INCLUDE_SUBSECTIONS стоит 0).

    FACET_ID — это синтетический идентификатор цен и свойств. Для цен используются нечётные идентификаторы (идентификатор цены умножается на 2 и прибавляется 1), а для свойств — четные (идентификатор свойства умножается на 2).
    Именно этот прием позволяет строить комбинированные фильтры по цене и значениям свойств.

    Примечание: подробную информацию о работе с таблицами базы данных смотрите в главе Монитор производительности.

    Производительность

    Производительность умного фильтра на каталоге среднего размера.

    Рассмотрим работу умного фильтра на каталоге среднего размера: 20 тысяч товаров, 60 тысяч предложений и 780 тысяч значений свойств. Таким образом, каталог с товарами имеет следующие характеристики:

    • таблица b_iblock_element содержит 80 тысяч записей, 20 Мбайт занимают данные и 8 Мб — индексы, средняя длина строки — 265 байт;
    • таблица b_iblock_element_property содержит 780 тысяч записей, 35 Мбайт занимают данные, индексы — 70 Мб, средняя длина строки — 44 байта.

    После того, как для каталога будут созданы фасетные индексы, таблица b_iblock_X_index будет содержать 520 тыс. записей, данные будут занимать 12 Мбайт, индексы — 30 Мб, средняя длина строки — 24 байта.

    На следующей диаграмме представлено время работы компонента умного фильтра без фильтрации на одном из корневых разделов каталога с 10 тысячами элементов, подразделе с 3 тысячами товаров и подразделе подраздела со 100 товарами:

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

    Если посмотреть показатели с установленным фильтром по цене, то видим, что использование фасетного индекса также эффективно:

    Хорошие показатели наблюдаются и при различных вариантах фильтрации каталога по цене и свойствам (например, по свойству Цвет):

    Кроме того, использование фасетных индексов позволяет снизить и стабилизировать расход памяти PHP:

    Это связано с тем, что вся работа по группировке товаров ложится на базу данных. Часть работы выполняется на этапе индексации, а часть — при выборке, опираясь на мощную поддержку в виде фасетного индекса. Поэтому на долю PHP остается малая часть, которая выполняется быстро и эффективно.

    План действий при проблемах

    При возникновении проблем в работе проекта для их устранения рекомендуем воспользоваться следующим алгоритмом:

    • Ставьте перед собой конкретную цель. Так как процесс оптимизации, улучшения – бесконечный. Например: каждая страница с каталогом ваших товаров должна открываться за определенное время, скажем 0,2 секунды. Только установка таких конкретных целей и их решение может быть эффективна, в отличие от неясных и формализованных пожеланий, «чтобы лучше работало».
    • С помощью страницы отладки (Настройки > Производительность > SQL Запросы) найти и убрать лишние запросы.

      Лишний запрос – это:

      • Запрос в цикле (вынести из цикла в переменную).
      • Запросы, которые добирают данные в цикле. (Лучше собрать данные в фильтре, а потом одним запросом вывести данные, потом дополнительный цикл – разложить данные) в этом случае запросы не зависят линейно от количества элементов.
    • Убрать из запросов неиспользуемые данные ($arSelect). Точно указывать поля, которые впоследствии будут использоваться. Это в разы повышает производительность, потому что такое четкое указание используемых данных означает для базы данных сокращение объемов сортировки. База данных производит такую сортировку не на жестком диске, а в оперативной памяти.
    • Оценить возможность использования PROPERTY_* в сочетании с инфоблоками 2.0. Инфоблоки 2.0 хранят свои свойства в отдельной таблице. Эта таблица при упоминании PROPERTY_* присоединяется на этапе выборки. И до выборки значения свойств присоединение этих свойств не происходит.

      Когда это не применимо? Например, при малой выборке из большого числа элементов (10 новостей из нескольких тысяч записей). Момент этот не очень однозначный, и малозависим от разработчика. Причина в том, что результат выборки из инфоблоков и инфоблоков 2.0 – разный. В простом инфоблоке при выборке записи начинают размножаться. А инфоблоки 2.0 возвращают массив значения свойства. Если код в шаблоне не предусматривает эту ситуацию, то смена обычных инфоблоков на инфоблоки 2.0 приведет только к тому, что шаблон «развалится».

    • Посмотреть план исполнения самых тяжелых запросов и добавить/удалить индексы.

    Список ссылок по теме:

    • Если долго сохраняется элемент в административном разделе.

    Highload-блоки

    Поскольку модуль Информационные блоки считается очень «тяжелым» для создания легких справочников или хранения большого количества данных (когда модуль может вести себя не очень оптимально), то появилась потребность в создании аналога инфоблоков (модуль Highload-блоки), но гораздо проще и «полегче». Новый модуль доступен с версии 14.0 продукта и написан на новом ядре D7. Структура данных этого модуля предполагает возможность использования в нагруженных проектах.

    Примечание: Highload-блоки и традиционные инфоблоки — это разные вещи. Поэтому нет возможности конвертации традиционных инфоблоков в Highload-блоки. Это все равно как конвертировать форум в wiki. Технически можно создать какую-то аналогичную структуру и перенести данные. Но это имеет смысл только для конкретного проекта при определенной необходимости.

    Highload-блоки

    Highload-блоки — это быстрые справочники, без поддержки иерархии, с ограниченной поддержкой свойств. Они могут обращаться к БД в том числе и через HandlerSocket и работать с большими объёмами данных. Highload-блоки хранят элементы в своих таблицах и используют свои индексы.

    Примечание: Кроме модуля Highload-блоки, в ядре D7 реализована возможность обращения к БД через HandlerSocket для всех сущностей ORM.

    Раньше данные хранились с использованием модуля, который закрывал таблицы — паттерн Table Module. В Highload-блоках дополнительно создан слой, который называется Table Data Gateway, который поддерживает всю технику работы с таблицами, а бизнес-логика находится в расширяющем классе.

    Highload-блоки — это логика для работы с данными, не более. Для конкретного применения данных нужно предусмотреть реализацию бизнес-логики самим приложением. Чтобы реализовать эту бизнес-логику надо расширить класс Highloadblock:

    use BitrixHighloadblock as HL;
    $hlblock   = HLHighloadBlockTable::getById( # )->fetch();
    $entity   = HLHighloadBlockTable::compileEntity( $hlblock ); //генерация класса 
    $entityClass = $entity->getDataClass();
    
    class MyDomainObjectTable extends #entityClass# {
    …//наша бизнес логика проекта, посмотрите содержимое $entityClass и впишите его в #entityClass#
    } 
    

    Преимущества Highload-блоков

    • Низкие накладные расходы (PHP, меньше запросов SQL).
    • Низкий риск блокировок в БД: так как данные хранятся в своих таблицах, нет единой, глобальной таблицы, которая может заблокироваться при больших нагрузках (выборка и одновременно импорт).
    • Тысячи, миллионы сущностей, справочники.
    • Снижение нагрузки на БД, хостинг.

    О производительности и ресурсах

    Highloadblock — это прослойка между пользователем и ORM. ООП само по себе ведет к бо́льшему потреблению памяти и CPU, предлагая взамен более удобную и эффективную разработку. Highloadblock предназначены для высоких нагрузок. Их преимущества лежат в области архитектуры, то есть на них проще достичь масштабируемости, проще управлять разработкой, проще учитывать риски.

    Выигрыш будет именно при работе с БД. Разработчик получает:

    • отдельные таблицы для сущностей (что может стать заметным на больших объемах),
    • возможность легко расставлять индексы для нужных вам выборок,
    • возможность прозрачно использовать handlersocket, который может заметно снизить нагрузку на СУБД.

    Архитектура модуля

    Описание

    В отличие от модуля Информационные блоки в модуле Highload-блоки предусматривается хранение каждой сущности в своей таблице, то есть формально не принадлежат какому-либо модулю. Поля сущностей — это [ds]пользовательские свойства ядра[/ds][di]Необходимо отличать Пользовательские поля в модулях системы и свойства используемые в рамках инфоблоков , хотя в формах системы (форма создания/редактирования пользователя, форме создания/редактирования раздела инфоблока и другие) используется термин пользовательские свойства.

    Подробнее …[/di]. Сущности конструируются в административном интерфейсе, то есть без дополнительного программирования.

    Доступ к данным предоставляется на основе ORM.

    Разнесение данных может быть не только по таблицам одной базы данных, но в принципе оно может быть и по разным базам данных. Таким образом, становится возможным горизонтальное масштабирование проекта, когда одни объекты живут на одном сервере, а другие — на другом.

    В модуле Highload-блоки добавлена поддержка концепции NoSQL на основе базы данных MySQL. Соединение с базой данных предусмотрено как с помощью расширения handlersocket для MySQL, так и без него. Если используется handlersocket, то необходимо произвести дополнительные настройки.

    Допустим, что в проекте сконфигурирована поддержка NoSQL, стоят все необходимые драйверы к MySQL и в конфигурации ядра указано соединение через эти драйверы. В результате модуль Highload-блоки позволяет создавать сущности и строить проект так, чтобы очень быстро осуществлялась выборка из базы через драйвер по первичному ключу.

    Чтобы воспользоваться ORM-сущностью для Highload-блоков, необходимо выполнить следующие действия:

    • Инициализировать сущность в системе:
      //сначала выбрать информацию о ней из базы данных
      $hldata = BitrixHighloadblockHighloadBlockTable::getById($ID)->fetch();
      
      //затем инициализировать класс сущности
      $hlentity = BitrixHighloadblockHighloadBlockTable::compileEntity($hldata);
      
    • Дальше можно создавать запросы с данной сущностью:
      $query = new EntityQuery($hlentity);
      

      или использовать ее через интерфейс DataManager:

      $hlDataClass = $hldata['NAME'].'Table';
      $hlDataClass::getList();
      

    Вся дальнейшая работа с Highload-блоками подчиняется законам ORM, поскольку созданная в модуле Highload-блоки сущность является ORM-сущностью.

    Модуль включает в себя два компонента: список записей сущностей (highloadblock.list) и детальный просмотр сущности (highloadblock.view).

    Привязка справочника

    Привязка справочника к Highload-блоку осуществляется через свойства USER_TYPE_SETTINGS. В них указывается имя таблицы.

    Привязка значений свойств идет по UF_XML_ID (поле должно быть обязательно заполнено), иначе значения свойства «справочник» не сохраняются у элемента.

    Highloadblock и handlersocket

    Традиционные ACID Базы данных в целом ряде задач затрудняют реализацию проектов. Для решения этих задач были предложены технологии NoSQL и HandlerSocket (в виде плагина к обычной MySQL).

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

    Примерное сравнение числа запросов, допускаемых разными способами работы с БД:

    Способ доступа Число запросов в секунду Нагрузка на CPU
    MySQL через SQL-клиент 105 000 Пользовательские процессы: 60%
    Системные процессы: 28%
    memcached 420 000 Пользовательские процессы: 8%
    Системные процессы: 88%
    MySQL через HandlerSocket-клиента 750 000 Пользовательские процессы: 45%
    Системные процессы: 53%

    Как работает memcache мы уже описывали. Разница между обращением через MySQL-клиент и через HandlerSocket заключается в том, что во втором случае минуется парсинг, открытие таблиц, оптимизация планов исполнения. То есть обращение происходит напрямую. Нагрузка на MySQL резко снижается. Запросы не выполняются быстрее, они меньше нагружают сервер.

    Подключение HandlerSocket задаётся в файле настроек параметров ядра.

    Установить сам плагин можно либо скачав исходники MySQL и собрав плагин, либо установить [dw]PerconaServer[/dw][di]С версии Percona 5.7 этот функционал более не доступен.[/di] или MariaDB, в которых он включён по умолчанию.

    При обращении к Highloadblock ($obj = $entityClass::getById( $arData["ID"] )->fetch();) через HandlerSocket происходит вызов HS API (обращение open_index и find в MySQL) и обработка результата вызова приложением.

    HandlerSocket запускает внутри БД пул потоков работающих в асинхронном режиме. (Аналогично работает NGINX.) Если обычно MySQL для каждого клиентского соединения поднимает один поток (thread) и работает внутри его, то в случае с HandlerSocket вводится пул потоков c использованием мультиплексирующих системных вызовов pool/select, поэтому, например, 5 потоков могут обработать сотни тысяч запросов.

    Примеры работы с Highload-блоками

    <?
    //Подготовка:
    if (CModule::IncludeModule('highloadblock')) {
       $arHLBlock = BitrixHighloadblockHighloadBlockTable::getById(1)->fetch();
       $obEntity = BitrixHighloadblockHighloadBlockTable::compileEntity($arHLBlock);
       $strEntityDataClass = $obEntity->getDataClass();
    }
    
    //Добавление:
    if (CModule::IncludeModule('highloadblock')) {
       $arElementFields = array(
          'UF_NAME' => $arPost['name'],
          'UF_MESSAGE' => $arPost['message'],
          'UF_DATETIME' => new BitrixMainTypeDateTime
       );
       $obResult = $strEntityDataClass::add($arElementFields);
       $ID = $obResult->getID();
       $bSuccess = $obResult->isSuccess();
    }
    
    //Получение списка:
    if (CModule::IncludeModule('highloadblock')) {
       $rsData = $strEntityDataClass::getList(array(
          'select' => array('ID','UF_NAME','UF_MESSAGE','UF_DATETIME'),
          'order' => array('ID' => 'ASC'),
          'limit' => '50',
       ));
       while ($arItem = $rsData->Fetch()) {
          $arItems[] = $arItem;
       }
    }
    ?>

    Выбор случайного значения:

    $q = new EntityQuery($entity);
           $q->setSelect(array('*'));
           $q->setFilter($arFilter);
           $q->setLimit(1);
           $q->registerRuntimeField(
               'RAND', array('data_type' => 'float', 'expression' => array('RAND()'))
           );
           $q->addOrder("RAND", "ASC");
           $result = $q->exec();
    

    Практика. Работа с элементами, разделами и свойствами

    Примеры решения различных задач, возникающих при работе с элементами, разделами и свойствами инфоблоков.

    Работа с пользовательскими свойствами инфоблоков

    Примеры решения задач, возникающих при работе с элементами, разделами и свойствами инфоблоков.

  • Получить значения всех свойств элемента, зная его ID
  • Получить свойства элементов, используя метод CIBlockElement::GetList
  • Добавить свойство типа TEXT/html для элемента
  • Заполнить множественное свойство типа Файл
  • Заполнить множественное свойство типа Список
  • Получить пользовательское свойство раздела
  • Пример создания своего типа данных для пользовательского свойства
  • Как удалить файл в свойстве элемента инфоблока
  • Задача 1:
    Получить значения всех свойств элемента, зная его ID.

    1	<? $db_props = CIBlockElement::GetProperty(IBLOCK_ID, ELEMENT_ID, "sort", "asc", array());
    2	$PROPS = array();
    3	while($ar_props = $db_props->GetNext())
    4	$PROPS[$ar_props['CODE']] = $ar_props['VALUE'];?>

    Теперь символьный код свойства является ключом ассоциативного массива $PROPS, то есть, если вам нужно значение свойства с кодом price, то оно будет храниться в $PROPS['price'].

    Задача 2:
    Получить свойства элементов, используя метод CIBlockElement::GetList

    1	<? $arSelect = array("ID", "NAME", "PROPERTY_prop_code_1", "PROPERTY_prop_code_2");
    2	$res = CIBlockElement::GetList(array(), array(), false, array(), $arSelect);?>

    Дальше использовать цикл и получить свойства с символьными кодами prop_code_1 и prop_code_2.

    Советы веб-разработчиков.

    Антон Долганин: Если для какого-либо изменения в БД предусмотрен специальный метод, следует использовать именно его, а не более общий метод изменения БД.

    Хороший пример: модуль интернет-магазина и работа с заказом. Можно изменить флаг оплаты заказа путем CSaleOrder::Update, а можно путем CSaleOrder::PayOrder. Так вот, следует применять именно PayOrder, потому что в нем произойдет вызов соответствующих обработчиков.

    Задача 3:

    Добавить свойство типа TEXT/html для элемента.

    Если свойство не множественное:

    01	<? $element = new CIBlockElement;
    02	$PROP = array();
    03	$PROP['символьный код свойства']['VALUE']['TYPE'] = 'text'; // или html
    04	$PROP['символьный код свойства']['VALUE']['TEXT'] = 'значение, которое нужно забить';
    05	$arLoadArray = array(
    06	  "IBLOCK_ID"      => IBLOCK_ID,
    07	  "PROPERTY_VALUES"=> $PROP,
    08	  "NAME"           => "Название элемента"
    09	  );
    10	$element->Add($arLoadArray);?>

    Если свойство множественное:

    01	<? // В $ITEMS хранятся значения множественного свойства, которое нужно забить
    02	foreach($ITEMS as $item)
    03	{
    04	    $VALUES[]['VALUE'] = array(
    05	     'TYPE' => 'text', // или html
    06           'TEXT' => $item,
    07           );
    08	}
    09	$element = new CIBlockElement;
    10	$PROPS = array();
    11	$PROPS['символьный код свойства'] = $VALUES;
    12	$arLoadArray = array(
    13	  "IBLOCK_ID"      => IBLOCK_ID,
    14	  "PROPERTY_VALUES"=> $PROPS,
    15	  "NAME"           => "Название элемента"
    16	  );
    17	$element->Add($arLoadArray);?>

    Задача 4:

    Заполнить множественное свойство типа Файл. Довольно часто при добавлении элемента в инфоблок может понадобиться привязать к нему несколько файлов. Для этого удобно создать у инфоблока множественное свойство типа Файл и хранить файлы в нём. Пример заполнения свойства:

    01	<?
    02	$arFiles = array();
    03	for($i = 1; $i < 10; $i++)
    04	{
    05	    if(file_exists($_SERVER['DOCUMENT_ROOT'].'/images/image_'.$i.'.jpg'))
    06	    {
    07	        $arFiles[] = array('VALUE' => CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"].'/images/image_'.$i.'.jpg'), 'DESCRIPTION' => '');
    08	    }
    09	}
    10	?>

    После этого массив $arFiles передается как значение свойства при добавлении элемента.

    Задача 5:

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

    1	<?
    2	if($first_condition == true) $values[] = array('VALUE' => 1);
    3	if($second_condition == true) $values[] = array('VALUE' => 2);
    4	CIBlockElement::SetPropertyValuesEx($ELEMENT_ID, $IBLOCK_ID, array('property_code' => $values));
    5	?>

    В данном случае при выполнении первого и второго условий мы отмечаем флажками элементы списка с ID =1 и ID=2 соответственно. Заменить следует $ELEMENT_ID, $IBLOCK_ID и property_code на нужные значения.

    Задача 6:

    Получить пользовательское свойство раздела

    1	<? $section_props = CIBlockSection::GetList(array(), array('IBLOCK_ID' => IBLOCK_ID, 'ID' => SECTION_ID), 
                                   true, array("UF_ADDITIONAL_PRICE"));
    2	$props_array = $section_props->GetNext(); ?>

    Теперь в $props_array['UF_ADDITIONAL_PRICE'] лежит значение свойства UF_ADDITIONAL_PRICE раздела инфоблока.

    Совет от веб-разработчиков.

    Алексей Коваленко: При работе с инфоблоками удобнее все коды свойств именовать заглавными буквами. В таком случае вы сможете избежать небольших несостыковок в своей работе.

    Например, значение свойства с кодом foto при работе с компонентами часто доступно через [PROPERTIES][foto][VALUE]?, а при использовании метода GetList вы можете получить PROPERTY_FOTO_VALUE.

    Пример создания своего типа данных для пользовательского свойства

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

    Один из вариантов реализации: хранить изображения в отдельном инфоблоке и показывать как привязку к элементу. Пример кода:

    AddEventHandler("iblock", "OnIBlockPropertyBuildList", array("CIBlockPropertyPicture", "GetUserTypeDescription"));
    AddEventHandler("iblock", "OnBeforeIBlockElementDelete", array("CIBlockPropertyPicture", "OnBeforeIBlockElementDelete"));
    class CIBlockPropertyPicture
    {
       function GetUserTypeDescription()
       {
          return array(
             "PROPERTY_TYPE"      =>"E",
             "USER_TYPE"      =>"Picture",
             "DESCRIPTION"      =>"Картинка",
             "GetPropertyFieldHtml" =>array("CIBlockPropertyPicture", "GetPropertyFieldHtml"),
             "GetPublicViewHTML" =>array("CIBlockPropertyPicture", "GetPublicViewHTML"),
             "ConvertToDB" =>array("CIBlockPropertyPicture", "ConvertToDB"),
    
             //"GetPublicEditHTML" =>array("CIBlockPropertyPicture","GetPublicEditHTML"),
             //"GetAdminListViewHTML" =>array("CIBlockPropertyPicture","GetAdminListViewHTML"),
             //"CheckFields" =>array("CIBlockPropertyPicture","CheckFields"),
             //"ConvertFromDB" =>array("CIBlockPropertyPicture","ConvertFromDB"),
             //"GetLength" =>array("CIBlockPropertyPicture","GetLength"),
          );
       }
    
       function GetPropertyFieldHtml($arProperty, $value, $strHTMLControlName)
       {
          $LINK_IBLOCK_ID = intval($arProperty["LINK_IBLOCK_ID"]);
          if($LINK_IBLOCK_ID)
          {
             $ELEMENT_ID = intval($value["VALUE"]);
             if($ELEMENT_ID)
             {
                $rsElement = CIBlockElement::GetList(array(), array("IBLOCK_ID" => $arProperty["LINK_IBLOCK_ID"],
                              "ID" => $value["VALUE"]), false, false, array("ID", "PREVIEW_PICTURE", "DETAIL_PICTURE"));
                $arElement = $rsElement->Fetch();
                if(is_array($arElement))
                   $file_id = $arElement["DETAIL_PICTURE"];
                else
                   $file_id = 0;
             }
             else
             {
                $file_id = 0;
             }
    
             if($file_id)
             {
                $db_img = CFile::GetByID($file_id);
                $db_img_arr = $db_img->Fetch();
                if($db_img_arr)
                {
                   $strImageStorePath = COption::GetOptionString("main", "upload_dir", "upload");
                   $sImagePath = "/".$strImageStorePath."/".$db_img_arr["SUBDIR"]."/".$db_img_arr["FILE_NAME"];
                   return '<label><input name="'.$strHTMLControlName["VALUE"].'[del]" value="Y" type="checkbox"> 
                            Удалить файл '.$sImagePath.'</label>'
                   .'<input name="'.$strHTMLControlName["VALUE"].'[old]" value="'.$ELEMENT_ID.'" type="hidden">';
                }
             }
             return '<input type="file" size="'.$arProperty["COL_COUNT"].'" name="'.$strHTMLControlName["VALUE"].'"/>';
          }
          else
          {
             return "Ошибка настройки свойства. Укажите инфоблок в котором будут храниться картинки.";
          }
       }
    
       function GetPublicViewHTML($arProperty, $value, $strHTMLControlName)
       {
          $LINK_IBLOCK_ID = intval($arProperty["LINK_IBLOCK_ID"]);
          if($LINK_IBLOCK_ID)
          {
             $ELEMENT_ID = intval($value["VALUE"]);
             if($ELEMENT_ID)
             {
                $rsElement = CIBlockElement::GetList(array(), array("IBLOCK_ID" => $arProperty["LINK_IBLOCK_ID"], 
                              "ID" => $value["VALUE"]), false, false, array("ID", "PREVIEW_PICTURE", "DETAIL_PICTURE"));
                $arElement = $rsElement->Fetch();
                if(is_array($arElement))
                   return CFile::Show2Images($arElement["PREVIEW_PICTURE"], $arElement["DETAIL_PICTURE"]);
             }
          }
          return "";
       }
    
       function ConvertToDB($arProperty, $value)
       {
          $arResult = array("VALUE" => "", "DESCRIPTION" => "");
          $LINK_IBLOCK_ID = intval($arProperty["LINK_IBLOCK_ID"]);
          if($LINK_IBLOCK_ID)
          {
             if(
                is_array($value["VALUE"])
                && is_array($value["VALUE"]["error"])
                && $value["VALUE"]["error"]["VALUE"] == 0
                && $value["VALUE"]["size"]["VALUE"] > 0
             )
             {
                $arDetailPicture =  array(
                   "name" => $value["VALUE"]["name"]["VALUE"],
                   "type" => $value["VALUE"]["type"]["VALUE"],
                   "tmp_name" => $value["VALUE"]["tmp_name"]["VALUE"],
                   "error" => $value["VALUE"]["error"]["VALUE"],
                   "size" => $value["VALUE"]["size"]["VALUE"],
                );
                $obElement = new CIBlockElement;
                $arResult["VALUE"] = $obElement->Add(array(
                   "IBLOCK_ID" => $LINK_IBLOCK_ID,
                   "NAME" => $arDetailPicture["name"],
                   "DETAIL_PICTURE" => $arDetailPicture,
                ), false, false, true);
             }
             elseif(
                is_array($value["VALUE"])
                && isset($value["VALUE"]["size"])
                && !is_array($value["VALUE"]["size"])
                && $value["VALUE"]["size"] > 0
             )
             {
                $arDetailPicture =  array(
                   "name" => $value["VALUE"]["name"],
                   "type" => $value["VALUE"]["type"],
                   "tmp_name" => $value["VALUE"]["tmp_name"],
                   "error" => intval($value["VALUE"]["error"]),
                   "size" => $value["VALUE"]["size"],
                );
                $obElement = new CIBlockElement;
                $arResult["VALUE"] = $obElement->Add(array(
                   "IBLOCK_ID" => $LINK_IBLOCK_ID,
                   "NAME" => $arDetailPicture["name"],
                   "DETAIL_PICTURE" => $arDetailPicture,
                ), false, false, true);
             }
             elseif($value["VALUE"]["del"])
             {
                $obElement = new CIBlockElement;
                $obElement->Delete($value["VALUE"]["old"]);
             }
             elseif($value["VALUE"]["old"])
             {
                $arResult["VALUE"] = $value["VALUE"]["old"];
             }
             elseif(!is_array($value["VALUE"]) && intval($value["VALUE"]))
             {
                $arResult["VALUE"] = $value["VALUE"];
             }
          }
          return $arResult;
       }
    
       function OnBeforeIBlockElementDelete($ELEMENT_ID)
       {
          $arProperties = array();
          $rsElement = CIBlockElement::GetList(array(), array("ID" => $ELEMENT_ID), false, false, array("ID", "IBLOCK_ID"));
          $arElement = $rsElement->Fetch();
          if($arElement)
          {
             $rsProperties = CIBlockProperty::GetList(array(), array("IBLOCK_ID" => $arElement["IBLOCK_ID"], "USER_TYPE" => "Picture"));
             while($arProperty = $rsProperties->Fetch())
                $arProperties[] = $arProperty;
          }
    
          $arElements = array();
          foreach($arProperties as $arProperty)
          {
             $rsPropValues = CIBlockElement::GetProperty($arElement["IBLOCK_ID"], $arElement["ID"], array(), array(
                "EMPTY" => "N",
                "ID" => $arProperty["ID"],
             ));
             while($arPropValue = $rsPropValues->Fetch())
             {
                $ID = intval($arPropValue["VALUE"]);
                if($ID > 0)
                   $arElements[$ID] = $ID;
             }
          }
    
          foreach($arElements as $to_delete)
          {
             CIBlockElement::Delete($to_delete);
          }
       }
    }

    Что мы в итоге имеем:

    • Интерфейс редактирования элемента с возможностью добавления и удаления изображений.
    • При удалении элемента связанная с ним информация удаляется.
    • Поддержка компонента в публичной части.

    Инструкция по применению:

    • Этот код разместите в файле /bitrix/php_interface/init.php.
    • Создайте инфоблок для хранения изображений и в его настройках укажите параметры генерации картинки предварительного просмотра из детальной (на вкладке Поля).
    • В инфоблоке Гостиницы добавьте свойство типа Картинка и в дополнительных настройках этого свойства укажите созданный на первом шаге инфоблок. Не забудьте указать символьный код свойства.
    • Создайте элемент и «поиграйтесь» со значениями этого свойства.
    • В публичной части, например в компоненте news, в параметрах настройки списка элементов выбрать это свойство.

    Как удалить файл в свойстве элемента инфоблока

    Обновить любое свойство можно с помощью методов:

    • Update
    • SetPropertyValues
    • SetPropertyValuesEx

    При использовании любого метода на ключ массива обновления идет код свойства, а значение — новое значение. Для удаления файла нам надо передать вот такой простой массив:

    array('MY_FILE' => array('XXX' => array('del' => 'Y')));

    Способ универсален и подходит что для инфоблоков, что для инфоблоков 2.0, что для документооборота. MY_FILE — это код вашего свойства типа Файл. А что такое ХХХ? В нём содержится ID значения_ свойства. То есть не ID свойства, а именно ID значения.

    CModule::IncludeModule('iblock');
    $IB = 24;
    $ID = 220304;
    $CODE = 'ONE_FL';
    if ($arProp = CIBlockElement::GetProperty($IB, $ID, 'ID', 'DESC', array('CODE' => $CODE))->fetch()) {
       $XXX = $arProp['PROPERTY_VALUE_ID'];
       CIBlockElement::SetPropertyValueCode($ID, $CODE, array($XXX => array('del' => 'Y')));
    }

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

    Что делать в случае множественного файла? Как удалить конкретный файл в списке? Все просто — используем в примере выше не if, а while, ну и дополнительно фильтруем, какой файл надо удалить.

    Примеры работы с множественными свойствами

    Задача 1: удаление одного из значений множественного свойства элемента инфоблока.

    Решение:

    $el = new CIBlockElement;
    $PROP = array();
    $PROP[property_id][id] = "4";  
    $PROP[property_id][id] = "5";
    $PROP[property_id][id] = "6";
    
    $arLoadProductArray = Array(
      "IBLOCK_ID" => $B_ID,
      "PROPERTY_VALUES" => $PROP,
      "NAME" => "Element",
      );
    
    $PRODUCT_ID = $E_ID;
    $res = $el->Update($PRODUCT_ID, $arLoadProductArray);
    

    При этом для удаления достаточно исключить из массива $PROP пару: ключ и значение удаляемого свойства.
    Данное решение является оптимальным в ситуации, когда необходимо сохранить id значения свойства прежним:

    $PROP[property_id ][id ]

    Также вариантом решения задачи может стать использование метода SetPropertyValues:

    CIBlockElement::SetPropertyValues($ELEMENT_ID, $IBLOCK_ID, $PROPERTY_VALUE, $PROPERTY_CODE);

    в четвёртый параметр функции следует передавать false, а в третий — массив «код свойства»=>»значение».

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

    Задача 2: добавление определенного значения для множественного свойства типа файл:

    Решение:

    //FILES - символьный код множественного свойства типа файл;
    
    $ELEMENT_ID = 392;
    $arFile = CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/help.gif");
    $arFile["MODULE_ID"] = "iblock";
    
    CIBlockElement::SetPropertyValueCode($ELEMENT_ID, "FILES", Array("VALUE"=>$arFile)  );
    

    Задача 3: добавление нескольких значений для множественного свойства типа файл:

    Решение:

    $arFile = array(
    0 => array("VALUE" => CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/01.gif"),"DESCRIPTION"=>""),
    1 => array("VALUE" => CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/help.gif"),"DESCRIPTION"=>"")
    );
    CIBlockElement::SetPropertyValueCode($ELEMENT_ID, $PROPERTY_CODE, $arFile);
    

    Задача 4: удаление определенного значения множественного свойства типа файл:

    Решение:

    //FILES - символьный код множественного свойства типа файл;
    //2033 - id значения свойства;
    
    $ELEMENT_ID = 392;
    $arFile["MODULE_ID"] = "iblock";
    $arFile["del"] = "Y";
    
    CIBlockElement::SetPropertyValueCode($ELEMENT_ID, "FILES", Array ("2033" => Array("VALUE"=>$arFile) ) );
    

    Задача 5: обновление определенного значения множественного свойства типа файл:

    Решение:

    //FILES - символьный код множественного свойства типа файл;
    //2033 - id значения свойства;
    
    $ELEMENT_ID = 392;
    $arFile = CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/help.gif");
    $arFile["MODULE_ID"] = "iblock";
    
    CIBlockElement::SetPropertyValueCode($ELEMENT_ID, "FILES", Array ("2033" => Array("VALUE"=>$arFile) ) );
    

    Задача 6: установка множественного свойства типа строка с полем для описания значения:

    Решение с помощью SetPropertyValueCode:

    $arValues = array(
      0 => array("VALUE"=>"значение","DESCRIPTION"=>"описание значения"),
      1 => array("VALUE"=>"значение2","DESCRIPTION"=>"описание значения2") 
    );  
    CIBlockElement::SetPropertyValueCode($ELEMENT_ID, $PROP_CODE, $arValues);  
    

    Решение с помощью SetPropertyValuesEx:

    $PROPERTY_VALUE = array(
      0 => array("VALUE"=>"значение","DESCRIPTION"=>"описание значения"),
      1 => array("VALUE"=>"значение2","DESCRIPTION"=>"описание значения2") 
    );
    CIBlockElement::SetPropertyValuesEx($ELEMENT_ID, $IBLOCK_ID, array($PROPERTY_CODE => $PROPERTY_VALUE));
    

    Задача 7: обновление множественного свойства типа Текст и сохранение при этом DESCRIPTION:

    Решение:

    CIBlockElement::SetPropertyValues($nProductID, $nIblockID, array(
                 array(
                     "VALUE" => array(
                         "TEXT"=>time(),
                         "TYPE"=>"HTML"
                      ),
                     "DESCRIPTION"=>"111"),
                 array(
                     "VALUE" => array(
                         "TEXT"=>time(),
                         "TYPE"=>"HTML"
                      ),
                     "DESCRIPTION"=>"222"),
                ), $prop['ID']);
    

    Копирование значений полей элементов в свойства

    Рассмотрим пример функции, которая копирует значения полей элементов инфоблока ($_FROM_FIELD_NAMES) в свойства элементов инфоблока ($TO_PROPERTY_NAMES).

    Копироваться будут поля Начало активности (DATE_ACTIVE_FROM) и Окончание активности (DATE_ACTIVE_TO) в свойства DATE_BEGIN и DATE_END элементов инфоблока с ID = 22:

     function copy_from_fields_to_propertys_values( $IBLOCK_ID, $_FROM_FIELD_NAMES, $TO_PROPERTY_NAMES ){
        /* *
         * $_FROM_FIELD_NAMES = array(DATE_ACTIVE_FROM, DATE_ACTIVE_TO);
         * $TO_PROPERTY_NAMES = array(DATE_BEGIN, DATE_END);
         * copy_from_fields_to_propertys_values(22, array("DATE_ACTIVE_FROM","DATE_ACTIVE_TO"), array("DATE_BEGIN","DATE_END"));
         * */
            if ( CModule::IncludeModule ( "iblock" ) ){
                $arOrder = array(
                    "sort" => "ASC",
                );
    
                $arFilter = array(
                    "IBLOCK_ID" => $IBLOCK_ID,
                );
    
                foreach ( $TO_PROPERTY_NAMES as $property_name ) {
                    $TO_PROPERTY_NAMES_with_prop[] = 'PROPERTY_' . $property_name;
                }
    
                $arSelect = array(
                    "NAME",
                    "ID"
                );
    
                $arSelect = array_merge ( $arSelect, $_FROM_FIELD_NAMES, $TO_PROPERTY_NAMES_with_prop );
    
                $db_elemens = CIBlockElement::GetList ( $arOrder, $arFilter, false, false, $arSelect );
    
                while ( $arElement = $db_elemens->Fetch () ) {
                    $PRODUCT_ID = $arElement["ID"];
    
                    foreach ( $TO_PROPERTY_NAMES as $key => $property_name ) {
                        CIBlockElement::SetPropertyValues ( $PRODUCT_ID, $IBLOCK_ID, $arElement[$_FROM_FIELD_NAMES[$key]], $property_name );
                    }
                }
    
            } else {
                die( "Модуль iblock не установлен" );
            }
        }
    

    Дополнительная информация

    • Документация по методу CIBlockElement::SetPropertyValues

    Получение суммы значений полей связанных инфоблоков

    Допустим, что у нас имеются связанные инфоблоки и поставлена следующая задача: необходимо в специальном поле элемента инфоблока получить сумму значений полей связанных элементов.

    Решение возможно как с использованием модуля Интернет-магазин, так и без него. При использовании интернет-магазина все инфоблоки должны иметь свойства торгового каталога с указанием цены в соответствующих полях.
    Если используется редакция без интернет-магазина, то все поля исходных инфоблоков должны иметь тип число и (в рамках данного решения) иметь код PRICE.

    Пусть поле результирующего инфоблока называется COST. Его тип должен быть — число. В поле «Значение по умолчанию» параметра COST должно быть внесено выражение типа:

    • {PROP_1_PRICE}+{PROP_2_PRICE}+… — для редакций с Интернет-магазином.
    • {PROP_1} + {PROP_2}+… — для редакций без Интернет- магазина.

    Код ниже приведен для вывода результата в компоненте catalog.section. Для вывода результатов в другом компоненте требуется модификация кода. Приведенный код вносится в файл result_modifer.php указанного компонента:

    <?
    //Эту строчку можно раскомментировать и посмотреть содержимое arResult
    //того компонента под который будет адаптирован этот модификатор
    //echo "<pre>",htmlspecialchars(print_r($arResult, 1)),"</pre>";
    
    //Символьный код свойства значение по умолчанию которого содержит выражения
    //результат вычисления будет показан шаблоном компонента
    //Само выражение представляет собой исполняемый eval'ом PHP код
    //в котором по шаблонам вида {<СИМВОЛЬНЫЙ КОД СВОЙСТВА>} будут подставлены конкретные значения
    //Эти свойства должны быть выбраны в настройках компонента и доступны через arResult
    //в противном случае надо воспользоваться функцией CIBlockElement::GetProperty для доступа к БД
    //Эти свойства должны быть НЕ множественными
    //Пример выражения: "({PROP_1_PRICE} + {PROP_2_PRICE}) * {PROP_COUNTER}"
    //Обратите внимание на _PRICE - это указание на необходимость выборки цены привязанного элемента! 
       //Само свойство должно иметь символьный код PROP_1 и PROP_2 соответственно
    
    $CALCULATOR_CODE="COST";
    //ИД цены которая будет браться для вычислений
    $PRICE_ID = 1;
    //Идентификатор инфоблока (для разных компонент надо брать разные поля arResult)
    $IBLOCK_ID = $arResult["IBLOCK_ID"];
    //Получаем метаданные свойства "Калькулятора"
    $arProperty = CIBlockProperty::GetPropertyArray($CALCULATOR_CODE, $IBLOCK_ID);
    //Если такое свойство есть и у него задано выражение для вычислений:
    if($arProperty && strlen($arProperty["DEFAULT_VALUE"]) > 0)
    {
       //Цикл по всем элементам каталога
       foreach($arResult["ITEMS"] as $i => $arElement)
       {
          //Берем выражение "Калькулятора"
          $EQUATION = $arProperty["DEFAULT_VALUE"];
          //Проверим надо ли выполнять подстановку шаблонов
          if(preg_match_all("/(\{.*?\})/", $EQUATION, $arMatch))
          {
             //Цикл по всем использованным в выражении свойствам
             $arPropCodes = array();
             foreach($arMatch[0] as $equation_code)
             {
                //Это "цена" и она потребует больших усилий
                $bPrice = substr($equation_code, -7)=="_PRICE}";
                //Символьный код свойства значение которого будет подставлено в выражение
                $property_code = ($bPrice? substr($equation_code, 1, -7): substr($equation_code, 1, -1));
    
                if($bPrice)
                {
                   //Находим связанный элемент
                   $rsLinkedElement = CIBlockElement::GetList(
                      array(),
                      array(
                         "=ID" => $arElement["PROPERTIES"][$property_code]["~VALUE"],
                         "IBLOCK_ID" => $arElement["PROPERTIES"][$property_code]["~LINK_IBLOCK_ID"]
                      ),
                      false, false,
                      array(
                         "ID", "IBLOCK_ID", "CATALOG_GROUP_".$PRICE_ID, "PROPERTY_PRICE"               )
                   );
                   $arLinkedElement = $rsLinkedElement->Fetch();
                   //Забираем его цену
                   if($arLinkedElement["CATALOG_PRICE_".$PRICE_ID])
                      $value = doubleval($arLinkedElement["CATALOG_PRICE_".$PRICE_ID]);
                   else
                      $value = doubleval($arLinkedElement["PROPERTY_PRICE_VALUE"]);
                }
                else
                {
                   //Если вам потребуются не только числа, но и строки
                   //избавьтесь от doubleval и добавьте экранирование строковых символов
                   $value = doubleval($arElement["PROPERTIES"][$property_code]["~VALUE"]);
                }
                //Подстановка значения
                $EQUATION = str_replace($equation_code, $value, $EQUATION);
             }
    
          }
          //Собственно вычисление
          $VALUE = @eval("return ".$EQUATION.";");
          //и сохрание его результата для показа в шаблоне
          $arResult["ITEMS"][$i]["DISPLAY_PROPERTIES"][$CALCULATOR_CODE] = $arResult["ITEMS"][$i]["PROPERTIES"][$CALCULATOR_CODE];
          $arResult["ITEMS"][$i]["DISPLAY_PROPERTIES"][$CALCULATOR_CODE]["DISPLAY_VALUE"] = htmlspecialchars($VALUE);
       }
    }
    ?>
    

    Вывод свойств элемента инфоблока

    Задача

    Выбрать свойство(-а) элемента инфоблока и вывести его на экран.

    Решение

    Решение первой части банально: метод GetProperty класса CIBlockElement подробно описан в документации.

    Решение второй части. Возьмём свойство типа HTMLтекст. Для этого свойства нельзя просто вывести его значение (ключ VALUE), т.к. это — массив, содержащий «сырое» значение и его тип (HTML или текст). Всего один вызов метода GetDisplayValue класса CIBlockFormatProperties:

    $arResult['DISPLAY_PROPERTIES'][$pid] = CIBlockFormatProperties::GetDisplayValue($arResult, $prop, 'news_out');

    Теперь в шаблоне мы можем писать так:

    echo $arResult['DISPLAY_PROPERTIES'][$pid]['DISPLAY_VALUE'];

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

    Практика. Копирование инфоблока

    В Bitrix Framework штатно не предусмотрена возможность копирования инфоблоков. Иногда возникает такая потребность и ее можно решить. Автоматизация этого процесса и будет примером использования API инфоблоков.

    Использование импорта XML

    Копирование инфоблоков можно осуществить через функцию импорта/экспорта XML:

    • Создайте инфоблок в который будете копировать данные.
    • Выгрузите необходимый инфоблок экспортом в XML.
    • Откройте для редактирования файл XML и аккуратно в нужных местах скорректируйте ID инфоблока-донора на ID инфоблока-реципиента. В начале XML можно поменять узел ИД и узел Наименование:
      <?xml version="1.0" encoding="UTF-8"?>
      <КоммерческаяИнформация ВерсияСхемы="2.021" ДатаФормирования="2010-03-20T09:55:13">
         <Классификатор>
            <Ид>2
            <Наименование>ноутбуки
            <Свойства>
               <Свойство>
                  <Ид>CML2_CODE
                  <Наименование>Символьный код

      после описания инфоблока и его свойств найдите код:

      <Каталог>
            <Ид>2
            <ИдКлассификатора>2
            <Наименование>ноутбуки

      установите данные в соответствии с внесенными изменениями выше в узлах ИД, ИД классификатора и Наименование.

    Автоматизация копирования

    Есть небольшой инструмент для импорта метаданных с ранее созданного информационного блока при генерации нового:

    Настройка копирования метаданных задается тремя полями:

    • Копируем ИБ. Поле обязательно для заполнения и всегда предустановлено. В данной секции указывается с какого ИБ будут импортироваться метаданные (за исключением описания свойств).
    • Копируем в новый ИБ свойства другого ИБ. Поле не обязательное, может быть использовано для импорта в новый информационный блок только метаданных свойств любого инфоблока. В случае если поле не заполнено, метаданные свойств берутся из инфоблока указанного в поле Копируем ИБ.
    • Копируем ИБ в тип. Поле не обязательное и может быть указано, в случае если новый информационный блок необходимо сгенерировать в каком либо типе ИБ. Если настройка не указана, используется тип инфоблока, указанного в поле Копируем ИБ.

      Новый инфоблок после копирования будет иметь имя старого с суффиксом _new.

    Код скрипта:

    CModule::IncludeModule("iblock");
        if(intval($_REQUEST["IBLOCK_ID_FIELDS"])>0){
            $bError = false;
            $IBLOCK_ID = intval($_REQUEST["IBLOCK_ID_FIELDS"]);
            $ib = new CIBlock;
            $arFields = CIBlock::GetArrayByID($IBLOCK_ID);
            $arFields["GROUP_ID"] = CIBlock::GetGroupPermissions($IBLOCK_ID);
            $arFields["NAME"] = $arFields["NAME"]."_new";
            unset($arFields["ID"]);
            if($_REQUEST["IBLOCK_TYPE_ID"]!="empty")
                $arFields["IBLOCK_TYPE_ID"]=$_REQUEST["IBLOCK_TYPE_ID"];
            $ID = $ib->Add($arFields);
                if(intval($ID)<=0)
                    $bError = true;        
            if($_REQUEST["IBLOCK_ID_PROPS"]!="empty")
                $iblock_prop=intval($_REQUEST["IBLOCK_ID_PROPS"]);
            else
                $iblock_prop=$IBLOCK_ID;
    
            $iblock_prop_new = $ID;
            $ibp = new CIBlockProperty;
            $properties = CIBlockProperty::GetList(Array("sort"=>"asc", "name"=>"asc"), Array("ACTIVE"=>"Y", "IBLOCK_ID"=>$iblock_prop));
            while ($prop_fields = $properties->GetNext()){
                if($prop_fields["PROPERTY_TYPE"] == "L"){
                    $property_enums = CIBlockPropertyEnum::GetList(Array("DEF"=>"DESC", "SORT"=>"ASC"),
                                                                   Array("IBLOCK_ID"=>$iblock_prop, "CODE"=>$prop_fields["CODE"]));
                    while($enum_fields = $property_enums->GetNext()){
                        $prop_fields["VALUES"][] = Array(
                          "VALUE" => $enum_fields["VALUE"],
                          "DEF" => $enum_fields["DEF"],
                          "SORT" => $enum_fields["SORT"]
                        );
                    }
                }
                $prop_fields["IBLOCK_ID"]=$iblock_prop_new;
                unset($prop_fields["ID"]);
                foreach($prop_fields as $k=>$v){
                    if(!is_array($v))$prop_fields[$k]=trim($v);
                    if($k{0}=='~') unset($prop_fields[$k]);
                }
                $PropID = $ibp->Add($prop_fields);
                if(intval($PropID)<=0)
                    $bError = true;
            }
            if(!$bError && $IBLOCK_ID>0)
                LocalRedirect($APPLICATION->GetCurPageParam("success=Y",array("success","IBLOCK_ID_FIELDS")));
            else 
                LocalRedirect($APPLICATION->GetCurPageParam("error=Y",array("success","IBLOCK_ID_FIELDS")));
        }
        $str .='<form action='.$APPLICATION->GetCurPageParam().' method="post"><table>';    
        if($_REQUEST["success"]=="Y") $str .='<tr><td><font color="green">ИБ успешно скопирован</font><b></td></tr>';
        elseif($_REQUEST["error"]=="Y") $str .='<tr><td><font color="red">Произошла ошибка</font><br/></td></tr>';
        $str .='<tr><td]Копируем мета данные ИБ в новый ИБ</b><br/></td></tr>';
        $res = CIBlock::GetList(Array(),Array(),true);
            while($ar_res = $res->Fetch())
                $arRes[]=$ar_res;
        $str .='<tr><td>Копируем ИБ:<br><select name="IBLOCK_ID_FIELDS">';
        foreach($arRes as $vRes)    
            $str .= '<option value='.$vRes['ID'].'>'.$vRes['NAME'].' ['.$vRes["ID"].']</option>';
        $str .='</select></td>';
        $str .='<td>Копируем в новый ИБ свойства другого ИБ: *<br><select name="IBLOCK_ID_PROPS">';
        $str .='<option value="empty">';
        foreach($arRes as $vRes)    
            $str .= '<option value='.$vRes['ID'].'>'.$vRes['NAME'].' ['.$vRes["ID"].']</option>';
        $str .='</select></td></tr>';
        $str .='<tr><td>Копируем ИБ в тип:<br><select name="IBLOCK_TYPE_ID">';
        $str .='<option value="empty">';
        $db_iblock_type = CIBlockType::GetList();
        while($ar_iblock_type = $db_iblock_type->Fetch()){
           if($arIBType = CIBlockType::GetByIDLang($ar_iblock_type["ID"], LANG))
              $str .= '<option value='.$ar_iblock_type["ID"].'>'.htmlspecialcharsex($arIBType["NAME"])."</option>";
        }
        $str .='</select></td></tr>';
        $str .='<tr><td><br/>* если значение не указано мета данные ИБ секции "Свойства" берутся из ИБ первого поля</td></tr>';
        $str .='<tr><td><input type="submit" value="копируем"></td></tr>';
        $str .='</table></form>';    
        echo $str;

    Скрипт может оказать неоценимую помощь например при копировании ИБ не прибегая к использованию механизмов XML экспорта и XML импорта информационных блоков.

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

    Скрипт должен быть размещен в корне сайта.

    Список ссылок по теме:

    • Бесплатное решение Инфоблоки, инструменты от Антона Долганина.

    Некоторые ошибки при работе с инфоблоками

    Ошибка типа:

    Fatal error: Class 'CIBlockElement' not found in /hosting/site.ru/www/index.php on line XX

    Если используете модуль Инфоблоки, его нужно сначала подключить:

    CModule::IncludeModule("iblock");

    Ошибка типа:

    Fatal error: Call to a member function GetNextElement() on a non-object in /hosting/site.ru/www/index.php on line XX

    Скорее всего вы передали неверные параметры какому-то методу. Например, так:

    $res = CIBlockElement::GetList(array(), $arFilter, array(), array(), $arSelect);

    Третий-то параметр должен быть true/false, а не array. Читайте внимательно описание используемого метода.

    Практика. Ограничение области поиска разделом

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

    Поиск по разделу инфоблока

    Чтобы поиск выполнялся по элементам некоторого раздела инфоблока, необходимо выполнить следующие действия:

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

      Для этого в файле /bitrix/php_interface/init.php следует создать обработчик события BeforeIndex и получить разделы привязки элемента (CIBlockElement::GetElementGroups):

      <?
      // файл /bitrix/php_interface/init.php
      // регистрируем обработчик
      AddEventHandler("search", "BeforeIndex", Array("MyClass", "BeforeIndexHandler"));
      
      class MyClass
      {
         // создаем обработчик события "BeforeIndex"
         function BeforeIndexHandler($arFields)
         {
            // элемент инфоблока 6 (не раздел)
            if($arFields["MODULE_ID"] == "iblock" && $arFields["PARAM2"] == 6 && substr($arFields["ITEM_ID"], 0, 1) != "S")
            {
               $arFields["PARAMS"]["iblock_section"] = array();
               //Получаем разделы привязки элемента (их может быть несколько)
               $rsSections = CIBlockElement::GetElementGroups($arFields["ITEM_ID"], true);
               while($arSection = $rsSections->Fetch())
               {
                  //Сохраняем в поисковый индекс
                  $arFields["PARAMS"]["iblock_section"][] = $arSection["ID"];
               }
            }
            //Всегда возвращаем arFields
            return $arFields;
         }
      }
      ?>
    • Пересохранить элемент этого инфоблока (в данном случае инфоблока с ID=6) или выполнить полную переиндексацию. Теперь в модуле производительности на странице просмотра содержания таблицы b_search_content_param можно увидеть результат работы этого обработчика:

    • Создать страницу поиска. В коде странице необходимо обязательно указать идентификатор раздела (в данном случае раздел с ID=12), поиск по которому будет выполняться, а в настройках компонента поиска должен быть задан соответствующий инфоблок (инфоблок с ID=6):

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

    Поиск по разделу и его подразделам

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

    • В файле init.php перед сохранением в поисковый индекс воспользоваться функцией CIBlockSection::GetNavChain:
      <?
      // файл /bitrix/php_interface/init.php
      // регистрируем обработчик
      AddEventHandler("search", "BeforeIndex", Array("MyClass", "BeforeIndexHandler"));
      
      class MyClass
      {
         // создаем обработчик события "BeforeIndex"
         function BeforeIndexHandler($arFields)
         {
            // элемент инфоблока 6 (не раздел)
            if($arFields["MODULE_ID"] == "iblock" && $arFields["PARAM2"] == 6 && substr($arFields["ITEM_ID"], 0, 1) != "S")
            {
               $arFields["PARAMS"]["iblock_section"] = array();
               //Получаем разделы привязки элемента (их может быть несколько)
               $rsSections = CIBlockElement::GetElementGroups($arFields["ITEM_ID"], true);
               while($arSection = $rsSections->Fetch())
               {
                  $nav = CIBlockSection::GetNavChain(6, $arSection["ID"]);
                  while($ar = $nav->Fetch()) {
                     //Сохраняем в поисковый индекс
                     $arFields["PARAMS"]["iblock_section"][] = $ar[ID];
                     }
               }
            }
            //Всегда возвращаем arFields
            return $arFields;
         }
      }
      ?>
      
    • Выполнить переиндексацию инфоблоков.

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

    • В файле init.php добавить сохранение подразделов в поисковый индекс:
      <?
      // файл /bitrix/php_interface/init.php
      // регистрируем обработчик
      AddEventHandler("search", "BeforeIndex", Array("MyClass", "BeforeIndexHandler"));
      
      
      class MyClass
      {
          // метод модифицирует поисковый индекс для элементов и разделов инфоблока
          
          function BeforeIndexHandler($arFields)
          {
              $IBLOCK_ID = 6;
              
              // Обрабатываем только нужный инфоблок
              if($arFields["MODULE_ID"] == "iblock" && $arFields["PARAM2"] == $IBLOCK_ID)
              {
                  $arFields["PARAMS"]["iblock_section"] = array();
                  
                  // Добавляем разделы элемента с учетом родительских разделов
                 
                  if(substr($arFields["ITEM_ID"], 0, 1) != "S")
                  {
                      // Получаем разделы привязки элемента (их может быть несколько)
                      $rsSections = CIBlockElement::GetElementGroups($arFields["ITEM_ID"], true);
                      while($arSection = $rsSections->Fetch())
                      {
                          $nav = CIBlockSection::GetNavChain($IBLOCK_ID, $arSection["ID"]);
                          while($ar = $nav->Fetch())
                          {
                              //Сохраняем в поисковый индекс
                              $arFields["PARAMS"]["iblock_section"][] = $ar['ID'];
                          }
                      }
                  }
                  // Добавляем разделы раздела с учетом родительских разделов
                  else
                  {
                      // Получаем разделы
                      $nav = CIBlockSection::GetNavChain($IBLOCK_ID, substr($arFields["ITEM_ID"], 1, strlen($arFields["ITEM_ID"])));
                      while($ar = $nav->Fetch())
                      {
                          //Сохраняем в поисковый индекс
                          $arFields["PARAMS"]["iblock_section"][] = $ar['ID'];
                      }
                  }
              }
              //Всегда возвращаем arFields
              return $arFields;
          }
      }
      ?>
      
    • Выполнить переиндексацию инфоблоков.

    Программирование в Bitrix Framework

    Цитатник веб-разработчиков.

    Хан Эрли: В конечном счёте качество сайта определятся не платформой, а мастерством разработчика.

    Программирование в Bitrix Framework не представляет особой сложности. Это обычное программирование на PHP. Особенности, конечно, есть, но это не те особенности, которые делают из «программистов на Битрикс» какую-то особую касту. Всё в пределах досягаемости для неленивого ума.

    В этой главе освещаются вопросы особенности программирования на Bitrix Framework.

    Командная PHP-строка

    Иногда бывают ситуации, когда нужно быстро исполнить некоторый код, вызывающий функции API Bitrix Framework без создания новых страниц на сайте для этого. В этом случае поможет удобный и простой инструмент — командная PHP-строка, позволяющий запускать произвольный код на PHP с вызовами функций.

    Инструмент расположен в административной части сайта по следующему пути: Настройки > Инструменты > Командная PHP-строка и имеет адрес /bitrix/admin/php_command_line.php.

    Вот как выглядит результат выполнения кода, использующего функции класса CUser Главного модуля:

    С помощью кнопки «+» можно создавать новые закладки и сохранять в них частоиспользуемый php-код. Для переименования закладки используйте //title:*** в начале вашего кода.

    Примечание: Аналогичный инструмент есть и для работы с БД.

    Организация разработки

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

    Система контроля версий

    Организовать сопровождение проекта с помощью системы контроля версий не сложно, если ограничиваться файлами. Для этого можно использовать, например, Mercurial — кроссплатформенную распределённую систему управления версиями, разработанную для эффективной работы с очень большими репозиториями кода. Рекомендуется использовать надстройку с графическим интерфейсом.

    Простая схема репозиториев

    В схеме три элемента:

    • Центральный репозиторий — место хранения изменений.
    • Копия для разработки — рабочие места для разработчиков, которых может быть несколько.
    • Боевой сайт — конечная цель всех изменений

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

    Как ведётся работа

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

    Нажмите на рисунок, чтобы увеличить

    Красный текст — удаление, зелёный — добавление. После проверки изменений нажимаем кнопку Commit и изменения отображаются в среде Mercurial:

    Нажмите на рисунок, чтобы увеличить

    Далее пишем изменения на Центральный репозиторий и переходим на «боевой» сайт. Изменения на «боевом» сайте вносятся уже из командной строки. Так же необходимо проверить вносились ли изменения на самом «боевом» проекте и, при необходимости, перенести их на Центральный репозиторий и на Копии для разработки.

    Примечание: Детально о работе системы контроля версий можно узнать из видео. (С 1 часа 21-ой минуты)

    Сложности

    Версия ядра на сервере сайта и на сервере Копии для разработчиков могут отличаться. Поэтому ядро исключается из системы контроля версий. Технически это делается набором правил для файла настройки .hgignore.

    Ещё одна сложность: файлы ядра изменяются не разработчиками проекта, а приходят «снаружи» в виде обновлений. При этом нельзя просто исключить папку /bitrix/, так как в ней могут находиться в том числе файлы проекта: модули, компоненты, шаблоны сайта и так далее. В итоге файл .hgignore приобретает избыточный вид:

    /bitrix/activities/bitrix/
    /bitrix/admin
    /bitrix/cache
    /bitrix/components/bitrix/
    /bitrix/gadgets/bitrix
    /bitrix/image_uploader
    /bitrix/images
    /bitrix/js
    /bitrix/managed_cache
    /bitrix/stack_cache
    /bitrix/modules/advertising
    /bitrix/modules/bitrix.sitecommunity
    ...
    /bitrix/modules/xdimport
    /bitrix/modules/xmpp
    /bitrix/modules/.htaccess
    /bitrix/otp
    /bitrix/sounds
    /bitrix/template/
    /bitrix/themes
    /bitrix/tmp
    /bitrix/tools
    /bitrix/wizards/bitrix
    /bitrix/[^/]*.php$
    /upload
    /bitrix/php_interface
    /bitrix/panel/
    /bitrix/updates/
    /bitrix/fonts/

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

    Внимание! При копировании файла init.php в папку /local, файл init.php находящийся в папке /bitrix перестает работать.

    Папка /local

      Папка для своих доработок

    Чтобы сделать жизнь разработчиков проектов удобнее, в ядре D7 с версии главного модуля 14.0.1 основные файлы пользовательских проектов вынесены из папки /bitrix в папку /local. Это позволяет изолировать изменяющиеся файлы вашего проекта от папки продукта. По сути, в исключения достаточно будет добавить одну папку /bitrix.

    Какие папки обрабатываются в /local?

    • activities — действия БП;
    • components — компоненты;
    • gadgets — гаджеты рабочего стола;
    • modules — модули;
    • php_interfaceinit.php, папка user_lang;
    • templates — шаблоны сайтов, шаблоны компонентов, шаблоны страниц;
    • blocks — блоки Сайтов24.
    • routes — файлs с конфигурацией маршрутов роутинга

    При обработке папок приоритет всегда у папки /local перед /bitrix. Это означает, что если в /local/templates/ и /bitrix/templates/ будут находиться шаблоны сайта с одинаковым названием, то подключится шаблон из /local.

    Примечание: не рекомендуется копировать сущности с одинаковыми именами в обе папки. Более правильное решение — это перенос сущности из одной папки в другую. При копировании система все равно будет корректно работать, но велика вероятность путаницы в работе разработчиков.

    Внимание! В целях безопасности проекта необходимо задать права доступа к папке /local/php_interface/ аналогичные правам доступа к папке /bitrix/php_interface/.

      Перенос проекта

    Как старый проект перенести в папку /local.

    Вам достался старый и запущенный проект. Кто когда и как вносил правки — неизвестно, куда вносились правки — неизвестно. Вам нужно навести в нём порядок.

    1. Для начала следует воспользоваться [dwi include_monitor]Монитором качества[/dwi] в нём есть проверка на модификацию файлов ядра. Этот тест поможет определить какие файлы были изменены.
    2. Далее создаете папку /local, в ней не создаете файл /local/php_interface/[dwi include_int]init[/dwi].php, а лишь /local/php_interface/constants.php, /local/php_interface/events.php, автолоадер для своих классов и так далее, и эти файлы подключаете в /bitrix/php_interface/init.php.
    3. В /local/templates/.default постепенно начинаете перенос ваши измененных шаблонов, при этом действовать нужно так: копируете в локал с другим названием, работаете с тестовой страницей (если тестового сервера нет). Потом заменяете название и из папки вашегo шаблона удаляете (или переименовываете) старый шаблон. Таким образом новый шаблон (чистый и готовый к использованию) постепенно будет собираться в local. Как только все шаблоны и компоненты перенесены в /local, init.php тоже можно будет перенести.
    4. С компонентами иначе — если это ваши родные компоненты, то их можно перенести и так (ничего от этого не изменится). Измененные штатные компоненты нужно копировать сначала из ядра, а потом уже сравнивать изменения и вносить правки.
    5. В шаблонах и компонентах нужно заменять прямые ссылки на /bitrix/templates/** , /bitrix/components/** и заменять на /local/**. Также внимательно проверяем и модули, если есть необходимость и их переносить.

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

    Цитатник веб-разработчиков.

    Иван Поддубный: Есть простой лайфхак, которым мы пользуемся:
    Переносим в локал все что нужно, и ставим симлинки с папок в битриксе в локал.
    Да, это конечно не полноценное решение проблемы, но это даст возможность разработчикам сразу начать работать с проектом с git с клиентом.

    Ведь когда такая необходимость возникает? Когда клиент приходит в компанию с старым сайтом. А на входе клиенту начать заниматься рефакторингом путей в шаблонах — такое, особенно если шаблонов много, выкатывать клиенту N часов на исправление вместо того чтобы решить более важные проблемы.

    У таких проектов обычно есть что рефакторить и это более важно, например забитые под завязку init.php с кучей процедурной логики которую написали разработчики тупо копипастя примеры из документации битрикса не удосужившись даже переименовать классы и методы, тормозящие компоненты, роутинг в шаблонах и прочее, перечислять приколы можно долго =)

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

    Composer и Bitrix Framework

    Composer — это пакетный менеджер уровня приложений для языка программирования PHP, который предоставляет средства по управлению зависимостями в PHP-приложении.

    В версии 18.0.5 мы начали использовать composer внутри продукта в режиме разработки. Он понадобится вам, если вы захотите воспользоваться такими преимуществами, как аннотация ORM классов и в целом интерфейсом командной строки CLI. Если вы уже используете composer в своем проекте на 1С-Битрикс, мы подготовили готовый рецепт интеграции с нашей конфигурацией зависимостей.

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

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

    $ composer -V
    Composer version 1.6.5 2018-05-04 11:44:59
    

    Установка зависимостей

    1. Вы еще не используете composer в проекте

    Устанавливаем зависимости из нашего bitrix/composer-bx.json:

    $ cd bitrix
    $ COMPOSER=composer-bx.json composer install
    

    После этого появится папка bitrix/vendor, в которую будут установлены необходимые библиотеки. Если вы хотите задать другое расположение этой директории, вам понадобится создать свой composer.json — смотрите второй вариант установки зависимостей.

    2. Вам нужна своя конфигурация composer.json

    По умолчанию система ожидает увидеть ваш файл composer.json в папке bitrix, но мы рекомендуем разместить его где-либо за пределами DOCUMENT_ROOT (чтобы он не был доступен публично). В этом случае нужно указать путь до файла в .settings.php, чтобы его конфигурация могла быть использована в продукте.

    Файл .settings.php:

    <?php
    return [
      'composer' => [
        'value' => ['config_path' => '/path/to/your/composer.json']
      ],
      // ...
    ];  
    

    В нем необходимо подключить наш файл с зависимостями bitrix/composer-bx.json с помощью плагина Composer Merge Plugin. В минимальном виде ваш composer.json должен содержать вызов плагина и подключение нашей конфигурации.

    Файл composer.json (можно скопировать из bitrix/composer.json.example):

    {
    	"require": {
    		"wikimedia/composer-merge-plugin": "dev-master"
    	},
    	"extra": {
    		"merge-plugin": {
    			"require": [
    				"/path/to/bitrix/composer-bx.json"
    			]
    		}
    	}
    }
    

    Вместо /path/to/bitrix/ вам нужно указать реальный путь до папки bitrix.

    К этому вы можете добавить свои зависимости и настройки. Например, чтобы явно задать путь до папки vendor (по умолчанию она будет там же, где файл composer.json), используйте директиву "vendor-dir".

    Файл composer.json:

    {
    	"require": {
    		"wikimedia/composer-merge-plugin": "dev-master"
    	},
    	"config": {
    		"vendor-dir": "../../vendor"
    	},
    	"extra": {
    		"merge-plugin": {
    			"require": [
    				"/path/to/bitrix/composer-bx.json"
    			]
    		}
    	}
    }
    

    После описания своей конфигурации останется установить библиотеки:

    $ composer install
    

    Теперь вы можете использовать преимущества composer в своем проекте, подключая файл vendor/autoload.php. При использовании CLI-команд он будет подключен автоматически.

    Bitrix CLI

    Командный интерфейс реализован на основе библиотеки symfony/console. Перед началом использования убедитесь, что установили зависимости через composer.

    Исполняемый файл находится в папке bitrix:

    $ cd bitrix
    $ php bitrix.php
    

    Для удобства вы можете создать символическую ссылку без постфикса php:

    $ chmod +x bitrix.php
    $ ln -s bitrix.php bitrix
    $ ./bitrix
    

    Список доступных команд «из коробки»:

    • orm:annotate

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

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

    • Инструмент @bitrix/cli.

    Немного теории PHP

    Не считаем наших читателей ламерами в PHP, тем не менее слегка напомним теорию. А заодно и зададим стандарты написания кода в Bitrix Framework.

    Замечания по $arParams и $arResult

    $arParams

    $arParams — это предопределенная для компонента переменная, представляющая собой массив входных параметров компонента. Ключами в этом массиве являются названия параметров, а значениями — их значения.

    Перед подключением компонента ко всем значениям параметров применяется функция htmlspecialcharsEx. Исходные значения параметров сохраняются в этом же массиве с теми же ключами, но с префиксом ~. Например, $arParams["NAME"] — входной параметр, к которому применена функция htmlspecialcharsEx, а $arParams["~NAME"] — исходный входной параметр.

    Переменная $arParams является псевдонимом для члена класса компонента, поэтому все изменения этой переменной отражаются и на этом члене класса. В начале кода компонента должна быть произведена проверка входных параметров, инициализация не установленных параметров, приведение к нужному типу (например, IntVal()). Все эти изменения входных параметров будут доступны и в шаблоне. То есть параметры будут там уже проверенными и максимально безопасными. Дублирование подготовки параметров в шаблоне компонента не требуется.

    $arResult

    $arResult — это предопределенная для компонента переменная, в которую собирается результат работы компонента для передачи в шаблон. Перед подключением файла компонента эта переменная инициализируется пустым массивом array().

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

    Ссылки в PHP

    Ссылки (references) в PHP служат для того, чтобы к одним и тем же данным можно было обратиться по разным именам. Если переменные $arParams и $arResult изменены некоторым образом в коде компонента, то они будут доступны измененными и в шаблоне.

    При этом нужно учитывать следующие нюансы:

    • Если в компоненте написать код:
      $arParams = & $arSomeArray;
      то переменная $arParams будет отвязана от члена класса компонента и привязана к массиву $arSomeArray. В этом случае дальнейшие изменения $arParams не попадут в шаблон компонента.
    • Если в компоненте написать код:
      unset($arParams);
      то это так же разорвет связь между $arParams и соответствующим членом класса компонента.

    HTTP POST запросы

    Если на сайте не существующие страницы обрабатываются с помощью опции ErrorDocument (ErrorDocument 404 /404.php), то при отправке HTTP POST запроса на несуществующую страницу данные POST запроса будут потеряны. Поэтому, такие запросы нужно направлять на скрипты, которые физически существуют.

    Один из способов организации ЧПУ в компонентах 2.0 предполагает использование опции ErrorDocument. Для компонентов 2.0 существует унифицированное решение, которое при написании компонентов и шаблонов компонентов позволяет не заботиться о том, осуществляется ли поддержка ЧПУ с помощью опции ErrorDocument или mod_rewrite.

    При написании в шаблонах компонентов форм, отправляющих данные методом POST, в качестве action необходимо указывать константу POST_FORM_ACTION_URI:

    <form method="post" action="<?=POST_FORM_ACTION_URI?>">
    	* * *
    </form>

    В этом случае при работе с использованием опции ErrorDocument POST-запрос будет направлен на физически существующий скрипт, а в остальных случаях — по текущему адресу. При этом для компонента не будет разницы, вызван ли он POST- или GET-запросом. Все необходимые переменные будут соответствующим образом установлены.

    Правила написания кода

    Цитатник веб-разработчиков.

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

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

    Правила форматирования кода должны быть едиными во всем проекте. И крайне желательно, чтобы они были очень похожими между проектами.

    • Форматирование кода
      • Длина строки
      • Правила переноса строки
      • Пробелы и табуляция
      • Форматирование подчиненности
      • Правила расстановки фигурных скобок
      • Использование тернарного оператора «?:»
      • Выражения
      • Инструкции if, else, while и т.п.
      • Сложные инструкции
      • Форматирование массивов
      • Пустые строки
      • Пробелы
      • Прочее
    • Соглашение об именовании
      • Общие понятия
      • Именование переменных
      • Именование методов
      • Именование классов
    • Комментарии
      • PHPDoc
    • Другое
      • Магические числа
      • Инструменты автоматического форматирования
        • php_beautifier

    1. Форматирование кода

    1.1. Структурирование текста

    1.1.1. Длина строки

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

    1.1.2. Правила переноса строки

    Если длина строки превышает 120 символов, то необходимо пользоваться следующими правилами переноса:

    • переносить можно после запятой или перед оператором;
    • переносимая строка должна быть сдвинута относительно верхней на один символ табуляции;
    • переносы должны быть в стиле UNIX.

    Пример 1: для строки

    $arAuthResult = $USER->ChangePassword($USER_LOGIN, $USER_CHECKWORD, $USER_PASSWORD, $USER_CONFIRM_PASSWORD, $USER_LID);

    допустимым будет следующий вариант переноса

    $arAuthResult = $USER->ChangePassword($USER_LOGIN, $USER_CHECKWORD,
         $USER_PASSWORD, $USER_CONFIRM_PASSWORD, $USER_LID);

    Пример 2: для строки

    if(COption::GetOptionString("main", "new_user_registration", "N")=="Y" && $_SERVER['REQUEST_METHOD']=='POST' && $TYPE=="REGISTRATION" && (!defined("ADMIN_SECTION") || ADMIN_SECTION!==true)) 

    допустимым будет следующий вариант переноса

    if (COption::GetOptionString("main", "new_user_registration", "N") == "Y"
         && $_SERVER['REQUEST_METHOD'] == 'POST' && $TYPE == "REGISTRATION"
         && (!defined("ADMIN_SECTION") || ADMIN_SECTION !== true))
    

    1.1.3. Пробелы и табуляция

    Для форматирования отступов в коде нужно использовать табуляцию. Использование пробелов запрещено. Причины:

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

    1.1.4. Форматирование подчиненности

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

    Пример:

    function func()
    {
         if (condition)
         {
              while (condition2)
              {
    
              }
          }
    }

    1.1.5. Правила расстановки фигурных скобок

    Открывающая скобка должна ставится под соответствующим оператором и на одном отступе с ним. Закрывающая скобка должна ставится под соответствующей открывающей.

    Пример:

    if ($condition)
    {
       ...
    }
    

    1.1.6. Использование тернарного оператора «?:»

    Условие следует заключать в скобки, тем самым отделяя его от остального кода. По возможности, действия, производимые по условию, должны быть простыми функциями. Если весь блок ветвления плохо читается, то стоит заменить его на if/else.

    Пример: (условие ? funct1() : func2());

    1.2. Инструкции, выражения

    1.2.1. Выражения

    Желательно, чтобы в каждой строчке присутствовало только одно выражение.

    Пример. Неправильно писать так:

    $a = $b; $b = $c; $c = $a;

    Правильно писать так

    $a = $b;
    $b = $c;
    $c = $a;
    

    1.2.2. Инструкции «if», «else», «while» и т.п.

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

    Пример. Неправильно писать так:

    if ($a == 0) $a = 10;
    else{
    $a = 5;
    $b = 10;}
    

    Правильно писать так:

    if ($a == 0)
    {
      $a = 10;
    }
    else
    {
      $a = 5;
      $b = 10;
    }
    

    1.2.3. Сложные инструкции

    Сложные инструкции следует разбивать по строкам. Например,

    if(COption::GetOptionString("main", "new_user_registration", "N")=="Y" && $_SERVER['REQUEST_METHOD']=='POST' &&
         $TYPE=="REGISTRATION" && (!defined("ADMIN_SECTION") || ADMIN_SECTION!==true))
    

    Можно записать как:

    if (COption::GetOptionString("main", "new_user_registration", "N") == "Y"
         && $_SERVER['REQUEST_METHOD'] == 'POST' && $TYPE == "REGISTRATION"
         && (!defined("ADMIN_SECTION") || ADMIN_SECTION !== true))
    {
    }
    

    Очень сложные инструкции рекомендуется разбивать на несколько более простых. Например,

    if((!(defined("STATISTIC_ONLY") && STATISTIC_ONLY && substr($APPLICATION->GetCurPage(), 0,
         strlen(BX_ROOT."/admin/"))!=BX_ROOT."/admin/")) && COption::GetOptionString("main", "include_charset", "Y")=="Y"
         && strlen(LANG_CHARSET)>0)
    

    Можно записать так:

    $publicStatisticOnly = False;
    if (defined("STATISTIC_ONLY")
      && STATISTIC_ONLY
      && substr($APPLICATION->GetCurPage(), 0, strlen(BX_ROOT."/admin/")) != BX_ROOT."/admin/")
    {
         $publicStatisticOnly = True;
    }
    if (!$publicStatisticOnly && strlen(LANG_CHARSET) > 0
         && COption::GetOptionString("main", "include_charset", "Y") == "Y")
    {
    }

    или так:

    if (!defined("STATISTIC_ONLY") || ! STATISTIC_ONLY
      || substr($APPLICATION->GetCurPage(), 0, strlen(BX_ROOT."/admin/")) == BX_ROOT."/admin/")
    {
      if (strlen(LANG_CHARSET) > 0 && COption::GetOptionString("main", "include_charset", "Y") == "Y")
      {
      }
    }
    

    1.2.4. Форматирование массивов

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

    $arFilter = array(
      "key1" => "value1",
      "key2" => array(
          "key21" => "value21",
          "key22" => "value22",
      )
    ); 

    1.3. Пустые строки и пробелы

    1.3.1. Пустые строки

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

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

    1.3.2. Пробелы

    После запятой должен быть пробел. После точки с запятой, если она не последняя в строке (например, в инструкции for), должен быть пробел. Перед запятой или точкой с запятой пробелы не ставятся. Все операторы должны быть отделены пробелом от операндов с обеих сторон. Замена пробела символом табуляции не допускается.

    Пояснения:

    • Один пробел используется в объявлении методов после запятой, но не перед скобками:
      TestMethod($a, $b, $c);

      Примеры неправильного использования:

      • TestMethod($a,$b,$c);
      • TestMethod( $a, $b, $c );
    • Так же одиночный пробел используют для выделения операторов: $a = $b * $c / $d;

      Пример неправильного использования: $a=$b*$c/$d;

    • Также пробелы используются при форматировании циклов:for ($i = 0; $i < 10; $i++).

      Пример неправильного использования: for($i=0;$i<10;$i++)

    • Табуляция всегда используется для отступов, но никогда не используется вместо пробелов.

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

      $arArray = array(
       "key1" =>→"value1",
       "key2" =>→"value2",
      );

      Примечание: присутствие или отсутствие пробела после if правилами не регламентируется.

    1.4. Прочее

    В сложных выражениях рекомендуется группировать операции с помощью скобок вне зависимости от того, требует это приоритет операций или нет. Пример: $r = $a + ($b * $c);

    2. Соглашение об именовании

    2.1. Общие понятия

    Не используйте подчеркивание для отделения слов внутри идентификаторов, это удлиняет идентификаторы и затрудняет чтение.

    Старайтесь давать переменным, методам и пр. «говорящие» названия. Предпочтительно использовать имена, которые ясно и четко описывают предназначение и/или смысл сущности.

    Старайтесь делать имена идентификаторов как можно короче (но не в ущерб читабельности).

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

    2.2. Именование переменных

    Первое логическое слово должно начинаться с маленькой буквы, остальные логические слова — с большой. Имена переменных могут иметь префиксы, если требуется явно указать тип переменной: ar — для массивов, db — для наборов данных из базы и т.п.

    Пример: $testCounter, $userPassword.

    2.3. Именование методов

    • Очевидность из названия действия, которое будет совершать функцияметод.
    • Использование префиксов: is (обозначение вопроса), get (получить значение), set (установить значение).

    Пример: isFileWriteable()

    2.4. Именование классов

    • В имени должна обозначаться сущность, описываемая классом.
    • В имени должно быть не более трех слов.
    • Нельзя использовать знак подчеркивания (‘_’).
    • В качестве разделителей слов в имени следует использовать заглавные буквы, строчные — для остальной части.

    Пример: class SaleAffiliateAccount

    3. Комментарии

    Комментарий должны быть на английском языке и носить содержательный характер.

    3.1. PHPDoc

    Необходимо описывать в стиле PHPDoc все классы и их публичные методы.

    Пример:

    /**
     * Gets a value that indicates whether a directory exists in the file system
     *
     * @param string $path - Path to the directory
     * @return bool - True if the directory exists, false - otherwise
     */
    

    4. Другое

    4.1. Магические числа

    В коде не должно быть магических чисел. Пример плохого кода:

    $task->Update($taskId, array('STATUS' => 3), 1);

    Пример хорошего кода:

    $task->Update($taskId, array('STATUS' => CTaskStatus::New), TASK_PERMISSIONS_WRITE);

    4.2. Инструменты автоматического форматирования

    4.2.1. php_beautifier

    1. Установить пакет php_beautifier (ubuntu):

    sudo aptitude install php_beautifier или sudo aptitude install php-pear и sudo pear install PHP_Beautifier-0.1.15
    cd
    hg clone
    sudo ln -s Bitrix_php_beautifier/Bitrix.filter.php /usr/share/php/PHP/Beautifier/Filter/Bitrix.filter.php

    2. Настроить редактор:

    • Выбрать пункт меню Settings — Configure Kate…
    • Выбрать пункт настроек Plugins и отметить галочку рядом с Text Filter
    • Нажать OK
    • Теперь в меню Tools появился пункт Filter Text

    Использование:

    • Выделяем строки (или Ctrl-A для всего текста)
    • ToolsFilter Text
    • Вводим (или выбираем из истории): php_beautifier -t -f — -l ‘Lowercase Bitrix’
    • Нажимаем OK.

    Список ссылок по теме:

    • Стандарты оформления кода PHP

    Архитектура продукта

    Цитатник веб-разработчиков.

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

    Любое программное обеспечение, развиваясь, должно соответствовать заявленной изначально цели. Эту задачу решает архитектурное проектирование. Архитектура продукта — подход к проектированию, гарантирующий, что программное обеспечение будет отвечать своему предназначению.

    Архитектура программного обеспечения — (англ. software architecture) — это структура программы или вычислительной системы, которая включает программные компоненты, видимые снаружи свойства этих компонентов, а также отношения между ними.

    Архитектура Bitrix Framework решает следующие задачи:

    • Преемственность. Каждый новый релиз продуктов поддерживает все предыдущие решения и технологии. Это позволяет осуществлять переход на новые версии продуктов сайтов, созданных на практически любой предыдущей версии.
    • Единство принципов работы с любой версией и любым решением на базе системы.
    • Безопасность. Архитектура позволяет создать достаточный уровень безопасности для сайтов любой направленности.
    • Масштабируемость. Не наложено никаких ограничений на развитие проектов по мере роста контента, сервисов, числа пользователей.
    • Производительность. Скорость работы системы зависит от качества настройки ее элементов, то есть в большей степени на производительность влияет уровень подготовки разработчика проекта, возможности хостинга.
    • Возможность развития системы усилиями сторонних разработчиков. Архитектура не накладывает никаких ограничений на создание собственных модулей, компонентов, решений.

    Цитатник веб-разработчиков.

    Степан Овчинников: Я убежден, что логика, представления и данные в Битриксе разделены самым разумным для задач CMS образом.

    Архитектура MVC для Bitrix Framework

    Шаблон MVC для Bitrix Framework:

    • Модель — это API;
    • Представление — это шаблоны;
    • Контроллер — это компонент.

    Сплошные линии — прямые связи, пунктир — косвенные связи.

    Структура

    Bitrix Framework по уровням архитектуры структуру можно описать так:

    Bitrix Framework:

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

  • шаблон
  • компоненты
  • страница
  • компонент:

  • вызов
  • параметры
  • шаблон
  • страница:

  • header
  • workarea
  • footer
  • Элементы структуры Bitrix Framework

    Модули

    Модуль — это модель данных и API для доступа к этим данным. Статические методы классов модуля могут вызываться в компонентах, шаблонах, других модулях. Также внутри контекста Bitrix Framework могут создаваться экземпляры классов.

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

    Внимание! На уровне ядра и модулей вмешательство в работу системы крайне не рекомендуется.

    Ядро продукта — файлы, находящиеся в директории /bitrix/modules/, а также файлы системных компонентов: /bitrix/components/bitrix/.

    Компоненты

    Компонент — это контроллер и представление для использования в публичном разделе. Компонент с помощью API одного или нескольких модулей манипулирует данными. Шаблон компонента (представление) выводит данные на страницу.

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

    Чтобы работать с API нужно просто понять структуру компонентов Bitrix Framework.

    Примечание: Модуль — это набор каких-либо сущностей. Компонент — это то, что этими сущностями управляет.

    Посмотрим на примере модуля Инфоблоки. Этот модуль представляет собой совокупность таблиц в базе данных и php-классов, которые могут проводить какие-либо операции с данными из таблиц (например, CIBlockElement::GetList() или CIBlockElement::GetByID ()). Компонентом является уже, например, Новость детально, который имеет собственные настройки (показывать дату, картинку и т.д. и т.п.) и работает с методами php-классов модуля.

    Страница

    Страница представляет из себя PHP файл, состоящий из пролога, тела страницы (основной рабочей области) и эпилога. Формирование страницы сайта производится динамически на основе используемого шаблона страницы, данных выводимых компонентами и статической информации, размещенной на странице.

    Видео по теме:

    • Программная архитектура веб-систем на Битриксе: от простого сайта до веб-кластера 28 минут 36 секунд, 700 Мб
    • Особенности проектирования под Битрикс 37 минут 34 секунды, 605 Мб

    Права доступа

    В системе Bitrix Framework поддерживается два уровня разграничения прав доступа:

    Доступ на файлы и каталоги

    Этот уровень прав проверяется в прологе, задается с помощью специального файла .access.php, содержащего PHP массив следующего формата:

    $PERM[файл/каталог][ID группы пользователей] = "ID права доступа";

    Где:

    • файл/каталог — имя файла или каталога для которых назначаются права доступа;
    • ID группы пользователей — ID группы пользователей на которую распространяется данное право (допустимо также использование символа — *, что означает — для всех групп);
    • ID права доступа — на сегодняшний день поддерживаются следующие значения (в порядке возрастания):
      • D — запрещён (при обращении к файлу доступ будет всегда запрещён);
      • R — чтение (при обращении к файлу доступ будет разрешен);
      • U — документооборот (файл может быть отредактирован в режиме документооборота);
      • W — запись (файл может быть отредактирован непосредственно);
      • X — полный доступ (подразумевает право на «запись» и модификацию прав доступа).

    В административной части сайта права доступа на файлы и каталоги можно назначать с помощью Менеджера файлов.

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

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

    Пример 1

    Файл /dir/.access.php

    <?
       $PERM["index.php"]["2"] = "R";
       $PERM["index.php"]["3"] = "D";
    ?>

    При попытке открытия страницы /dir/index.php пользователь, принадлежащий группе ID=3, будет иметь право доступа D (запрещено), пользователь из группы ID=2 будет иметь право R (чтение). Пользователь, принадлежащий обеим группам, будет иметь максимальный уровень доступа — R (чтение).

    Пример 2

    Файл /.access.php

    <?
       $PERM["admin"]["*"] = "D";
       $PERM["admin"]["1"] = "R";
       $PERM["/"]["*"] = "R";
       $PERM["/"]["1"] = "W";
    ?>

    Файл /admin/.access.php

    <?
       $PERM["index.php"]["3"] = "R";
    ?>

    При доступе к странице /admin/index.php пользователь, принадлежащий группе ID=3, будет иметь доступ, а пользователю, принадлежащему группе ID=2, будет в доступе отказано. При доступе к странице /index.php все посетители будут иметь доступ.

    Права в рамках логики модуля

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

    Если пользователь имеет на файл как минимум право R (чтение) и если данный файл является функциональной частью того или иного модуля, то проверяется 2-ой уровень прав, задаваемый в настройках соответствующего модуля. Например: При заходе на страницу Список обращений в техподдержке администратор видит все обращения, сотрудник техподдержки — только те за которые он ответственнен, а обычный пользователь — только свои обращения. Так работает право доступа в рамках логики модуля Техподдержка.

    Используются две методологии разграничения прав доступа 2-го уровня (уровень прав в рамках логики модуля):

    • права;
    • роли.

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

    Модули, в которых поддерживаются роли, можно увидеть в фильтре Модуль на странице Настройки > Пользователи > Уровни доступа в Административном разделе. Во всех остальных модулях и во всех остальных настройках системы используются права.

    Пример:

    • Права. Если вы принадлежите группам для которых в модуле Статистика заданы права Полный административный доступ и например, Просмотр статистики без финансовых показателей, то вы будете обладать максимальным правом — Полный административный доступ.
    • Роли. Если вы принадлежите к группам для которых в модуле Техподдержка заданы роли Клиент техподдержки и Демо-доступ, то вы одновременно будете обладать возможностями этих двух ролей. Т.е. вы сможете видеть все обращения в режиме демо-доступа и одновременно с этим можете создавать свои обращения как клиент техподдержки.

    Список ссылок по теме:

    • Управление правами доступа в курсе Контент-менеджер
    • Управление доступом в курсе Администратор. Базовый

    Файлы и База данных

    Файлы и База данных

    Вопрос: Почему допускается хранение контента в файловой системе, пусть даже статичного? Не место ли контенту в базе данных?

    Вадим Думбравану: так удобнее, причем как разрабатывать, так и управлять.

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

    Примечание: исполнение PHP это большое преимущество статической страницы Bitrix Framework.

    Файлы можно править как по FTP, так и в SSH, не прибегая к дополнительным инструментам СУБД. Их легко копировать, перемещать, делать резервные копии и т.п. Строго говоря, вы можете весь контент хранить в БД. Но для простых статичных сайтов это будет явное усложнение и замедление.

    Реализация на файлах кажется проблематичной в том плане, что от такой системы ожидается десятки тысяч файлов на диске. Обычно это не так. Динамическая информация (новости, каталог товаров, статьи) сохраняются в БД модулем Информационные блоки. Тогда для вывода, например, десятка тысяч товаров в интернет-магазине используется одна единственная физическая страница (файл). В этом файле вызывается компонент инфоблоков, который в свою очередь выбирает и выводит товары из базы данных.

    Например, для каталога товаров действительно нужно создать папку на диске, но только одну, например, /catalog, поместить туда комплексный компонент и далее страницы товаров могут иметь вид, например: http://***.ru/catalog/1029.html
    Естественно, что эти адреса будут «мнимыми» и обрабатываться системой. Файлов под них в папке /catalog не создается.

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

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

    Размер системы довольно большой, так как в её состав включено множество компонентов необходимых для быстрого старта и работы административной части. Компоненты не консолидированы, потому что система модульная. Модули, компоненты и шаблоны имеют определенную структуру. Это важно и для обновлений системы и для разработки своих компонентов.

    Большое количество файлов — свойство аналогичных систем. (У ZendFramework есть такая же особенность). При правильной конфигурации хостинга эту проблему возьмут на себя прекомпиляторы php. Критичным может оказаться размер выделяемого хостером места и большое число файлов системы. (Проблемой становится не штатная работа Bitrix Framework, а, например, работа систем бэкапов у хостеров. На большом количестве файлов они начинают себя чувствовать не очень хорошо.) Поэтому для выбора хостера рекомендуем пользоваться списком рекомендуемых хостингов.

    Резюме. В качестве инструмента хранения структуры сайта выбрана именно файловая система, а не база данных в силу того что:

    • Файл дает больше свободы разработчику сайта. Поскольку файл в системе — это просто исполняемый файл.
    • Так понятнее для управления. В корне такого представления — структура статических страниц HTML, разложенных по папкам. Путем некоторого совершенствования (внедряя небольшое количество PHP-кода), мы из такого сайта сразу получаем работающий на Bitrix Framework проект.
    • В какой-то мере это — традиция, которая имела большое значение на заре становления CMS.
    • Такое представление соответствует опыту контент-менеджеров, которые работают с локальными файловыми системами (папки и файлы).

    Структура сайта также может быть и в БД (инфоблоки), но управлять иерархией в реляционной БД не очень-то удобно.

    Рассмотрим использование файлов в Bitrix Framework на примерах:

    1. Файловая система и меню. Меню в файлах позволяет не подключать БД там, где это реально не нужно. То же самое относится к свойствам страниц и разделов, а также правам доступа к файлам. Теоретически можно собрать информационный сайт, где вообще не будет ни одного обращения к БД. Будет работать быстрее, особенно на разделяемом хостинге. Есть и бонусы: при копировании раздела сразу естественным образом копируются меню, права доступа, свойства раздела.
    2. Файловая система и пользователи. Пользователям из административного раздела открыт доступ к файлам ядра и другим программным файлам. Но пользователи бывают разные. Например, техподдержка 1С-Битрикса. Если веб-разработчик не уверен в своих пользователях, то он всегда может запретить им как редактирование кода PHP, так и целых разделов (ядра). По современной концепции Bitrix Framework в публичной части не должно быть кода PHP — все должно быть инкапсулировано в компоненты. Тогда пользователь редактирует или «голую» статику, или настраивает компонент.
    3. Файловая система и языковые версии. Было бы трудно сопровождать языковую информацию в БД. Информация в языковых файлах меняется крайне редко — проще раз в год отредактировать строчку в языковом файле, чем хранить эти статические фразы в базе. И повторимся: база данных — это медленно и избыточно.

    Структура файлов

    Файловая структура Bitrix Framework организована таким образом, что программные компоненты ядра продукта были отделены от пользовательских файлов, а также файлов, определяющих внешнее представление сайта. Данная особенность позволяет:

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

    Вся система целиком лежит в каталоге /bitrix/, в него входят следующие подкаталоги и файлы:

    • /admin/ — административные скрипты;
    • /cache/ — файлы кэша;
    • /activities/ — папки действий для бизнес-процессов;
    • /components/ — папка для системных и пользовательских компонентов;
    • /gadgets/ — папки гаджетов;
    • /js/ — файлы javascript модулей;
    • /stack_cache/ — файлы кеша «с вытеснением»;
    • /themes/ — темы административного раздела;
    • /wizards/ — папки мастеров;
    • /images/ — изображения используемые как системой в целом, так и отдельными модулями;
    • /managed_cache/ — управляемый кеш;
    • /modules/ — каталог с модулями системы, каждый подкаталог которого имеет свою строго определённую структуру;
    • /php_interface/ — вспомогательный служебный каталог, в него входят следующие каталоги и файлы:
      • dbconn.php — параметры соединения с базой. С версии 20.900.0 параметры соединения берутся из файла /bitrix/.settings.php;
      • init.php — дополнительные параметры портала;
      • after_connect.php — подключается сразу же после создания соединения с базой;
      • dbconn_error.php — подключается при ошибке в момент создания соединения с базой;
      • dbquery_error.php — подключается при ошибке в момент выполнения SQL запроса;
      • /ID сайта/init.php — дополнительные параметры сайта; файл подключается сразу же после определения специальной константы c идентификатором сайта — SITE_ID;
    • /templates/ — каталог с шаблонами сайтов и компонентов , в него входят следующие подкаталоги:
      • /.default/ — подкаталог с общими файлами, используемыми тем или иным шаблоном по умолчанию, структура данного каталога аналогична нижеописанной структуре каталога содержащего конкретный шаблон;
      • /ID шаблона сайта/ — подкаталог с шаблоном сайта, в него входят следующие подкаталоги и файлы:
        • /components/ — каталог с кастомизированными шаблонами компонентов;
        • /lang/ — языковые файлы принадлежащие как данному шаблону в целом, так и отдельным компонентам;
        • /images/ — каталог с изображениями данного шаблона;
        • /page_templates/ — каталог с шаблонами страниц и их описанием хранящимся в файле .content.php. Когда пользователь создает новую страницу, он может выбрать, по какому шаблону из представленных в этом каталоге это будет сделано;
        • header.php — пролог данного шаблона;
        • footer.php — эпилог данного шаблона;
        • template_styles.css — основной файл стилей для шаблона;
        • styles.css — CSS стили шаблона для визуального редактора (вкладка Стили сайта);
    • /tools/ — при инсталляции в этот каталог копируются дополнительные страницы, которые могут быть непосредственно использованы на любых страницах сайта: помощь, календарь, показ изображения и т.п.;
    • /updates/ — каталог, автоматически создаваемый системой обновлений;
    • header.php — стандартный файл, подключающий в свою очередь конкретный пролог текущего шаблона сайта; данный файл должен использоваться на всех страницах публичной части;
    • footer.php — стандартный файл, подключающий в свою очередь конкретный эпилог текущего шаблона сайта; данный файл должен использоваться на всех страницах публичной части;
    • license_key.php — файл с лицензионным ключом;
    • spread.php — файл используемый главным модулем для переноса куков посетителя на дополнительные домены различных сайтов;
    • redirect.php — файл используемый модулем Статистика для фиксации событий перехода по ссылке;
    • rk.php — файл по умолчанию используемый модулем Реклама для фиксации событий клика по баннеру;
    • stop_redirect.php — файл используемый модулем Статистика для выдачи какого либо сообщения посетителю, попавшему в стоп-лист;
    • activity_limit.php — файл используемый модулем Статистика для выдачи сообщения роботу при превышении им лимита активности;
    • .settings.php — [ds]файл настроек[/ds][di]Bitrix Framework имеет ряд специфичных настроек ядра, которые не имеют визуального интерфейса редактирования. Этот подход вызван тем, что изменение настроек или ошибка в них легко могут привести к неработоспособности системы (настройки подключения к базе данных, настройки кеширования и т.д.).

      Подробнее …[/di] ядра D7.

    • и другие служебные файлы и папки.

    В зависимости от используемой редакции некоторые каталоги и файлы могут отсутствовать.

    Работа с базами данных

    Цитатник веб-разработчиков.

    Антон Долганин:

    Новичкам надо поклясться на мануале, что они забудут про прямые запросы, вообще про БД забудут. Когда появится немного опыта и будет казаться что вы зверь в Битриксе — тогда можно почитать о том как происходит общение с БД, но все равно не трогать, подождать еще год-два активной работы с системой. И вот только потом уже можно подружиться с БД полностью, но помнить, что в первую очередь надо всеми правдами и неправдами сделать стандартными методами Битрикс. К БД прибегать только в случае:

    • Невозможно сделать стандартно без большой нагрузки на PHP;
    • Невозможно сделать стандартно без большой нагрузки на MySQL.

    Разработчик должен точно понимать БД. Понимать что изменится, а что нет при ближайшем обновлении.

    Одним из первых впечатлений, возникающих у начинающего осваивать Bitrix Framework программиста, бывает непонимание запросов к базе данных. Код Bitrix Framework порождает очень большие и не сразу понятные запросы. Несколько экранов не очень понятного SQL традиционно пугают людей, которые редко пишут запросы сложнее select * from ... where id=... и увереных, что объединение таблиц по условиям делается через where так же производительно, как и через on.

    Но всё не так просто. Тема работы с БД включает в себя вопросы:

    • узкотехнологические, вроде производительности конкретных выборок;
    • вопросы удобного применения технологий работы с БД в больших развиваемых проектах на растущей CMS;
    • вопросы стоимости владения и поддержки.

    Почему в Bitrix Framework запрос получается неудобочитаемым?

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

    В мощных СУБД есть умные и очень эффективные оптимизаторы запросов, а совсем плохие запросы такие СУБД даже не дают исполнять. Подавляющее большинство установок продуктов на Bitrix Framework работают на MySQL, чей оптимизатор достаточно слаб. В итоге мы имеем большие запросы, ограниченные средства их изменения с сохранением логики, и механизмы кеширования поверх всего этого.

    Минус такого положения дел

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

    • изменять порядок объединений;
    • задавать специфические условия на выборки;
    • создавать локальные кеширующие временные таблицы.

    Плюсы такого положения дел

    Плюсы оказываются важнее, чем минусы:

    • Запросы не надо писать руками. Создание проекта на Bitrix Framework обычно предусматривает визуальную настройку готовых компонентов, создании структуры сайта и интеграцию дизайна. Не в каждом проекте приходится кастомизировать компоненты, но даже эта работа не заставляет писать запросы.
    • Безопасность. Обертки над запросами решают задачу защиты от атак или глупостей разработчика.

    Квалификация разработчика

    Как и в любом другом деле, всё решает профессионализм работы с Bitrix Framework. И мастерство работы с БД не играет существенной роли. Объективно в большинстве проектов нагрузка на sql (время исполнения) несущественна по сравнению с нагрузкой на процессор и затратами на интерпретацию скриптов. Тормозит не MySQL, а PHP.

    В связи с этим огромную роль играет правильность проектирования структуры данных, выбор связей и их реализация средствами системы инфоблоков или таблиц. Устранить «тормоза» на проекте гораздо правильнее и эффективнее грамотным проектированием, чем оптимизацией запроса, который Bitrix Framework генерирует по вашим указаниям.

    Использование кеширования позволяет сэкономить на времени исполнения запросов. Грамотное распределение логики по компонентам позволяет вообще их не вызывать. Есть правило: главная страница сайта (обычно не самая простая) не должна отправлять запросы к БД при включенном кеше.

    Разработчик должен учитывать специфику проекта и правильно выбирать тип таблиц: InnoDB или MyISAM .

    Разработчик должен уметь оценивать уровень задач. Для действительно серьезных проектов есть кластеризация My SQL.

    Что есть в Bitrix Framework для облегчения жизни простого программиста?

    Перед началом работ по оптимизации проектов воспользуйтесь Монитором производительности для поиска узких мест на сайте.

    В составе административной части системы есть инструменты проверки БД и её оптимизации. Эти действия автоматизированы и вмешательство разработчика в них исключено.

    Используйте инфоблоки 2.0. Это включаемый галочкой в административном разделе режим работы инфоблока, когда его поля переносятся в отдельную таблицу. Но у них есть свои особенности, которые надо знать и учитывать.

    Используйте настраиваемую из административной части кластеризацию БД на стандартных механизмах MySQL.

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

    Цитатник веб-разработчиков.

    Денис Шаромов: Есть миф, что длинный запрос долго исполняется. В действительности, всё зависит от того, какой объем данных приходится обработать базе для исполнения запроса. Разбор собственно запроса занимает ничтожно малую часть времени. Основное время занимает фильтрация и сортировка. Время фильтрации зависит от параметров фильтра и индексов, а сортировка — от числа записей в выборке.

    Работа с БД

    Перед тем как начать работать с БД усвойте, что:

    Внимание! Прямое обращение к базе данных в рамках Bitrix Framework не приветствуется. Более того, если речь идет о системных таблицах самого Bitrix Framework, это не просто не приветствуется, это не поддерживается. Необходимо с ними работать через API системы, так как физическая структура БД может измениться, а работа даже самого древнего API гарантирована.

    В силу этого предупреждения названия таблиц не афишируются. Если, все же, вы возьмёте на себя ответственность прямой работы с базой данных, то учтите, что все таблицы от Bitrix Framework начинаются с b_. Соответственно ваши префиксы должны быть другими: необходимо чтобы имена ваших таблиц не пересеклись с битриксовыми. Вполне возможна ситуация, когда в новых обновлениях появится таблица с таким же именем, как и созданная вами (если вы используете префикс b_). В лучшем случае обновление не установится.

    Для работы с собственными таблицами используйте методы глобальной переменной $DB (класс CDatabase).

    Детально об оптимизации БД читайте в главе Оптимизация базы данных Курса для хостеров.

    Список ссылок по теме:

    • План действий при проблемах
    • Переезжаем на MSSQL
    • Кардинально ускоряем Bitrix+MSSQL (блог)
    • Проблема с deadlock на MSSQL (блог)
    • Пример оптимизации работы MySQL
    • 11 «рецептов приготовления» MySQL в Битрикс24

    Отложенные функции

    Отложенные функции

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

    Технология была создана в первую очередь для использования в компонентах, которые, как правило, выводятся в теле страницы, но при этом внутри них могут быть заданы заголовок страницы, добавлен пункт в навигационную цепочку, добавлена кнопка в панель управления и так далее. Отложенные функции нельзя использовать в файлах шаблона компонента: template.php и result_modifier.php (так как результаты их выполнения кешируются).

    Внутри отложенной функции можно подключать компоненты, но при этом необходимо вручную подключать файлы CSS и js.

    Примечание: Есть ряд новых функций, которые могут работать в условиях кеширования (SetViewTarget, EndViewTarget). Но такие функции новые, ещё не описаны в документации и их надо рассматривать скорее как исключение, чем как правило.

    Алгоритм работы

    1. Любой исходящий поток из PHP скрипта буферизируется.
    2. Как только в коде встречается одна из следующих функций:
      • CMain::ShowTitle,
      • CMain::ShowCSS,
      • CMain::ShowNavChain,
      • CMain::ShowProperty,
      • CMain::ShowMeta,
      • CMain::ShowPanel

      или другая функция, обеспечивающая откладывание выполнения какой-либо функции, то:

      1. весь буферизированный до этого контент запоминается в очередном элементе стека A;
      2. в стек A добавляется пустой элемент, который в дальнейшем будет заполнен результатом выполнения отложенной функции;
      3. имя отложенной функции запоминается в стеке B;
      4. буфер очищается и буферизация снова включается.

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

      Также существует стек B, в котором запоминаются имена и параметры отложенных функции в порядке их следования в коде.

    3. В конце страницы в служебной части эпилога выполняются следующие действия:
      1. все отложенные функции из стека B начинают выполняться одна за другой;
      2. результаты их выполнения вставляются в специально предназначенные для этого места в стек A;
      3. весь контент из стека A «склеивается» (конкатенируется) и выводится на экран.

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

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

    Пример кода, в котором отложенная функция не будет отрабатывать код в шаблоне как ожидается:

    if (!$APPLICATION->GetTitle())
      echo "Стандартная страница";
    else
      echo $APPLICATION->GetTitle();

    А такой код будет работать:

    $APPLICATION->AddBufferContent('ShowCondTitle');
    
    function ShowCondTitle()
    {
      global $APPLICATION;
     if (!$APPLICATION->GetTitle())
        return "Стандартная страница";
     else
        return $APPLICATION->GetTitle();
    }

    Ещё один пример

    $page_title = $APPLICATION->GetPageProperty($title);
    if (strlen($page_title)<=0) $page_title = "Заголовок страницы по умолчанию";
    echo $page_title;

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

    Пример:

    <?
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
    $APPLICATION->SetTitle("Старый заголовок");
    ?>
    <?
    global $APPLICATION;
    $strTitle = $APPLICATION->GetTitle();
    echo $strTitle." - Заголовок страницы

    "; $APPLICATION->SetTitle('Новый заголовок'); ?> <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>

    На странице будет напечатано — Старый заголовок, а в браузере — Новый заголовок.

    Задействованные группы функций

    Технология позволяет создавать отложенные функции с помощью метода CMain::AddBufferContent.

    Список ссылок по теме:

    • Вызов компонента через отложенную функцию. (блог)

    Файл init.php

    Файл init.php

    Цитатник веб-разработчиков.

    Максим Месилов: С течением времени на проекте накапливается ворох «общих» функций, обработчиков событий, специфических библиотек и т.д. Очень часто всё это валят в init.php и там чёрт ногу сломит.

    init.php — необязательный файл в рамках структуры файлов Bitrix Framework. Он автоматически подключается в прологе.

    Файл может содержать в себе инициализацию обработчиков событий, подключение дополнительных функций — общие для всех сайтов. В этом случае он располагается по пути /bitrix/php_interface/init.php. Для каждого отдельного сайта может быть свой аналогичный файл. В этом случае он располагается по пути /bitrix/php_interface/ID сайта/init.php. Если есть оба файла, то система подключит оба, но первым при этом будет файл /bitrix/php_interface/init.php.
    Начиная с версии 14.0.1 рекомендуется размещать этот файл в папке [ds]/local[/ds][di]Чтобы сделать жизнь разработчиков проектов удобнее основные файлы проекта вынесены из папки /bitrix в папку /local. Это позволит изолировать изменяющиеся файлы проекта от папки продукта. По сути, в исключения достаточно будет добавить одну папку /bitrix.

    Подробнее …[/di] по пути /local/php_interface/ID сайта/init.php. Файл /bitrix/php_interface/init.php при этом перестаёт работать.

    Примечание: Файл /bitrix/php_interface/ID сайта/init.php не подключается в административном разделе, так как там отсутствует понятие сайта. Учтите и то, что SITE_ID равен текущему языку и, следовательно, может подключиться не тот файл, который ожидался.

    Код условного отключения по значению $_SESSION в файле init.php не работает, так как эта переменная определяется позже.

    Чтобы init.php не превращался в свалку непонятного кода следует код размещать логически группируя по файлам и классам.

    Рекомендуется придерживаться следующих самых общих правил:

    1. init.php содержит только подключения файлов. Причем подключать эти файлы желательно через __autoload. Штатными средствами это делается так:
      session_start();
      CModule::AddAutoloadClasses(
              '', // не указываем имя модуля
              array(
                 // ключ - имя класса, значение - путь относительно корня сайта к файлу с классом
                      'CMyClassName1' => '/path/cmyclassname1file.php',
                      'CMyClassName2' => '/path/cmyclassname2file.php',
              )
      );
    2. Если функционал используется только на одном из сайтов в системе, то он выносится в свой init.php;
    3. Обработчики событий лучше группировать в одном файле и тщательно аннотировать где они используются и какая задача перед ними стоит.

    Примечание: Перменная $_SESSION не доступна в init.php, так как ядро стартует сессию позже, совершая определенные настройки и проверки. Настоятельно не рекомендуется самостоятельно подключать сессию «руками».

    Проблемы при редактировании

    Как избежать проблем при редактировании init.php без ftp/ssh доступа

    Ошибка в файле init.php приводит к полной потере работоспособности сайта и невозможности что-то исправить без доступа к файлу через ftp/ssh напрямую с диска. Такое возможно, например, в случаях:

    • У клиента хостинг под Windows, а у вас «серый» IP и ftp не работает.
    • Хостинг под Linux, но php и ftp работают из-под разных пользователей и файл недоступен для редактирования по ftp.
    • У клиента свой сервер и доступ по ftp он не дает.

    Если доступ есть только через веб, то один из наиболее простых способов — вынос всего вашего кода во внешний файл и подключение его примерно так:

    if (isset($_GET['noinit']) && !empty($_GET['noinit']))
    {
    	$strNoInit = strval($_GET['noinit']);
    	if ($strNoInit == 'N')
    	{
    		if (isset($_SESSION['NO_INIT']))
    			unset($_SESSION['NO_INIT']);
    	}
    	elseif ($strNoInit == 'Y')
    	{
    		$_SESSION['NO_INIT'] = 'Y';
    	}
    }
    
    if (!(isset($_SESSION['NO_INIT']) && $_SESSION['NO_INIT'] == 'Y'))
    {
    	if (file_exists($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/functions.php"))
    		require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/functions.php");
    }

    Примечание: Параметр в адресной строке noinit=Y — отключает подключение, noinit=N — включает подключение. Свой функционал должен быть размещен (в примере) в /bitrix/php_interface/functions.php.

    Рекомендуется использовать собственное именование ключа. К примеру, nomysuperinit, так как курс в открытом доступе и любой может ознакомиться с этим методом, в том числе и с неблаговидными целями.

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

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

    init.php vs собственный модуль

    У разработчика проектов есть два способа постоянного использования уже созданных наработок: собственный модуль или файл init.php. Оба варианта имеют свои плюсы и минусы.

    init.php. Когда у вас есть классы, единые для нескольких сайтов (при многосайтовости, или на одном сервере), то для удобства сами файлы с классами располагаются в одной папке, далее создаются символические ссылки.

    Либо, при многосайтовости, есть и ещё один способ: воспользоваться уже готовыми папками. Например, папками шаблонов. (Теоретически, с точки зрения системы, это «шаблон», для нас — просто общее хранилище файлов.)

    И при втором и первом способах код include $_SERVER["DOCUMENT_ROOT"]."/bitrix/templates/..." ведет в одно и то же место для всех сайтов, где можно разместить общие для всех проектов файлы. В том числе никто не мешает создать свою папку для своих классов и уже оттуда их подключать, обращаясь к /bitrix/templates/my_classes/....

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

    Модули. Использование init.php будет предпочтительным если создаются проекты, которые точно всю жизнь проживут на одном сервере, и вы хотите максимально минимизировать затраты на поддержку кастомных библиотек. Желательно также чтобы эти библиотеки поддерживал один и тот же человек. Но если планируется эти наработки также использовать для заказных проектов, то дальновиднее делать модуль.

    Модуль больше подходит для распространяемых API. При этом придется поддерживать и их интерфейс и формат ответа и другие параметры. Иначе: случайное обновление на одном из сайтов, использующих модуль, может оказаться роковым в случае изменения интерфейсов или форматов ответов API. Выигрыш спорный. С одной стороны — выигрываете, с другой — должны обеспечить пожизненные гарантии для всех использующих API модуля сайтов.

    Языковые файлы

    Цитатник веб-разработчиков.

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

    Языковой файл — представляет из себя PHP скрипт, хранящий переводы языковых фраз на тот или иной язык. Данный скрипт состоит из массива $MESS, ключи которого — идентификаторы языковых фраз, а значения — переводы на соответствующий язык.

    Языковые файлы не являются обязательными.

    Пример языкового файла для русского языка:

    <?
    $MESS ['SUP_SAVE'] = "Сохранить";
    $MESS ['SUP_APPLY'] = "Применить";
    $MESS ['SUP_RESET'] = "Сбросить";
    $MESS ['SUP_EDIT'] = "Изменить";
    $MESS ['SUP_DELETE'] = "Удалить";
    ?>

    Пример языкового файла для английского языка:

    <?
    $MESS ['SUP_SAVE'] = "Save";
    $MESS ['SUP_APPLY'] = "Apply";
    $MESS ['SUP_RESET'] = "Reset";
    $MESS ['SUP_EDIT'] = "Change";
    $MESS ['SUP_DELETE'] = "Delete";
    ?>

    Примеры работы

    • просмотр всего массива словаря:
      <? echo'<pre>';print_r($MESS);echo'</pre>'; ?>
      
    • получение названия месяца в двух падежах:
      <?
         echo $MESS['MONTH_'.date('n')]; // Июнь
         echo $MESS['MONTH_'.date('n').'_S']; // Июня
      ?>
      

    Работа с языковыми файлами

    Файлы в собственных компонентах

    При создании собственного компонента языковой путь должен выглядеть следующим образом:

    /bitrix/templates/[шаблон_сайта|.default]/components/[пространство_имен]/[имя_компонента]/[имя_шаблона_компонента]/lang/[код_языка]/template.php

    Где код языка, к примеру = ru.

    В таком случае языковые файлы подключатся автоматически.

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

    function IncludeComponentLangFile ($abs_path, $lang = false)
    {
    if ($lang === false) $lang = LANGUAGE_ID;
    
    global $BX_DOC_ROOT;
    
    $filepath = rtrim (preg_replace ("'[\\/]+'", "/", $abs_path), "/ ");
    
    if (strpos ($filepath, $BX_DOC_ROOT) !== 0)
    {
    return;
    }
    
    $relative_path = substr ($filepath, strlen ($BX_DOC_ROOT));
    
    if (preg_match ("~^/bitrix/components/([-a-zA-Z0-9_.%]+)/([-a-zA-Z0-9._%]+)/templates/([-a-zA-Z0-9._%]+)/(.*)$~", $relative_path, $matches))
    {
    $lang_path = $BX_DOC_ROOT."/bitrix/components/$matches[1]/$matches[2]/templates/$matches[3]/lang/$lang/$matches[4]";
    __IncludeLang ($lang_path);
    return;
    }
    
    if (preg_match ("~^/bitrix/components/([-a-zA-Z0-9_.%]+)/([-a-zA-Z0-9._%]+)/(.*)$~", $relative_path, $matches))
    {
    $lang_path = $BX_DOC_ROOT."/bitrix/components/$matches[1]/$matches[2]/lang/$lang/$matches[3]";
    __IncludeLang ($lang_path);
    return;
    }
    }
    

    Замена фраз в продукте

    Иногда при разработке требуется заменить какие-то слова или фразы в компонентах или модулях.

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

    Путь к файлу замен:

    /bitrix/php_interface/user_lang/<код языка>/lang.php

    Примечание: если в php_interface нет нужных папок, их следует создать.
    Возможно использование папки /local/php_interface/user_lang/<код языка>/lang.php.

    В файле должны определяться элементы массива $MESS в виде $MESS['языковой файл']['код фразы'] = 'новая фраза', например:

    <?
    $MESS["/bitrix/components/bitrix/system.auth.form/templates/.default/lang/ru/template.php"]["AUTH_PROFILE"] = "Мой любимый профиль";
    $MESS["/bitrix/modules/main/lang/ru/public/top_panel.php"]['top_panel_tab_view'] = "Смотрим";
    $MESS["/bitrix/modules/main/lang/ru/interface/index.php"]['admin_index_sec'] = "Проактивка";
    ?>
    

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

    Важно! Возможны проблемы сопровождения этого файла при изменении кода фразы или расположения языкового файла.

    Языковые фразы в D7

    По умолчанию подстановка языковых фраз не работает в файле component_epilog.php шаблона компонента. Поэтому в нем языковые фразы подключаются следующим образом:

    use BitrixMainLocalizationLoc;
    Loc::loadLanguageFile(__FILE__);
    echo Loc::getMessage("SOMETHING_LANGUAGE_CONSTANT"); 
    

    Сами языковые фразы, например, для русского языка должны быть заданы в файле lang/ru/component_epilog.php. Кроме того, при таком подключении языковые фразы будут работать как в кеше, так и «мимо» кеша.

    Гаджеты и их создание

    Гаджет – особый программный элемент, выполняющий функцию вывода определенных данных.

    Об имеющихся гаджетах, их настройке и управлении ими, вы можете прочитать в курсах:

    • Контент-менеджер;
    • Администратор Корпоративного портала.

    Для отображения гаджетов используется компонент Рабочий стол (Desktop). Этот одностраничный компонент позволяет создать настраиваемый рабочий стол с использованием гаджетов. В компоненте гаджеты инсталлируются с главным модулем системы. Разработчики могут создавать собственные гаджеты. Компонент их увидит, если они будут размещаться в папке /bitrix/gadgets/. (Название папки гаджета обязательно должно быть в нижнем регистре.) Системные гаджеты располагаются во вложенной папке /bitrix/.

    Внимание! Категорически не рекомендуется без крайней необходимости трогать структуру системных гаджетов.

    Структура гаджета

    • .description.php — файл описания, содержащий массив параметров;
    • .parameters.php — файл с настройками. В данном файле формируется массив $arParameters. Особое внимание стоит обратить на ключи PARAMETERS (общие настройки гаджета) и USER_PARAMETERS (уникальные настройки конкретного пользователя);
    • файл index.php, который содержит исполняемый код, реализующий задачу гаджета;
    • языковые файлы в папке /lang/.
    • другие служебные файлы и папки.

    Гаджеты и компоненты имеют похожую структуру и назначение, однако:

    • Гаджеты не используют шаблоны. HTML код зашит в файле index.php, в отличие от компонентов, где представление и логика разнесены.
    • Гаджеты могут запоминать настройки для каждого пользователя, в отличие от компонентов, которые могут только выводить или не выводить информацию в зависимости от прав доступа.
    • Настройки у гаджетов разделены на 2 группы: общие настройки, для гаджетов одного типа (например, для всех гаджетов Новости в рамках одного Рабочего стола), а также настройки каждого конкретного гаджета. Общие настройки задаются в компоненте Рабочий стол. Индивидуальные настройки задаются в контекстном меню каждого конкретного гаджета.

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

    Служебные параметры гаджетов

    Служебные параметры недоступны настройке через интерфейс.

    Параметр Описание
    NAME Название
    DESCRIPTION Описание
    ICON Иконка (указать путь)
    GROUP Группа, к которой относится гаджет. Указание группы определяет где в выпадающем списке гаджетов он будет показан
    SU_ONLY Только для подключения у пользователя.
    SG_ONLY Только для подключения в группах.
    BLOG_ONLY Только для подключения в блогах.
    AI_ONLY Только для подключения в административном разделе.
    NOPARAMS Нет параметров

    Список ссылок по теме:

    • Пример создания гаджета (блог).

    JS-библиотека

    Цитатник веб-разработчиков.

    Рамиль Юналиев: Почему-то многие неохотно используют битриксовую библиотеку аякса, почти всегда делая предпочтения в сторону Jquery или других js библиотек. Несомненно то, что Jquery очень неплохая библиотека и отлично работает с ajax, но чтобы свободно разрабатывать сайты на 1с-Битрикс надо знать родное API.

    Документация по JS-библиотеке Bitrix предполагает уверенные знания разработчика в JavaScript.

    Javascript-библиотека Битрикс очень разнообразна. Основным (главным) классом является класс BX. Также к услугам разработчика представлено достаточно большое количество расширений. Например, расширение по работе с датами.

    При работе с JS в Bitrix Framework можно использовать как штатную библиотеку, так и сторонние библиотеки. При использовании сторонних библиотек, их предварительно необходимо подключить. Подробнее про подключение внешнего JS-кода вы можете прочитать тут.

    Внимание! С версии главного модуля 22.0 прекращена поддержка IE в core.js.

    Список ссылок по теме:

    • Документация по JS-библиотеке Bitrix Framework
    • Как правильно организовать хранение языковых фраз для javascript-кода
    • О том, как не нужно работать с JavaScript и jQuery
    • Современный учебник JavaScript

    Подключение JS-кода

    Варианты расположения кода

    Перед написанием JS-кода встает резонный вопрос — где его хранить?

    Существуют следующие варианты:

    • Если вы разрабатываете JS-код для компонента и данный код больше нигде не применяется, разумнее расположить файл script.js в шаблоне самого компонента.
    • Если JS-код общий для всей публичной части, разумнее его разместить в шаблоне сайта. Как правило, такие JS-файлы хранятся в подпапке шаблона js/ и подключаются в самом шаблоне методом: BitrixMainPageAsset::getInstance()->addJs(); Но этот путь не подходит, если шаблонов сайта несколько, или вы разрабатываете код для административной части, или публичный шаблон запрещено менять по причине его обновлений компанией Битрикс (например, коробочный шаблон Битрикс24).
    • В случае, описанном выше, применяется метод хранения файлов по пути /local/file.js. Этот же метод размещения стоит избрать в случае создания вами своего модуля.

    Регистрация и подключение библиотек

    Остановимся поподробнее на последнем варианте. Естественно, вы можете также разместить код в шаблоне сайта через BitrixMainPageAsset::getInstance()->addJs();. Но более правильным решением будет третий подход.

    Каждый файл в вашей папке по сути является отдельной мини-библиотекой, которую надо зарегистрировать. [dw]Регистрация[/dw][di]Регистрация библиотек в include.php модуля или в init.php.[/di] осуществляется с помощью следующего кода:

    $arJsConfig = array( 
        'custom_main' => array( 
            'js' => '/bitrix/js/custom/main.js', 
            'css' => '/bitrix/js/custom/main.css', 
            'rel' => array(), 
        ) 
    ); 
    
    foreach ($arJsConfig as $ext => $arExt) { 
        CJSCore::RegisterExt($ext, $arExt); 
    }
    

    Как видите, код универсален, и можно зарегистрировать несколько файлов. В ключе CSS вы дополнительно можете указать CSS-файл (в случае если CSS-код идет в пару к JS), а ключом rel перечислить коды других BX-библиотек, которые будут автоматически подключены при подключении этой библиотеки.

    Когда библиотеки зарегистрированы, их можно подключить с помощью следующей конструкции:

    CUtil::InitJSCore(array('custom_main'));
    

    Два указанных выше блока кода иногда идут вместе, иногда — раздельно. Например, в случае разработки собственного модуля регистрацию надо совершать в include.php модуля (к примеру), а инициировать (вызывать InitJSCore) в нужном вам месте (например, в шаблоне компонента).

    JS-класс к шаблону компонента

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

    if (typeof(BX.CrmQuickPanelEdit) === 'undefined')
    {
       BX.CrmQuickPanelEdit = function(id)
       {
          this._id = id;
          this._settings = {};
          this._submitHandler = BX.delegate(this._clickHandler, this);
          BX.bind(BX(this._id + '_submit'), 'click', this._submitHandler);
       };
       BX.CrmQuickPanelEdit.prototype =
       {
          initialize: function(id, settings)
          {
             this._id = id;
             this._settings = settings;
          },
          getId: function()
          {
             return this._id;
          },
          _clickHandler: function(e)
          {
             console.log(e);
          }
       };
       BX.CrmQuickPanelEdit.create = function(id, settings)
       {
          var _self = new BX.CrmQuickPanelEdit(id);
          _self.initialize(id, settings);
          return _self;
       };
    }

    Аналогичные подходы вы можете видеть в JS-ядре Bitrix Framework (расположен в /bitrix/js/). Разобравшись в данном примере вы сможете легче понимать JS-код разработчиков Битрикс.

    Пример вызова:

    <script type="text/javascript">
       BX.ready(function(){
          BX.CrmQuickPanelEdit.create('some_id', null);
       });
    </script>

    Если нужен паттерн «одиночка», то реализовать это можно как часть create:

       BX.CrmQuickPanelEdit._self  = null;
       BX.CrmQuickPanelEdit.create = function(id, settings)
       {
          if (!this._self) {
             this._self = new BX.CrmQuickPanelEdit();
          }
          this._self.initialize(id, settings);
          return this._self;
       };

    Что делает JS-класс: запоминает внутри себя некий ID (например, это может быть ID контейнера) и массив параметров, а также вешает обработчик на событие клика по кнопке подтверждения в форме указанного контейнера.

    JS-расширение медиаплеера

    Примеры работы на js с плеером

    Не забываем подключить расширение:

    CJSCore::Init(['player']);

    Создание и инициализация плеера

    Здесь самое важное — передать mime-type для каждого файла. В данном примере плеер будет проходить по всем файлам из списка и проиграет первый, который сможет. Поэтому в списке должен быть один и тот же ролик с разными расширениями.

    BX.ready(function()
    {
        var player = new BX.Fileman.Player('player_id', {
            sources: [
                {
                    src: 'https://dev.1c-bitrix.ru/download/files/video/learning/hermitage.mp4',
                    type: 'video/mp4'
                }
            ]
        });
        var playerNode = player.createElement();
        BX('player_node').appendChild(playerNode);
        player.init();
    });

    Описание параметров плеера

    Параметр Тип Описание
    sources array Массив файлов для проигрывания
    autostart bool Запускать автоматически или нет
    hasFlash bool Установить true, если придётся проигрывать *.flv файлы. В этом случае плеер подгрузит swf-файл плеера.
    playbackRate float От 0 до 3. Скорость проигрывания (работает не всегда).
    volume float От 0 до 3. Громкость
    startTime int Время в секундах, с которого надо начать проигрывание
    onInit function Вызовется сразу после инициализации плеера
    lazyload bool Если true — плеер будет инициализироваться только при нахождении на экране пользователя.
    skin string Название класса-скина. CSS-файл надо предварительно загрузить самостоятельно.
    width int Ширина плеера
    height int Высота плеера
    isAudio bool Установить true, если плеер нужен для проигрывания аудио.

    После создания получить объект плеера можно через менеджер

    var player = BX.Fileman.PlayerManager.getPlayerById('player_id'); 

    Некоторые полезные методы

    Метод Описание
    player.createElement(); Создает html-ноду и возвращает её.
    player.isPlaying(); Возвращает true, если плеер сейчас проигрывает что-то.
    player.pause(); Ставит проигрывание на паузу.
    player.isEnded(); Возвращает true, если файл был проигран до конца.
    player.isReady(); Возвращает true, если плеер полностью инициализирован.
    player.play(); Запускает проигрывание.
    player.setSource({
    src: 'path',
    type: 'mime-type'
    });
    Устанавливает источник проигрывания
    player.getSource(); Возвращает текущий источник
    player.init(); Выполняет инициализацию плеера.
    player.mute(status); Включает / выключает звук

    Пример создания и инициализации аудио плеера

    BX.ready(function()
    {   
    	var audioPlayer = new BX.Fileman.Player('audio_player_id', {
            isAudio: true,
            sources: [
                {
                    src: '/upload/SampleAudio_0.7mb.mp3',
                    type: 'audio/mp3'
                }
            ],
            onInit: function(player)
            {
                // следующие три строки нужны, чтобы скрыть кнопку разворачивания на весь экран
            	player.vjsPlayer.controlBar.removeChild('timeDivider');
                player.vjsPlayer.controlBar.removeChild('durationDisplay');
                player.vjsPlayer.controlBar.removeChild('fullscreenToggle');
                // это прячет большую кнопку плей
                player.vjsPlayer.hasStarted(true);
            }
        });
        var audioPlayerNode = audioPlayer.createElement();
        BX('audio_player_node').appendChild(audioPlayerNode);
        audioPlayer.init();
    });

    Примеры кастомизации публичной части

    Описание

    Задачи добавления функционала в визуальную часть действующего проекта — частая просьба клиента. Например, просьба добавить кнопку в карточку задачи, группы или сотрудника. Для «1С-Битрикс: Управление сайтом» более приемлем классический способ копирования и кастомизации шаблона компонента. Кастомизация визуальной части проекта, реализованная с помощью JS, более практична для решения задач в коробочной версии Битрикс24, хотя может использоваться и в БУС тоже.

    Использование для этих целей JS вместо кастомизации шаблона, имеет свои особенности:

    1. Возможна задержка вывода контента при построении страницы.
    2. Зависимость от обновлений визуальной части вендором.

    В каждом конкретном случае разработчик самостоятельно решает какой способ добавления визуального функционала (JS или классический) предпочтителен.

    Добавляемый JS-код хранится в рамках системы в нескольких местах, но при кастомизации визуальной части лучше использовать место для файлов по пути: /bitrix/js/<ваша уникальная папка>/file.js.

    Кнопка в карточке задачи

    Добавить кнопку в карточку задачи

    Заказчик поставил задачу: Поставить кнопку Скачать в PDF в карточке задачи.

    После выбора места для кнопки находим конкретного «соседа» к которому нужно привязаться при внедрении кнопки. В нашем примере место для кнопки — рядом с кнопкой Редактировать, значит ищем класс элемента-соседа:

    Нажмите на рисунок, чтобы увеличить

    Далее используем код:

    //код исполняем, только когда DOM загружен 
    BX.ready(function(){ 
        var editButton = BX.findChild(//найти пасынков... 
                                BX('task-view-buttons'),//...для родителя 
                                {//с такими вот свойствами 
                                    tag: 'a', 
                                    className: 'task-view-button edit' 
                                }, 
                                true//поиск рекурсивно от родителя 
                            ); 
        if (editButton)  
        { 
            var href = window.location.href, matches, taskId; 
            //узнаем id задачи из URL 
            if (matches = href.match(//task/view/([d]+)//i)) { 
                taskId = matches[1]; 
            } 
            //создаем кнопку 
            var newButton = BX.create('a', { 
                attrs: { 
                    href: href + (href.indexOf('?') === -1 ? '?' : '&') + 'task=' + taskId + '&' + 'pdf=1&sessid=' + BX.bitrix_sessid(), 
                    className: 'task-view-button edit webform-small-button-link task-button-edit-link' 
                }, 
                text: 'Скачать как PDF' 
            }); 
            //вставляем кнопку 
            BX.insertAfter(newButton, editButton); 
        } 
    });

    Допускается такой способ создания кнопки:

    Добавление в меню

    Аналогичную команду Скачать как PDF внедряем в меню Ещё карточки задачи.

    Задача решается с помощью обработчика:

    //код исполняем, только когда DOM загружен 
    BX.ready(function(){ 
        BX.addCustomEvent('onPopupFirstShow', function(p) { 
            var menuId = 'task-view-b'; 
            if (p.uniquePopupId === 'menu-popup-' + menuId) 
            { 
                var menu = BX.PopupMenu.getMenuById(menuId), 
                    href = window.location.href,  
                    matches, taskId;             
                //узнаем id задачи из URL 
                if (matches = href.match(//task/view/([d]+)//i)) { 
                    taskId = matches[1]; 
                } 
                //добавляем пункт меню, полученному по id 
                menu.addMenuItem({ 
                    text: 'Скачать как PDF', 
                    href: href + (href.indexOf('?') === -1 ? '?' : '&') + 'task=' + taskId + '&' + 'pdf=1&sessid=' + BX.bitrix_sessid(), 
                    className: 'menu-popup-item-create' 
                }); 
            } 
        }); 
    });

    Добавление пункта в меню предпочтительнее, чем использование кнопки. В этом случае при построении DOM не будет «миганий» и «дёрганий».

    Узнать ID popup’а можно так:

    Нажмите на рисунок, чтобы увеличить

    Внедрение в меню — частный случай внедрения в popup. Общий пример выглядит так:

    //код исполняем, только когда DOM загружен 
    BX.ready(function(){ 
        BX.addCustomEvent('onPopupFirstShow', function(p) { 
            if (p.uniquePopupId === 'task-templates-popup-templateselector') 
            { 
                p.contentContainer.innerHTML = 'blabla'; 
            } 
        }); 
    });

    Результат такого внедрения:

    Замена действий

    Например, при нажатии на кнопку Завершить должен появиться popup для введения обязательного комментария.

     //метод, вызываемый при клике
        var completeAction = function(){ 
            var popup = new BX.PopupWindow('customComplete', BX.proxy_context, { 
                darkMode: true, 
                closeByEsc : true, 
                contentColor: 'white' 
            }); 
            popup.setContent('Введите сначала комментарий: <input type="text">'); 
            popup.show(); 
        }; 
    
      //селектор места вставки
        var completeButton = BX.findChild(//найти пасынков... 
                                BX('task-view-buttons'),//...для родителя 
                                {//с такими вот свойствами 
                                    tag: 'span', 
                                    className: 'task-view-button complete' 
                                }, 
                                true//поиск рекурсивно от родителя 
                            ); 
    
    if (completeButton)  
        { 
            //сначала вставляем свою кнопку 
            BX.insertAfter(BX.create('span', { 
                attrs: { 
                    href: '#', 
                    className: 'task-view-button complete webform-small-button webform-small-button-accept' 
                }, 
                events: { 
                    click: BX.proxy(completeAction, this) 
                }, 
                text: 'Завершить' 
            }), completeButton); 
            //затем удаляем старую 
            BX.remove(completeButton); 
        } 

    Серверный контроль

    При изменении визуальной части необходим контроль со стороны сервера так как: DOM может не успеть подгрузиться, задачу закрыли из другого места, другие ситуации. Это необходимо предусмотреть и вывести ошибку. Сделайте, например, так:

    <?php 
    AddEventHandler('main', 'onProlog', function(){ 
        $request = BitrixMainContext::getRequest(); 
        if ($request->get('pdf_download')) 
        { 
            // 
        } 
    });

    Форматирование дат в Javascript

    В js-библиотеке есть расширение core_date.js, позволяющее форматировать дату.

    Подключение в PHP

    CJSCore::Init("date");

    Вызов в Javascipt

    BX.date.format("формат", дата);

    формат — полный аналог формата функции date, за исключением формата T и e (символьное название таймзоны). Также поддерживаются расширенные форматы функции FormatDate.

    Если есть символы, которые не должны быть отформатированы, то необходимо эти символы оформить слешами.

    BX.date.format("H:m:s \m \i\s \m\o\n\t\h")

    Если формат начинается с символа ^, то функция обрежет нули. Примеры:

    15.04.12 13:00:00 => 15.04.12 13:00
    00:01:00 => 00:01
    4 may 00:00:00 => 4 may
    01-01-12 00:00 => 01-01-12

    дата — это либо timestamp в секундах (тип Number), либо объект класса Date. Иначе по умолчанию текущее время (new Date()). Примеры:

    BX.date.format("d-m-Y H:i:s");
    BX.date.format("j F Y H:i:s");
    BX.date.format("^d-m-Y H:i:s");
    BX.date.format("H:m:s \m \i\s \m\o\n\t\h");
    BX.date.format("Hago | dago | sago | iago | mago | Yago", new Date(2007, 2, 1, 0, 0, 0));
    BX.date.format("sago | iago", 1320271200);

    Также как и FormatDate, BX.date.format может принимать массив форматов для вычисление вида «1 секунда назад», «2 минуты назад» и так далее.

    var format = [
       ["tommorow", "tommorow, H:i:s"],
       ["s" , "sago"],
       ["H", "Hago"],
       ["d", "dago"],
       ["m100", "mago"],
       ["m", "mago"],
       ["-", ""]
    ];
    BX.date.format(format, new Date(2007, 2, 1, 0, 0, 0));

    Массив format состоит из элементов вида ["интервал формата", "формат"], где интервал формата определяет на каком интервале времени применится формат.

    Массив format обрабатывается последовательно до первого совпадения.

    Интервал времени определяется между указанной датой (первый параметр в BX.format.date) и текущей датой.

    Стандарные значения «интервал формата»
    s до 60 секунд
    i до 60 минут
    H до 24 часов
    d до 31 дня
    m до года
    sN до N секунд *
    iN до N минут *
    HN до N часов *
    dN до N дней *
    mN до N месяцев *
    today сегодня
    yesterday вчера
    tommorow завтра
    дефис означает дату в будущем

    * — где N — это любое положительное число.

    Рассмотрим пример:

    var format = [
       ["-", "d.m.Y H:i:s"]
       ["s300" , "sago"],
       ["H", "Hago"],
       ["d", "dago"],
       ["m", "mago"]
    ];
    
    BX.date.format(format, new Date(2007, 2, 2, 9, 58, 0), new Date(2007, 2, 2, 10, 0, 0)); //1
    BX.date.format(format, new Date(2007, 2, 2, 0, 0, 0), new Date(2007, 2, 2, 10, 0, 0)); //2
    BX.date.format(format, new Date(2007, 2, 1, 0, 0, 0), new Date(2007, 2, 2, 10, 0, 0)); //3
    BX.date.format(format, new Date(2007, 2, 3, 0, 0, 0), new Date(2007, 2, 2, 10, 0, 0)); //4

    Третий параметр в BX.date.format — это текущая дата (описание смотри ниже).

    1. «120 секунд назад», сработало [«s300» , «sago»], т.к интервал времени меньше 300 секунд
    2. «10 часов назад», сработало [«H», «Hago»], т.к интервал времени меньше 24 часов
    3. «1 сутки назад», сработало [«d», «dago»], т.к интервал времени меньше 31 дня
    4. «03.03.2007 00:00:00» сработало [«-«, «d.m.Y H:i:s»], так указана дата в будущем

    Для значения по умолчанию последним элементом можно указать пустой «интервал формата»:

    var format = [
       ["s" , "sago"],
       ["H", "Hago"],
       ["d", "dago"],
       ["m", "mago"],
       ["", "d.m.Y H:i:s"]
    ];
    BX.date.format(format, new Date(2007, 2, 1, 0, 0, 0));

    Полная сигнатура BX.date.format

    BX.date.format("формат", дата, текущее время, utc);

    текущая дата — дата (timestamp в секундах, либо объект класса Date), которая используется для вычислений типа «1 секунда назад», «2 года назад». Если не задано, по умолчанию new Date().

    utc — дата в UTC. По умолчанию false. Если необходимо работать с датами в UTC.

    Небольшая шпаргалка для соответствия серверного и клиентского кода:

    time() = new Date()
    mktime(...) = new Date(...)
    gmmktime(...) = new Date(Date.UTC(...))
    mktime(0,0,0, 1, 1, 1970) != 0          new Date(1970,0,1).getTime() != 0
    gmmktime(0,0,0, 1, 1, 1970) == 0        new Date(Date.UTC(1970,0,1)).getTime() == 0
    date("d.m.Y H:i:s") = BX.date.format("d.m.Y H:i:s")
    gmdate("d.m.Y H:i:s") = BX.date.format("d.m.Y H:i:s", null, null, true);

    BX.date.convertBitrixFormat

    Функция конвертирует битрикс-формат даты в формат функции date. Форматы даты текущего сайта можно получить так:

    BX.message("FORMAT_DATE");
    BX.message("FORMAT_DATETIME");

    Типовые ошибки и советы

    Небольшой список типовых ошибок и советов по работе с JS-библиотекой.

    • Не используйте старые библиотеки utils.js, ajax.js и chttprequest.js.
    • Избегайте использования BX.findChild и BX.findChildren. Функции эти не страшные, но из-за неправильного использования и большого DOM’а, количество итераций возрастает до нескольких тысяч. Лучше используйте выборки по ID или по firstChild, parentNode, nextSibling, previousSibling.
    • Избегайте BX.loadScript и BX.loadCSS. Они плохи тем, что:
      • Не работает сжатие JS и CSS
      • loadScript повторно выполняет скрипт. В этом примере
        BX.loadScript("script.js"); BX.loadScript("script.js");

        первый вызов скачает и выполнит скрипт, второй вызов выполнит скрипт повторно.

      • loadScript грузит скрипты последовательно (скрипты, подключенные через тег script, грузятся параллельно), делая задержки в 50 мс между загрузками. Если вы грузите 5 файлов, то в лучшем случае это займет 200мс.
      • Загрузка CSS вызывает пересчет стилей
      • Практически везде, где используется loadScript, разработчики не добавляют timestamp после названия файла (my_script.js?12345678). Из-за этого происходит двойное скачивание и выполнение скрипта. Плюс при изменении файла, у клиентов не сбросится кеш.

      Выход: использовать методы AddHeadScript и SetAdditionalCSS.

      Еще один момент, который может показаться очевидным. Браузер гарантирует подключение и выполнение скриптов в той последовательности, в которой они идут на странице. Скрипты с атрибутами async и defer не подчиняются этому правилу, но в Bitrix Framework таких нет. Не нужно проверять объект на существование перед использованием и уж тем более не стоит его загружать через loadScript.

    • Не рекомендуется Inline CSS (имеется в виду теги link и style, выводимые в body).
    • Не делайте больших inlinе скриптов. Основной код желательно выносить во внешние файлы. Данные из PHP — вносить JSON’ом. Хороший вариант, когда на странице только вызов конструктора или Init’а c передачей данных в JSON’е.
    • Внимательно используйте setTimeout. Достаточно часто таймауты используются необоснованно. Например, если не работает код в браузере, то часто прибегают к установке таймаута «на глаз». Это происходит из-за недопонимая событийной модели.

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

    • Используйте «ленивую» инициализацию (загрузку). Создавайте объекты, верстку, окна и прочее только тогда, когда это действительно нужно. Например, нет смысла заранее создавать BX.PopupWindow (а он вставляет в DOM новые узлы) до того момента, когда это действительно нужно (нажатие на ссылку или кнопку). Если интерфейс используется редко и некритично показать/не показать процесс загрузки, то лучше сделать ajax’ую подзагрузку.
    • Глобальные переменные используйте минимально, локальные переменные используйте с var.
    • С осторожностью относитесь к глобальным обработчикам (window, document, document.body). Этот код вызывается на каждый клик, на каждое движение мыши, на каждое изменение скроллинга. И не забывайте делать unbind.
    • Делайте один appendChild вместо нескольких в цикле. Еще посмотрите что такое DocumentFragment.
    • Объединяйте и разделяйте CSS/JS. Здесь нужно руководствоваться здравым смыслом. Есть компоненты, которые содержат множество мелких скриптов и CSS-файлов. Имеет смысл их объединить в один файл.
      Есть и обратные примеры. Например, в создаваемом вами модуле есть большой CSS-файл, который содержит почти все стили для своего отображения. И это лучше, чем множество мелких (отдельно на таблицу, тулбар, попапы и фильтры). Но вот появилась новая задача — добавить в живую ленту небольшой блок из вашего модуля. В этом случае не нужно тащить в живую ленту весь файл CSS из модуля, лучше сделать небольшой отдельный файл.
    • Создайте защиту от двойного подключения на странице, так как компонент может быть подключен на странице несколько раз.
    • Для замера производительности запускайте профилировщики Chrome и Internet Explorer’а.
    • Проверяйте свой код в PhpStorm‘е или аналогичных инструментах.

    Расширения (extensions)

    Расширение (экстеншн, extension) — способ организации JS и CSS кода в продуктах 1С-Битрикс: Управление Сайтом и Битрикс24.

    Расположение расширений

    В продукте:

    bitrix/js/<module>/<extension>
    bitrix/modules/js/<module>/<extension>
    local/js/<module>/<extension>
    

    Примечание: в директории bitrix расположены только те экстеншны, которые поставляются с продуктами 1С-Битрикс: Управление Сайтом и Битрикс24. Клиентские экстеншны должны располагаться в папке local.

    Структура

    • myextension
      • src — исходные файлы
      • dist — [dw]бандлы[/dw][di]
        Бандл (bundle, комплект/набор) — это совокупность каких-либо программных данных (файлов), объеденных по какому-либо признаку. В данном случае бандлы — это обработанные и объединенные исходные файлы.
        [/di] для браузера
      • bundle.config.js — конфигурационный файл для сборщика
      • config.php — конфигурационный файл экстеншна
      • lang — локализации
      • test — тесты
      • @types — файлы *.d.ts

    Обязательные элементы: src, dist, bundle.config.js, config.php.

    Необязательные элементы (директории): lang, test, @types.

    Если у вас установлен @bitrix/cli, то структуру расширения можно [ds]создать[/ds][di]
    Для создания расширения («экстеншна»):

    1) Перейдите в директорию local/js/{module}

    2) Выполните команду bitrix create

    3) Ответьте на вопросы мастера

    Подробнее…[/di], выполнив команду bitrix create.

      src

      В директории src следует располагать исходные файлы в формате ES6. Из файлов в данной директории на основании данных файла конфигурации и внутренних ссылок будут созданы финальные версии для подключения в браузере (бандлы) в формате ES5.

      Внутри файла вы можете использовать import других файлов из текущей директории или импортировать другие CoreJS расширения.

      Для импорта переменных и классов из другого файла в текущей директории используйте следующий синтаксис:

      • если в папке src есть файл file.js и в нем есть экспортируемый класс SomeClass:
        import {SomeClass} from "./file";
        
      • для импорта file.css:
        import './file.css';
        
      • для импорта CoreJS 2.0:
        import {Loader} from 'main.loader';
        
      • для импорта библиотеки из CoreJS 1.0:
        import "main.date";
        

      dist

      В директории dist располагаются файлы, автоматически созданные с помощью сборщика для последующего подключения в браузере. Обычно это файлы <extension>.bundle.js и <extension>.bundle.css.

      bundle.config.js

      Файл конфигурации сборщика.

      • Базовая конфигурация:
        module.exports = {
            input: './src/app.js', 
            output: './dist/app.bundle.js',
        };
        

        Обратите внимание: в данном файле конфигурации не указываются CSS файлы, их необходимо импортировать в файле input.

      • Все параметры:
        module.exports = {
        	// Файл, для которого необходимо выполнить сборку. 
        	// Обычно это './src/<extension>.js
        	// Необходимо указать относительный путь 
        	input: string, 
        	
        	// Путь к бандлу, который будет создан в результате сборки 
        	// Обычно это ./dist/<extension>.bundle.js
        	// Необходимо указать относительный путь 
        	output: string,
        	
        	// Неймспейс, в который будут добавлены все экспорты из файла указанного в input
        	// Например 'BX.Main.Filter'
        	namespace: string,
        	
        	// Списки файлов для принудительного объединения. 
        	// Файлы будут объединены без проверок на дублирование кода. 
        	// sourcemap's объединяются автоматически 
        	// Необходимо указать относительные пути
        	concat: {
        		js: Array<string>,
        		css: Array<string>,
        	},
        	
        	// Разрешает или запрещает сборщику модифицировать config.php
        	// По умолчанию true (разрешено)
        	adjustConfigPhp: boolean,
        	
        	// Разрешает или запрещает сборщику удалять неиспользуемый код. 
        	// По умолчанию true (включено).
        	treeshake: boolean,
        	
        	// Разрешает или запрещает пересобирать бандлы 
        	// если сборка запущена не в корне текущего экстеншна 
        	// По умолчанию `false` (разрешено)
        	'protected': boolean,
        	
        	plugins: {
        		// Переопределяет параметры Babel.
        		// Можно указать собственные параметры Babel
        		// https://babeljs.io/docs/en/options
        		// Если указать false, то код будет собран без транспиляции
        		babel: boolean | Object,
        		
        		// Дополнительные плагины Rollup, 
        		// которые будут выполняться при сборке бандлов 
        		custom: Array<string | Function>,
        	},
        };
        

      config.php

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

      При использовании @bitrix/cli файл config.php будет создан автоматически при сборке и будет обновляться автоматически по мере необходимости. Например, если в JS коде появится зависимость которая не указана в config.php, то она будет автоматически добавлена в rel.

      • Базовая конфигурация:
        if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true)
        {
        	die();
        }
        
        return [
        	'css' => './dist/loader.bundle.css',
        	'js' => './dist/loader.bundle.js',
        	'rel' => [
        		'main.core'
        	]
        ];
        
      • Все параметры:
        ...
        
        return [
            // Путь к `css` файлу или массив путей 
            // Рекомендуется указывать относительный путь 
        	'css' => String | Array<String>,
        	
        	// Путь к `js` файлу или массив путей к `js` файлам 
            // Рекомендуется указывать относительный путь 
        	'js' => String | Array<String>,
        	
        	// Список зависимостей
        	// Необходимо указать имена расширений, которые должны быть 
        	// подключены перед подключением текущего расширения
        	// Зависимости подключаются рекурсивно и с учетом указанного порядка 
        	'rel' => String | Array<String>,
        	
        	// Путь к файлу с языковыми фразами или массив путей 
        	// файл `lang//config.php` подключается автоматически, 
        	// его тут можно не указывать
        	'lang' => String | Array<String>,
        	
        	// Запрещает подключать `main.core` автоматически, как зависимость
        	// По умолчанию `false` — `main.core` подключается. 
        	// При сборке бандла значение параметра устанавливается автоматически
        	// если в коде нет прямой зависимости на `main.core`
        	'skip_core' => Boolean,
        	
        	// Обработчик, который вызывается перед подключением расширения на страницу
        	// в качестве первого параметра будет передан массив конфигурации расширения
        	// обработчик может модифицировать этот массив и вернуть из функции
        	// Это может быть полезно когда необходимо добавить в языковые фразы 
        	// какие-то данные с сервера
        	'oninit' => Function,
        	
        	// Дополнительные языковые фразы
        	// Это может быть полезно для передачи вычисляемых значений языковых фраз
        	// Принимает массив. В качестве ключей необходимо указывать идентификаторы языковых фраз
        	'lang_additional' => Array<string, string>,
        
        	// Параметр доступен с версии 20.5.100 модуля main.
        	// Параметр позволяет указать настройки,
        	// которые могут быть получены в JS,
        	// с помощью метода Extension.getSettings().
        	'settings' => Array
        ];
        

      @types

      Директория может содержать файлы <name>.d.ts с описанием публичного JS API расширения, на TypeScript. Рекомендуется использовать *.d.ts файлы для описания API библиотек, написанных на ES5. Описывать ES6 код не нужно.

      Пример описания расширения main.loader:

      declare module 'main.loader' 
      {
          type loaderOptions = {
              target?: HTMLElement,
              size?: number,
              mode?: 'absolute' | 'inline' | 'custom',
              offset?: {
                  top?: string,
                  left?: string
              },
              color?: string
          };
          
          class Loader 
          {
              constructor(options?: loaderOptions);
      
              readonly layout: HTMLElement;
              readonly circle: HTMLElement;
      
              createLayout(): HTMLElement;
              show(target?: HTMLElement): Promise<any>;
              hide(): Promise<any>;
              isShown(): boolean;
              setOptions(options: loaderOptions): void;
              destroy(): void;
          }
      }
      

      test

      Директория должна содержать вложенные директории и файлы [ds]Mocha[/ds][di]
      Mocha (Мока) — JavaScript тест-фреймворк, который можно запускать как на node.js, так и в браузере, удобен для асинхронного тестирования. Тесты Mocha запускаются серийно, позволяя гибко и точно создавать отчеты.

      Подробнее…[/di]-тестов. Для каждого файла должна создаваться директория с именем тестируемого файла и вложенным в нее файлом с тестами в формате <sourceName>.test.js.

      Например, при такой структуре в `src`

      • src
        • entity
          • column.js
          • row.js
        • app.js

      директория test должна иметь следующую структуру:

      • test
        • entity
          • column
            • column.test.js
          • row
            • row.test.js
        • app
          • app.test.js

    Использование расширений

    • В PHP
      BitrixMainUIExtension::load('main.loader');
      

      Метод BitrixMainUIExtension::load принимает в качестве параметра имя расширения, либо массив имен.

    • В JS
    1. Импорт экспортов расширения:
      import {Loader} from 'main.loader';
      

      Если вы хотите импортировать старое расширение (не поддерживающие import ES6, например, main.date), укажите импорт без экспорта расширения:

      import "main.date";
      

      Когда вы импортируете расширение в JS, сборщик автоматически добавляет это расширение как зависимость в config.php.

    2. Отложенное подключение:
      import {loadExtension} from 'main.core';
      
      loadExtension('main.loader').then(() => {
      	// Код который использует `main.loader`
      });
      

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

    Инструмент @bitrix/cli

    Описание

    @bitrix/cli — консольный инструмент Битрикс-разработчика. Основная цель — упростить и автоматизировать разработку фронтенда для проектов на 1С-Битрикс: Управление Сайтом и Битрикс24.
    Требования для его нормальной работы, версии:
    Node: 9.11.2
    NPM: 5.6.0.

    @bitrix/cli — это набор консольных команд:

    1. bitrix build для сборки и [dw]транспиляции[/dw][di]
      Транспиляция — это перевод исходного кода с одного языка на другой.
      [/di] ES6+ кода в кросс-браузерный ES5;
    2. bitrix test для запуска [ds]Mocha[/ds][di]
      Mocha (Мока) — JavaScript тест-фреймворк, который можно запускать как на node.js, так и в браузере, удобен для асинхронного тестирования. Тесты Mocha запускаются серийно, позволяя гибко и точно создавать отчеты.

      Подробнее…[/di]-тестов;

    3. bitrix create для быстрого создания [ds]расширения[/ds][di]
      Расширение (экстеншн, extension) — способ организации JS и CSS кода в продукте.

      Подробнее…[/di] («экстеншна»).

    Примечание: В первую очередь @bitrix/cli предназначен для работы расширениями («экстеншнами»), шаблонами сайта и шаблонами компонентов.

    Установка

    • [ds]NPM:[/ds][di]
      npm — менеджер пакетов, входящий в состав Node.js.

      Подробнее…[/di]

      $ npm install -g @bitrix/cli
      
    • [ds]YARN:[/ds][di]
      Yarn — это новый менеджер пакетов, созданный совместно Facebook*, Google, Exponent и Tilde.

      * Социальная сеть признана экстремистской и запрещена на территории Российской Федерации.

      Подробнее…[/di]

      $ yarn global add @bitrix/cli
      

    Конфигурация

    • Базовая конфигурация:
      module.exports = {
      	input: './app.js', 
      	output: './dist/app.bundle.js',
      };
      
    • Все параметры:
      module.exports = {
      	// Файл, для которого необходимо выполнить сборку. 
      	// Необходимо указать относительный путь 
      	input: string, 
      	
      	// Путь к бандлу, который будет создан в результате сборки. 
      	// Обычно это ./dist/<extension_name>.bundle.js
      	// Необходимо указать относительный путь 
      	output: string || {js: string, css: string},
      	
      	// Неймспейс, в который будут добавлены все экспорты из файла, 
      	// указанного в input. Например, 'BX.Main.Filter'
      	namespace: string,
      	
      	// Списки файлов для принудительного объединения. 
      	// Файлы будут объединены без проверок на дублирование кода. 
      	// sourcemap's объединяются автоматически 
      	// Необходимо указать относительные пути
      	concat: {
      		js: Array<string>,
      		css: Array<string>,
      	},
      	
      	// Разрешает или запрещает сборщику модифицировать config.php
      	// По умолчанию true (разрешено)
      	adjustConfigPhp: boolean,
      	
      	// Разрешает или запрещает сборщику удалять неиспользуемый код. 
      	// По умолчанию true (включено)
      	treeshake: boolean,
      	
      	// Разрешает или запрещает пересобирать бандлы, 
      	// если сборка запущена не в корне текущего расширения 
      	// По умолчанию `false` (разрешено)
      	'protected': boolean,
      	
      	plugins: {
      		// Переопределяет параметры Babel.
      		// Можно указать собственные параметры Babel
      		// https://babeljs.io/docs/en/options
      		// Если указать false, то код будет собран без транспиляции
      		babel: boolean | Object,
      		
      		// Дополнительные плагины Rollup, 
      		// которые будут выполняться при сборке бандлов 
      		custom: Array<string | Function>,
      	},
          // Определяет правила обработки путей к изображениям в CSS
          // Доступно с версии 3.0.0
          cssImages: {
              // Определяет правило, по которому изображения должны 
              // быть обработаны:
              // 'inline' — преобразует изображения в инлайн 
              // 'copy' — копирует изображения в директорию 'output'
              // По умолчанию 'inline'
              type: 'inline' | 'copy', 
      
              // Путь к директории, в которую должны быть скопированы 
              // используемые изображения 
              output: string,
      
              // Максимальный размер изображений в кб, которые могут быть 
              // преобразованы в инлайн.
              // По умолчанию 14кб
              maxSize: number,
      
              // Использовать ли svgo для оптимизации svg 
              // По умолчанию true 
              svgo: boolean, 
          },
          resolveFilesImport: {
              // Путь к директории, в которую должны быть скопированы 
              // импортированные изображения 
              output: string,
              
              // Определяет разрешенные для импорта типы файлов.
              // По умолчанию ['**/*.svg', '**/*.png', '**/*.jpg', '**/*.gif']
              // https://github.com/isaacs/minimatch
              include: Array<string>,
      
              // По умолчанию []
              exclude: Array<string>,
          },
          
          // Определяет правила Browserslist: 
          // false — не использовать (по умолчанию)
          // true — использовать файл .browserslist / .browserslistrc
          browserslist: boolean | string | Array<string>,
        
          // Включает или отключает минификацию. 
          // По умолчанию отключено. 
          // Может принимать объект настроек Terser:
          // false — не минифицировать (по умолчанию)
          // true — минифицировать с настройками по умолчанию 
          // object — минифицировать с указанными настройками 
          minification: boolean | object,
        
          // Включает или отключает преобразование нативных JS классов. 
          // По умолчанию значение параметра выставляется автоматически 
          // на основании browserslist
          transformClasses: boolean,
        
          // Включает или отключает создание Source Maps файлов 
          sourceMaps: boolean,
          
          // Настройки тестов 
          tests: {
              // Настройки локализации 
              localization: {
                  // Код языка локализации. По умолчанию 'en'
                  languageId: string,
                  // Включает или выключает автозагрузку фраз в тестах. 
                  // По умолчанию включено 
                  autoLoad: boolean,
              },
          },
      };
      

    Подробнее о структуре расширений читайте в [ds]соответствующем уроке.[/ds][di]
    Расширение (экстеншн, extension) — способ организации JS и CSS кода в продуктах 1С-Битрикс: Управление Сайтом и Битрикс24.

    Подробнее…[/di]

    Сборка

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

    $ bitrix build
    

    Сборщик рекурсивно найдет все файлы bundle.config.js и выполнит для каждого конфига сборку и транспиляцию.

    Дополнительные параметры:

    • —watch [<fileExtension>[, …]], -w=[<fileExtension>[, …]]

      Режим отслеживания изменений. Пересобирает [dw]бандлы[/dw][di]
      Бандл (bundle, комплект/набор) — это совокупность каких-либо программных данных (файлов), объеденных по какому-либо признаку. В данном случае бандлы — это обработанные и объединенные исходные файлы.
      [/di] после изменения исходных файлов. В качестве значения можно указать список расширений файлов, в которых нужно отслеживать изменения.

      $ bitrix build --watch
      

      Сокращённый вариант:

      $ bitrix build -w
      

      Вариант с отслеживанием изменений в указанных типах файлов:

      $ bitrix build -w=defaults,json,mjs,svg
      

      Примечание: defaults – набор расширений файлов, которые отслеживаются по умолчанию. Он равен js,jsx,vue,css,scss.

    • —test, -t

      Режим непрерывного тестирования. Тесты запускаются после каждой сборки. Обратите внимание, сборка с параметром —test выводит в отчёте только статус прохождения тестов — прошли или не прошли, полный отчет выводит только команда bitrix test.

      $ bitrix build --test
      
    • —modules <moduleName>[, …], -m=<moduleName>[, …]

      Сборка только указанных модулей. Параметр поддерживается только в корневой директории c модулями local/js и bitrix/modules. В значении укажите имена модулей через запятую, например:

      $ bitrix build --modules main,ui,landing
      
    • —path <path>, -p=<path>

      Запуск сборки для указанной директории. В значении укажите относительный путь к директории, например:

      $ bitrix build --path ./main/install/js/main/loader
      

      Сокращённый вариант:

      $ bitrix build -p=./main/install/js/main/loader
      
    • —extensions <extensionName>[, …], -e=<extensionName>[, …]

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

      $ bitrix build -e=main.core,ui.buttons,landing.main
      

    Запуск тестов

    Команда запускает Mocha-тесты и выводит подробный отчет о прохождении тестов.

    $ bitrix test
    

    Примечание: Тестами считаются JS файлы, расположенные в директории ./test относительно файла bundle.config.js. В момент запуска тестов исходный код и код тестов на лету обрабатывается сборщиком и после чего выполняется. Поэтому тесты можно писать на ES6+.

    Дополнительные параметры:

    • —watch [<fileExtension>[, …]], -w=[<fileExtension>[, …]]

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

      $ bitrix test --watch
      

      Сокращённый вариант:

      $ bitrix test -w
      

      Вариант с отслеживанием изменений в указанных типах файлов:

      $ bitrix test -w=defaults,json,mjs,svg
      

      Примечание: defaults – набор расширений файлов, которые отслеживаются по умолчанию. Он равен js,jsx,vue,css,scss.

    • —modules <moduleName>[, …], -m=<moduleName>[, …]

      Тестирование только указанных модулей. Параметр поддерживается только в корневой директории репозитория. В значении укажите имена модулей через запятую, например:

      $ bitrix test --modules main,ui,landing
      
    • —path <path>, -p=<path>

      Запуск тестов для указанной директории. В значении укажите относительный путь к директории, например:

      $ bitrix test --path ./main/install/js/main/loader.
      

      Сокращённый вариант:

      $ bitrix test -p=./main/install/js/main/loader
      
    • —extensions <extensionName>[, …], -e=<extensionName>[, …]

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

      $ bitrix test -e=main.core,ui.buttons,landing.main
      

    Создание расширения

    Для создания расширения («экстеншна»):

    • Перейдите в директорию local/js/[dw]{module}[/dw][di]{module} – должно быть без точек в названии, например, партнерский префикс. Подробнее…[/di]
    • Выполните команду bitrix create. После выполнения команды создаётся папка расширения, например, /ext, и подключается так:
      BitrixMainUIExtension::load("partner.ext"); для пути local/js/partner/ext/.
    • Ответьте на вопросы мастера

    Примечание: Можно не переходить в /{module}. Останьтесь в local/js/, выполните в bitrix create расширение myext и загрузите его
    BitrixMainUIExtension::load("myext");.

      Видео от разработчика

    @bitrix/cli: сборка проекта с NPM

    В предыдущем уроке мы познакомились с консольным инструментом [ds]@bitrix/cli[/ds][di]
    @bitrix/cli — консольный инструмент Битрикс-разработчика. Основная цель — упростить и автоматизировать разработку фронтенда для проектов на 1С-Битрикс: Управление Сайтом и Битрикс24.
    Требования для его нормальной работы, версии:
    Node: 9.11.2
    NPM: 5.6.0.

    Подробнее…[/di]. В этом уроке рассмотрим, как осуществить сборку проектов с [dw]NPM[/dw][di]
    NPM (Node Package Manager, для его аббревиатуры на английском языке) является менеджером пакетов для JavaScript, по умолчанию для node.js. Он используется для скачивания пакетов из облачного сервера npm, либо для загрузки пакетов на эти сервера.
    [/di].

    Сборка проекта с NPM

    1. Создайте package.json.

      В нем объявляются внешние зависимости, необходимые для работы приложения. Например, если приложение использует React, Lodash или какую-то другую библиотеку, то эти библиотеки должны быть описаны в package.json с указанием версий и описанием вашего проекта.

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

      Быстро создать package.json можно с помощью команды npm init. Она запустит мастер сборки, и вам останется только ответить на вопросы. Команду необходимо выполнить в директории [ds]экстеншна.[/ds][di]
      Расширение (экстеншн, extension) — способ организации JS и CSS кода в продукте.

      Подробнее…[/di]

    2. В файле bundle.config.js укажите параметр plugins.resolve = true;

      Этот параметр сообщит сборщику, что все импорты NPM пакетов необходимо разрешить («разрезолвить») и добавить в бандл вашего приложения.

      Пример bundle.config.js, уже настроенного на работу с NPM пакетами:

      module.exports = {
      	input: 'src/app.js',
      	output: 'dist/app.bundle.js',
      	plugins: {
      		resolve: true,
      	},
      };
      

    Вложенные библиотеки

    Библиотеки могут содержать вспомогательные библиотеки, которые могут быть подключены отдельно.

    Полный путь в файловой системе:

    `/bitrix/modules/<module_name>/install/js/<module_name>/<library_name>/<sub_library_name>/`
    

    В таком формате вызов вашей библиотеки будет таким:

    BitrixMainUIExtension::load('<module_name>.<library_name>.<sub_library_name>');
    

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

    <module_name>.<library_name>.<sub_library_name>.<sub_library_name_2>.<sub_library_name_3>
    

    В данном случае путь будет таким:

    /bitrix/modules/<module_name>/install/js/<module_name>/<library_name>/<sub_library_name>/<sub_library_name_2>/<sub_library_name_3>/
    

    Использование ES6

    Использование ES6

    С 2019 г. все новые расширения Bitrix CoreJS пишутся на [ds]ES6.[/ds][di]
    ECMAScript — это встраиваемый расширяемый не имеющий средств ввода-вывода язык программирования, используемый в качестве основы для построения других скриптовых языков.

    Подробнее…[/di]

    Использование в старых браузерах

    Количество браузеров, поддерживающих современные JavaScript, зависит от регионов и составляет от 90 до 96%. В остальных браузерах необходимо использовать [dw]полифилы[/dw][di]
    Полифилы — это файлы, содержащие недостающие функции.
    [/di]
    и [dw]транспиляцию кода.[/dw][di]
    Транспиляция кода — это перевод кода из ES6 в ES5.
    [/di]

    • Полифилы

      В расширениях, использующих код ES6, необходимо подключить зависимость на библиотеку main.polyfill.complex.

    • Транспиляция

      Для корректной работы кода в старых браузерах необходимо перевести его из нового формата в старый, воспользовавшись специальной программой — [ds]транспилятором[/ds][di]@bitrix/cli — консольный инструмент Битрикс-разработчика. Основная цель — упростить и автоматизировать разработку фронтенда для проектов на 1С-Битрикс: Управление Сайтом и Битрикс24.

      Подробнее …[/di].

      Принцип работы транспилятора заключается в следующем: вы пишете код на ES6, а транспилятор в автоматическом режиме создает отдельный файл в старом формате:

    Работа с магазином

    Цитатник веб-разработчиков.

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

    В данной главе рассматриваются вопросы кастомизации магазина.

    Список ссылок по теме:

    • События интернет-магазина (Документация D7)
    • Приёмы работы с методами интернет-магазина (Документация D7)
    • Кастомизация административной формы заказа
    • Работа с D7 на примере местоположений
    • Глава Интернет-магазин в курсе Администратор. Бизнес

    Товары и CIBlockElement::GetList

    CIBlockElement::GetList

    Как вы знаете, метод CIBlockElement::GetList модуля Информационные блоки может работать с данными товара (при наличии модуля Торговый каталог). Это подробно описано в документации и активно используется как в публичных компонентах, так и на административных страницах и скриптах. Однако архитектурные особенности реализации, равно как и неправильное использование этих возможностей, приводят к резкому падению производительности.

    Давайте сделаем разные вызовы CIBlockElement::GetList и посмотрим, какие запросы будут выполнены в итоге.

    1. Сначала сделаем простую выборку товаров из инфоблока с ID = 2:

      $iterator = CIBlockElement::GetList(
         array(),
         array('IBLOCK_ID' => 2),
         false,
         false,
         array('ID', 'NAME', 'IBLOCK_ID')
      );
      

      Запрос, который пойдет в базу:

      SELECT  BE.ID as ID,BE.NAME as NAME,BE.IBLOCK_ID as IBLOCK_ID
      FROM b_iblock B
      INNER JOIN b_lang L ON B.LID=L.LID
      INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID
      WHERE 1=1 
         AND (
            ((((BE.IBLOCK_ID = '2'))))
         )
         AND (((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL)))
      
    2. Теперь в выборку добавим фильтрацию по доступности товара:

      $iterator = CIBlockElement::GetList(
         array(),
         array('IBLOCK_ID' => 2, 'CATALOG_AVAILABLE' => 'Y'),
         false,
         false,
         array('ID', 'NAME', 'IBLOCK_ID')
      );
      

      Это приводит к запросу:

      SELECT  BE.ID as ID,BE.NAME as NAME,BE.IBLOCK_ID as IBLOCK_ID ,CAT_PR.QUANTITY as CATALOG_QUANTITY,
      IF (CAT_PR.QUANTITY_TRACE = 'D', 'Y', CAT_PR.QUANTITY_TRACE) as CATALOG_QUANTITY_TRACE,
      CAT_PR.QUANTITY_TRACE as CATALOG_QUANTITY_TRACE_ORIG, CAT_PR.WEIGHT as CATALOG_WEIGHT,
      CAT_PR.VAT_ID as CATALOG_VAT_ID, CAT_PR.VAT_INCLUDED as CATALOG_VAT_INCLUDED,
      IF (CAT_PR.CAN_BUY_ZERO = 'D', 'N', CAT_PR.CAN_BUY_ZERO) as CATALOG_CAN_BUY_ZERO,
      CAT_PR.CAN_BUY_ZERO as CATALOG_CAN_BUY_ZERO_ORIG,
      CAT_PR.PURCHASING_PRICE as CATALOG_PURCHASING_PRICE, CAT_PR.PURCHASING_CURRENCY as CATALOG_PURCHASING_CURRENCY,
      CAT_PR.QUANTITY_RESERVED as CATALOG_QUANTITY_RESERVED,
      IF (CAT_PR.SUBSCRIBE = 'D', 'Y', CAT_PR.SUBSCRIBE) as CATALOG_SUBSCRIBE, CAT_PR.SUBSCRIBE as CATALOG_SUBSCRIBE_ORIG,
      CAT_PR.WIDTH as CATALOG_WIDTH, CAT_PR.LENGTH as CATALOG_LENGTH, CAT_PR.HEIGHT as CATALOG_HEIGHT,
      CAT_PR.MEASURE as CATALOG_MEASURE, CAT_PR.TYPE as CATALOG_TYPE, CAT_PR.AVAILABLE as CATALOG_AVAILABLE,
      CAT_PR.BUNDLE as CATALOG_BUNDLE, CAT_PR.PRICE_TYPE as CATALOG_PRICE_TYPE,
      CAT_PR.RECUR_SCHEME_LENGTH as CATALOG_RECUR_SCHEME_LENGTH, CAT_PR.RECUR_SCHEME_TYPE as CATALOG_RECUR_SCHEME_TYPE,
      CAT_PR.TRIAL_PRICE_ID as CATALOG_TRIAL_PRICE_ID, CAT_PR.WITHOUT_ORDER as CATALOG_WITHOUT_ORDER,
      CAT_PR.SELECT_BEST_PRICE as CATALOG_SELECT_BEST_PRICE,
      IF (CAT_PR.NEGATIVE_AMOUNT_TRACE = 'D', 'N', CAT_PR.NEGATIVE_AMOUNT_TRACE) as CATALOG_NEGATIVE_AMOUNT_TRACE,
      CAT_PR.NEGATIVE_AMOUNT_TRACE as CATALOG_NEGATIVE_AMOUNT_TRACE_ORIG, CAT_VAT.RATE as CATALOG_VAT
      
         FROM b_iblock B
         INNER JOIN b_lang L ON B.LID=L.LID
         INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID
      
         left join b_catalog_product as CAT_PR on (CAT_PR.ID = BE.ID)
         left join b_catalog_iblock as CAT_IB on ((CAT_PR.VAT_ID IS NULL or CAT_PR.VAT_ID = 0) and CAT_IB.IBLOCK_ID = BE.IBLOCK_ID)
         left join b_catalog_vat as CAT_VAT on
               (CAT_VAT.ID = IF((CAT_PR.VAT_ID IS NULL OR CAT_PR.VAT_ID = 0), CAT_IB.VAT_ID, CAT_PR.VAT_ID))
      
         WHERE 1=1 
         AND (
            ((((BE.IBLOCK_ID = '2'))))
            AND ((((CAT_PR.AVAILABLE='Y'))))
         )
         AND (((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL)))
      

      Таким образом, любое обращение к данным товара (фильтрация, сортировка, выборка одного из полей) приводит к join трех таблиц и выборке всех полей товара.

    3. Уберем фильтрацию, но будем выбирать цену одного из типов (ID типа цены — 1):

      $iterator = CIBlockElement::GetList(
         array(),
         array('IBLOCK_ID' => 2),
         false,
         false,
         array('ID', 'NAME', 'IBLOCK_ID', 'CATALOG_CATALOG_GROUP_ID_1')
      );
      

      Видим, что стало еще хуже:

      SELECT BE.ID as ID,BE.NAME as NAME,BE.IBLOCK_ID as IBLOCK_ID,
      CAT_P1.CATALOG_GROUP_ID as CATALOG_GROUP_ID_1, CAT_P1.ID as CATALOG_PRICE_ID_1,
      CAT_P1.PRICE as CATALOG_PRICE_1, CAT_P1.CURRENCY as CATALOG_CURRENCY_1,
      CAT_P1.QUANTITY_FROM as CATALOG_QUANTITY_FROM_1, CAT_P1.QUANTITY_TO as CATALOG_QUANTITY_TO_1,
      CAT_P1.EXTRA_ID as CATALOG_EXTRA_ID_1,
      'Базовая цена' as CATALOG_GROUP_NAME_1, 'Y' as CATALOG_CAN_ACCESS_1, 'Y' as CATALOG_CAN_BUY_1,
      CAT_PR.QUANTITY as CATALOG_QUANTITY, IF (CAT_PR.QUANTITY_TRACE = 'D', 'Y', CAT_PR.QUANTITY_TRACE) as CATALOG_QUANTITY_TRACE,
      CAT_PR.QUANTITY_TRACE as CATALOG_QUANTITY_TRACE_ORIG, CAT_PR.WEIGHT as CATALOG_WEIGHT,
      CAT_PR.VAT_ID as CATALOG_VAT_ID, CAT_PR.VAT_INCLUDED as CATALOG_VAT_INCLUDED,
      IF (CAT_PR.CAN_BUY_ZERO = 'D', 'N', CAT_PR.CAN_BUY_ZERO) as CATALOG_CAN_BUY_ZERO,
      CAT_PR.CAN_BUY_ZERO as CATALOG_CAN_BUY_ZERO_ORIG, CAT_PR.PURCHASING_PRICE as CATALOG_PURCHASING_PRICE,
      CAT_PR.PURCHASING_CURRENCY as CATALOG_PURCHASING_CURRENCY, CAT_PR.QUANTITY_RESERVED as CATALOG_QUANTITY_RESERVED,
      IF (CAT_PR.SUBSCRIBE = 'D', 'Y', CAT_PR.SUBSCRIBE) as CATALOG_SUBSCRIBE, CAT_PR.SUBSCRIBE as CATALOG_SUBSCRIBE_ORIG,
      CAT_PR.WIDTH as CATALOG_WIDTH, CAT_PR.LENGTH as CATALOG_LENGTH, CAT_PR.HEIGHT as CATALOG_HEIGHT,
      CAT_PR.MEASURE as CATALOG_MEASURE, CAT_PR.TYPE as CATALOG_TYPE, CAT_PR.AVAILABLE as CATALOG_AVAILABLE,
      CAT_PR.BUNDLE as CATALOG_BUNDLE, CAT_PR.PRICE_TYPE as CATALOG_PRICE_TYPE,
      CAT_PR.RECUR_SCHEME_LENGTH as CATALOG_RECUR_SCHEME_LENGTH, CAT_PR.RECUR_SCHEME_TYPE as CATALOG_RECUR_SCHEME_TYPE,
      CAT_PR.TRIAL_PRICE_ID as CATALOG_TRIAL_PRICE_ID, CAT_PR.WITHOUT_ORDER as CATALOG_WITHOUT_ORDER,
      CAT_PR.SELECT_BEST_PRICE as CATALOG_SELECT_BEST_PRICE,
      IF (CAT_PR.NEGATIVE_AMOUNT_TRACE = 'D', 'N', CAT_PR.NEGATIVE_AMOUNT_TRACE) as CATALOG_NEGATIVE_AMOUNT_TRACE,
      CAT_PR.NEGATIVE_AMOUNT_TRACE as CATALOG_NEGATIVE_AMOUNT_TRACE_ORIG, CAT_VAT.RATE as CATALOG_VAT
      FR OM b_iblock B
      INNER JOIN b_lang L ON B.LID=L.LID
      INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID
      left join b_catalog_price as CAT_P1 on (CAT_P1.PRODUCT_ID = BE.ID and CAT_P1.CATALOG_GROUP_ID = 1)
      left join b_catalog_product as CAT_PR on (CAT_PR.ID = BE.ID)
      left join b_catalog_iblock as CAT_IB on ((CAT_PR.VAT_ID IS NULL or CAT_PR.VAT_ID = 0) and CAT_IB.IBLOCK_ID = BE.IBLOCK_ID)
      left join b_catalog_vat as CAT_VAT on (CAT_VAT.ID = IF((CAT_PR.VAT_ID IS NULL OR CAT_PR.VAT_ID = 0), CAT_IB.VAT_ID, CAT_PR.VAT_ID))
      WH ERE 1=1
         AND (
            ((((BE.IBLOCK_ID = '2'))))
         )
         AND (((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL)))
      

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

    Подведем промежуточные итоги:

    • Выборки цен, количества на складах в CIBlockElement::GetList необходимо избегать категорически (особенно при наличии сортировки и неважно по каким полям). Эти данные нужно получать отдельными вызовами АПИ. К слову сказать, в штатных компонентах это было сделано еще в версии 17.0.
    • Остается фильтрация и сортировка по полям товара, ценам, складам. Обращение к полям товара по ключам CATALOG_ дает дополнительный join трех таблиц. Обращение к N типам цен или складов — join N+3 таблиц. Мало того, что время запроса увеличивается, так еще можно получить ошибку MySql «Too many tables; MySQL can only use 61 tables in a join».

    До выпуска связки обновлений catalog 18.6.100 + iblock 18.6.200 все вышеописанное относится, в том числе, к штатным компонентам и административным спискам модуля Информационные блоки (особенно в режиме совместного просмотра элементов и разделов, смотрите примечание ниже). После выхода данных обновлений в CIBlockElement::GetList доступны новые возможности работы с товарами.

    Примечание: ранее для всех больших инфоблоков мы рекомендовали не использовать режим совместного просмотра по причине большого расхода памяти. С выходом версии 18.5.5 модуля Информационные блоки эта проблема решена — инфоблок со 100 тысячами элементов спокойно выводится в административном разделе в этом режиме (расход памяти снижен примерно в 20 раз).

      С версий catalog 18.6.100 + iblock 18.6.200

    С версии iblock 18.6.200 изменяются ключи метода. По всем ключам возможна фильтрация, сортировка, выборка.

    С версии catalog 20.0.200 добавились поля товара

    Теперь вызов метода с фильтрацией по доступности выглядит так:

    $iterator = CIBlockElement::GetList(
       array(),
       array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y'),
       false,
       false,
       array('ID', 'NAME', 'IBLOCK_ID')
    );
    

    а в запросе только то, что просили, и join только один:

       SELECT  BE.ID as ID,BE.NAME as NAME,BE.IBLOCK_ID as IBLOCK_ID
    
       FROM b_iblock B
       INNER JOIN b_lang L ON B.LID=L.LID
       INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID
       left join b_catalog_product as PRD on (PRD.ID = BE.ID)
    
       WHERE 1=1 
       AND (
          ((((BE.IBLOCK_ID = '2'))))
          AND ((((PRD.AVAILABLE='Y'))))
       )
       AND (((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL)))
    

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

    $iterator = CIBlockElement::GetList(
       array(),
       array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y', '=TYPE' => 1),
       false,
       false,
       array('ID', 'NAME', 'IBLOCK_ID', 'WEIGHT', 'WIDTH', 'HEIGHT', 'LENGTH')
    );
    

    Запрос получается следующим:

       SELECT  BE.ID as ID,BE.NAME as NAME,BE.IBLOCK_ID as IBLOCK_ID ,
       PRD.WEIGHT as WEIGHT, PRD.WIDTH as WIDTH, PRD.HEIGHT as HEIGHT, PRD.LENGTH as LENGTH
    
       FROM b_iblock B
       INNER JOIN b_lang L ON B.LID=L.LID
       INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID
       left join b_catalog_product as PRD on (PRD.ID = BE.ID)
    
       WHERE 1=1 
       AND (
          ((((BE.IBLOCK_ID = '2'))))
          AND ((((PRD.AVAILABLE='Y'))))
          AND ((((PRD.TYPE = '1'))))
       )
       AND (((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL)))
    

    Поля цен (без привязки к конкретному типу цены)

    Поля цен (с указанием типа цены)

      Примеры выборок

    Фильтрация по цене любого типа:

    $iterator = CIBlockElement::GetList(
       array(),
       array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y', '=TYPE' => 1, '>PRICE' => 500),
       false,
       false,
       array('ID', 'NAME', 'IBLOCK_ID', 'WEIGHT', 'WIDTH', 'HEIGHT', 'LENGTH')
    );
    

    Фильтрация для типа цены с кодом 1 (обычно это базовая цена):

    $iterator = CIBlockElement::GetList(
       array(),
       array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y', '=TYPE' => 1, '>PRICE' => 500, '=PRICE_TYPE' => 1),
       false,
       false,
       array('ID', 'NAME', 'IBLOCK_ID', 'WEIGHT', 'WIDTH', 'HEIGHT', 'LENGTH')
    );
    

    Или в таком варианте:

    $iterator = CIBlockElement::GetList(
       array(),
       array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y', '=TYPE' => 1, '>PRICE_1' => 500),
       false,
       false,
       array('ID', 'NAME', 'IBLOCK_ID', 'WEIGHT', 'WIDTH', 'HEIGHT', 'LENGTH')
    );
    

    Теперь можно делать фильтры, ранее недоступные. Выбрать все товары, имеющие цены любого типа от 500 до 1000:

    $iterator = CIBlockElement::GetList(
       array(),
       array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y', '=TYPE' => 1, '>=PRICE' => 500, '<=PRICE' => 1000),
       false,
       false,
       array('ID', 'NAME', 'IBLOCK_ID')
    );
    

    Или только цены типов с кодом 1,4,5:

    $iterator = CIBlockElement::GetList(
       array(),
       array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y', '=TYPE' => 1, '>=PRICE' => 500, '<=PRICE' => 1000, '@PRICE_TYPE' => [1,4,5]),
       false,
       false,
       array('ID', 'NAME', 'IBLOCK_ID')
    );
    

    Выбрать все товары с ценами в любой валюте, эквивалентными диапазону от 100 до 200 USD:

    $iterator = CIBlockElement::GetList(
       array(),
       array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y', '=TYPE' => 1, '>=PRICE' => 100, '<=PRICE' => 200, 'CURRENCY_FOR_SCALE' => 'USD'),
       false,
       false,
       array('ID', 'NAME', 'IBLOCK_ID')
    );
    

    Выбрать товары, которых на любом складе не больше 3:

    $iterator = CIBlockElement::GetList(
       array(),
       array('IBLOCK_ID' => 2, '<=STORE_AMOUNT' => 3),
       false,
       false,
       array('ID', 'NAME', 'IBLOCK_ID')
    );
    

    Выбрать товары, которых на 17-м складе от 5 до 7:

    $iterator = CIBlockElement::GetList(
       array(),
       array('IBLOCK_ID' => 2, '<=STORE_AMOUNT' => 7, '>=STORE_AMOUNT' => 5, 'STORE_NUMBER' => 17),
       false,
       false,
       array('ID', 'NAME', 'IBLOCK_ID')
    );
    

    Либо в таком виде:

    $iterator = CIBlockElement::GetList(
       array(),
       array('IBLOCK_ID' => 2, '<=STORE_AMOUNT_17' => 7, '>=STORE_AMOUNT_17' => 5),
       false,
       false,
       array('ID', 'NAME', 'IBLOCK_ID')
    );
    

    Фильтрация товаров по наличию на определенных складах:

    if(!empty($arParams['STORES'])){ 
        $GLOBALS[$arParams['FILTER_NAME']]['@STORE_NUMBER'] => $arParams['STORES'];
       $GLOBALS[$arParams['FILTER_NAME']]['>STORE_AMOUNT'] = 0;
    }

    Другой пример фильтрации товаров по наличию на определенных складах:

    if(!empty($arParams['STORES'])){
    
    $storesFilter = [
                         'LOGIC'=>'OR'
                ];
                foreach ($arParams['STORES'] as $store_id){
                    $storesFilter[] = ['STORE_NUMBER' => intval($store_id),'>STORE_AMOUNT'=>0];
                }
    
      $GLOBALS[$arParams['FILTER_NAME']][] = $storesFilter;
            }
    

    Заключение

    После установки обновлений catalog 18.6.100 + iblock 18.6.200 настоятельно рекомендуется перевести свои компоненты и скрипты на новые ключи. Увеличение производительности прямо пропорционально числу товаров в каталоге. Так, при тестах на разделе из 1,5 тысяч товаров прирост скорости выполнения составил порядка 30%. Штатные компоненты ([comp include_62980]catalog.section[/comp], [comp include_62981]catalog.element[/comp], [comp include_62986]catalog.top[/comp]), а также все компоненты наследники BitrixIblockComponentBase переведены на новые фильтры.

    Пользовательские типы свойств заказа

    Свои типы свойств

    В системе имеются следующие стандартные типы свойств: Строка, Число, Да/Нет, Перечисление, Файл, Дата и Местоположение. Но вы можете добавлять свои типы свойств и самостоятельно определять их внешний вид. Таким образом, у покупателя при оформлении заказа будет спрашиваться какое-то значение, которое вы сами запрограммируете. Для этого вам необходимо выполнить следующие действия:

    • Унаследовать класс пользовательского типа:
      class MyType extends BitrixSaleInternalsInputBase
      {
      	protected static function getEditHtmlSingle($name, array $input, $value){...} 
      	protected static function getErrorSingle(array $input, $value){...}
      	static function getSettings(array $input, $reload){...}
      }
      
    • Подключить тип свойства к системе — тип подключается на событии registerInputTypes:

      BitrixMainEventManager::getInstance()->addEventHandler(
      	'sale',
      	'registerInputTypes',
      	'myFunction'
      );
      
    • В обработчике события зарегистрировать свой тип свойства с помощью метода Manager::register, где указывается ваш класс-обработчик и имя вашего типа:

      public function myFunction(BitrixMainEvent $event)
      {
      	BitrixSaleInternalsInputManager::register(
      		"myType",
      		array(
      			'CLASS' => 'MyNamespaceMyType',
      			'NAME' => 'Мой тип',
      		)	
      	);
      }
      
    • [ds]Описать JS-класс[/ds][di]Иногда при разработке компонента его шаблон необходимо наделить js-функциональностью, событиями и прочим.

      Подробнее …[/di] для работы со свойством и [ds]подключить[/ds][di]Перед написанием JS-кода встает резонный вопрос — где его хранить?

      Подробнее …[/di] его:

      BX.Sale.Input.Manager.MyType = MyType;
      BX.Sale.Input.Utils.extend(MyType, BX.Sale.Input.BaseInput);
      BX.Sale.Input.Manager.register('myType', MyType);
      
      function MyType(name, settings, value, publicO)
      {
          MyType.__super__.constructor.call(this, name, settings, value, publicO);
      }
      
      MyType.prototype.createEditorSingle = function (name, value)
      {
          ...
      };
      
      MyType.prototype.afterEditorSingleInsert = function (item)
      {
          ...
      };
      
      MyType.prototype.setValueSingle = function (item, value)
      {
          ...
      };
      
      MyType.prototype.getValueSingle = function (item)
      {
          ...
      };
      
      MyType.prototype.setDisabledSingle = function (item, disabled)
      {
          ...
      };
      
      MyType.prototype.addEventSingle = function (item, name, action)
      {
          ...
      };
      

    Важно! Название типа myType должно быть уникальным в рамках всей системы.

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

    Обратите внимание! Поддержку созданного пользовательского свойства в компоненте оформления заказа [comp include_146775]sale.order.ajax[/comp] необходимо делать самостоятельно.

    Пример создания типа свойств String

    Наследуем класс, подключаем тип свойства к системе и регистрируем его:

    class StringInput extends BitrixSaleInternalsInputBase 
    {
    	public static function getEditHtmlSingle($name, array $input, $value)
    	{
    		if ($input['MULTILINE'] == 'Y')
    		{
    			$attributes = static::extractAttributes($input,
    				array('DISABLED'=>'', 'READONLY'=>'', 'AUTOFOCUS'=>'', 'REQUIRED'=>''),
    				array('FORM'=>1, 'MAXLENGTH'=>1, 'PLACEHOLDER'=>1, 'DIRNAME'=>1, 'ROWS'=>1, 'COLS'=>1, 'WRAP'=>1));
    
    			return '<textarea name="'.$name.'"'.$attributes.'>'.htmlspecialcharsbx($value).'</textarea>';
    		}
    		else
    		{
    			$attributes = static::extractAttributes($input,
    				array('DISABLED'=>'', 'READONLY'=>'', 'AUTOFOCUS'=>'', 'REQUIRED'=>'', 'AUTOCOMPLETE'=>'on'),
    				array('FORM'=>1, 'MAXLENGTH'=>1, 'PLACEHOLDER'=>1, 'DIRNAME'=>1, 'SIZE'=>1, 'LIST'=>1, 'PATTERN'=>1));
    
    			return '<input type="text" name="'.$name.'" value="'.htmlspecialcharsbx($value).'"'.$attributes.'>';
    		}
    	}
    
    	/**
    	 * @param $name
    	 * @param array $input
    	 * @param $value
    	 * @return string
    	 */
    	public static function getFilterEditHtml($name, array $input, $value)
    	{
    		return static::getEditHtmlSingle($name, $input, $value);
    	}
    
    	public static function getErrorSingle(array $input, $value)
    	{
    		$errors = array();
    
    		$value = trim($value);
    
    		if ($input['MINLENGTH'] && strlen($value) < $input['MINLENGTH'])
    			$errors['MINLENGTH'] = Loc::getMessage('INPUT_STRING_MINLENGTH_ERROR', array("#NUM#" => $input['MINLENGTH']));
    
    		if ($input['MAXLENGTH'] && strlen($value) > $input['MAXLENGTH'])
    			$errors['MAXLENGTH'] = Loc::getMessage('INPUT_STRING_MAXLENGTH_ERROR', array("#NUM#" => $input['MAXLENGTH']));
    
    		if ($input['PATTERN'] && !preg_match($input['PATTERN'], $value))
    			$errors['PATTERN'] = Loc::getMessage('INPUT_STRING_PATTERN_ERROR');
    
    		return $errors;
    	}
    
    	static function getSettings(array $input, $reload)
    	{
    		$settings = array(
    			'MINLENGTH' => array('TYPE' => 'NUMBER', 'LABEL' => Loc::getMessage('INPUT_STRING_MINLENGTH'), 'MIN' => 0, 'STEP' => 1),
    			'MAXLENGTH' => array('TYPE' => 'NUMBER', 'LABEL' => Loc::getMessage('INPUT_STRING_MAXLENGTH'), 'MIN' => 0, 'STEP' => 1),
    			'PATTERN'   => array('TYPE' => 'STRING', 'LABEL' => Loc::getMessage('INPUT_STRING_PATTERN'  )),
    			'MULTILINE' => array('TYPE' => 'Y/N'   , 'LABEL' => Loc::getMessage('INPUT_STRING_MULTILINE'), 'ONCLICK' => $reload),
    		);
    
    		if ($input['MULTILINE'] == 'Y')
    		{
    			$settings['COLS'] = array('TYPE' => 'NUMBER', 'LABEL' => Loc::getMessage('INPUT_STRING_SIZE'), 'MIN' => 0, 'STEP' => 1);
    			$settings['ROWS'] = array('TYPE' => 'NUMBER', 'LABEL' => Loc::getMessage('INPUT_STRING_ROWS'), 'MIN' => 0, 'STEP' => 1);
    		}
    		else
    		{
    			$settings['SIZE'] = array('TYPE' => 'NUMBER', 'LABEL' => Loc::getMessage('INPUT_STRING_SIZE'), 'MIN' => 0, 'STEP' => 1);
    		}
    
    		return $settings;
    	}
    }
    
    BitrixSaleInternalsInputManager::register('STRING', array(
    	'CLASS' => 'StringInput',
    	'NAME' => BitrixMainLocalizationLoc::getMessage('INPUT_STRING'),
    ));

    Описываем и подключаем JS-класс:

    BX.Sale.Input.Manager.StringInput = StringInput;
    BX.Sale.Input.Utils.extend(StringInput, BX.Sale.Input.BaseInput);
    BX.Sale.Input.Manager.register('STRING', StringInput);
    
    function StringInput(name, settings, value, publicO)
    {
    	StringInput.__super__.constructor.call(this, name, settings, value, publicO);
    }
    
    StringInput.prototype.createEditorSingle = function (name, value)
    {
    	var s, size = 5, settings = this.settings;
    
    	if ((s = settings.MIN) && s.toString().length > size)
    		size = s;
    
    	if ((s = settings.MAX) && s.toString().length > size)
    		size = s;
    
    	if ((s = settings.STEP) && s.toString().length > size)
    		size = s;
    
    	var element = document.createElement('input');
    	element.type  = 'text';
    	element.name  = name;
    	element.value = value;
    	element.size  = size;
    
    	BX.Sale.Input.Utils.applyBooleanAttributesTo(element, settings, BX.Sale.Input.Utils.globalBooleanAttributes, {DISABLED:'', READONLY:'', AUTOFOCUS:'', REQUIRED:'', AUTOCOMPLETE:'on'});
    	BX.Sale.Input.Utils.applyValueAttributesTo(element, settings, BX.Sale.Input.Utils.globalValueAttributes, {FORM:1, LIST:1, PLACEHOLDER:1});
    	this.applyEventAttributesTo(element, settings, BX.Sale.Input.Utils.globalEventAttributes);
    
    	return [element];
    };
    
    StringInput.prototype.afterEditorSingleInsert = function (item)
    {
    	item[0].focus();
    };
    
    StringInput.prototype.setValueSingle = function (item, value)
    {
    	item[0].value = value;
    };
    
    StringInput.prototype.getValueSingle = function (item)
    {
    	var element = item[0];
    	return element.disabled ? null : element.value;
    };
    
    StringInput.prototype.setDisabledSingle = function (item, disabled)
    {
    	item[0].disabled = disabled;
    };
    
    StringInput.prototype.addEventSingle = function (item, name, action)
    {
    	BX.Sale.Input.Utils.addEventTo(item[0], name, action);
    };
    

    Пользовательские ограничения

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

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

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

    • для служб доставок onSaleDeliveryRestrictionsClassNamesBuildList:
      BitrixMainEventManager::getInstance()->addEventHandler(
          'sale',
          'onSaleDeliveryRestrictionsClassNamesBuildList',
          'myDeliveryFunction'
      );
      
    • для платежных систем onSalePaySystemRestrictionsClassNamesBuildList:
      BitrixMainEventManager::getInstance()->addEventHandler(
          'sale',
          'onSalePaySystemRestrictionsClassNamesBuildList',
          'myPayFunction'
      );
      
    • для касс onSaleCashboxRestrictionsClassNamesBuildList:
      BitrixMainEventManager::getInstance()->addEventHandler(
          'sale',
          'onSaleCashboxRestrictionsClassNamesBuildList',
          'myCashboxFunction'
      );
      
    • для компаний onSaleCompanyRulesClassNamesBuildList:
      BitrixMainEventManager::getInstance()->addEventHandler(
          'sale',
          'onSaleCompanyRulesClassNamesBuildList',
          'myCompanyFunction'
      );
      

    В обработчиках событий соответственно следует возвращать ваш класс ограничений:

    • для служб доставок:
      function myDeliveryFunction()
      {
          return new BitrixMainEventResult(
              BitrixMainEventResult::SUCCESS,
              array(
                  'MyDeliveryRestriction' => '/bitrix/php_interface/include/mydelrestriction.php',
              )
          );
      }
      
    • для платежных систем:
      function myPayFunction()
      {
          return new BitrixMainEventResult(
              BitrixMainEventResult::SUCCESS,
              array(
                  'MyPayRestriction' => '/bitrix/php_interface/include/mypayrestriction.php',
              )
          );
      }
      
    • для касс:
      function myCashboxFunction()
      {
          return new BitrixMainEventResult(
              BitrixMainEventResult::SUCCESS,
              array(
                  'MyCashboxRestriction' => '/bitrix/php_interface/include/mycashboxrestriction.php',
              )
          );
      }
      
    • для компаний:
      function myCompanyFunction()
      {
          return new BitrixMainEventResult(
              BitrixMainEventResult::SUCCESS,
              array(
                  'MyCompanyRestriction' => '/bitrix/php_interface/include/mycompanyrestriction.php',
              )
          );
      }
      

    Далее, описывая ограничение, вы можете вводить какие-то собственные правила. Например, в примере ниже приведено ограничение доступности службы доставки по лунным суткам:

    use BitrixSaleDeliveryRestrictions;
    use BitrixSaleInternalsEntity;
    
    class MyDeliveryRestriction extends RestrictionsBase
    {
        public static function getClassTitle()
        {
            return 'по лунным суткам';
        }
    
        public static function getClassDescription()
        {
            return 'доставка будет выводится только в указанном диапазоне лунных суток';
        }
    
    public static function check($moonday, array $restrictionParams, $deliveryId = 0)
    {
        if ($moonday < $restrictionParams['MIN_MOONDAY']
            || $moonday > $restrictionParams['MAX_MOONDAY'])
            return false;
    
        return true;
    }
    protected static function extractParams(Entity $shipment)
    {
        $json = file_get_contents('http://moon-today.com/api/index.php?get=moonday');
        $res = json_decode($json, true);
        return !empty($res['moonday']) ? intval($res['moonday']) : 0;
    }
    public static function getParamsStructure($entityId = 0)
        {
            return array(
                "MIN_MOONDAY" => array(
                    'TYPE' => 'NUMBER',
                    'DEFAULT' => "1",
                    'LABEL' => 'Минимальные сутки'
                ),
                "MAX_MOONDAY" => array(
                    'TYPE' => 'NUMBER',
                    'DEFAULT' => "30",
                    'LABEL' => 'Максимальные сутки'
                )
            );
        }
    }
    

    Пользовательские правила компаний

    Вы можете дополнить стандартный набор правил компаний своими собственными правилами. Для этого следует использовать событие onSaleCompanyRulesClassNamesBuildList:

    BitrixMainEventManager::getInstance()->addEventHandler(
            "sale",
            "onSaleCompanyRulesClassNamesBuildList", 
            "myCompanyRulesFunction"
    );
    

    В обработчике события следует вернуть ваш класс правил:

    function myCompanyRulesFunction()
    {
        return new BitrixMainEventResult(
            BitrixMainEventResult::SUCCESS,
            array(
                'MyCompanyRules' => '/bitrix/php_interface/include/mycompanyrules.php',
            )
        );
    }
    

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

    use BitrixSaleServicesBase;
    use BitrixSaleInternalsEntity;
    
    class MyCompanyRules extends BaseRestriction
    {
        public static function getClassTitle()
        {
            return 'по лунным суткам';
        }
    
        public static function getClassDescription()
        {
            return 'компания будет использоваться только в указанном диапазоне лунных суток';
        }
    
    public static function check($params, array $restrictionParams, $serviceId = 0)
    {
        if ($params < $restrictionParams['MIN_MOONDAY']
            || $params > $restrictionParams['MAX_MOONDAY'])
            return false;
    
        return true;
    }
    protected static function extractParams(Entity $entity)
    {
        $json = file_get_contents('http://moon-today.com/api/index.php?get=moonday');
        $res = json_decode($json, true);
        return !empty($res['moonday']) ? intval($res['moonday']) : 0;
    }
    public static function getParamsStructure($entityId = 0)
        {
            return array(
                "MIN_MOONDAY" => array(
                    'TYPE' => 'NUMBER',
                    'DEFAULT' => "1",
                    'LABEL' => 'Минимальные сутки'
                ),
                "MAX_MOONDAY" => array(
                    'TYPE' => 'NUMBER',
                    'DEFAULT' => "30",
                    'LABEL' => 'Максимальные сутки'
                )
            );
        }
    }

    Кастомизация типов дополнительных услуг

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

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

    EventManager::getInstance()->addEventHandler(
    	'sale',
    	'onSaleDeliveryExtraServicesClassNamesBuildList',
    	'myFunction'
    );	
    

    Зарегистрируйте свой класс, который реализует ваш кастомный тип услуг:

    class MyService extends BitrixSaleDeliveryExtraServicesBase
    {
    	public function getClassTitle()
    	{
    			return "Моя услуга";
    	}
    	...
    }
    

    Обработчик события должен вернуть список ваших классов типов дополнительных услуг и путей к ним:

    public static function myFunction(MainEvent $event)
    {
    	return new MainEventResult(
    		MainEventResult::SUCCESS,
    		array(
    			'MyService' = > 'folder/myservice.php',
    		)
    	);
    }
    

    В результате ваш класс включится в работу и будет реализован интерфейс услуг в соответствии с вашими настройками.

    Класс наследуется от базового Base, которые размещается в директории: /bitrix/modules/sale/lib/delivery/extra_services.
    Там же вы сможете найти примеры штатных типов услуг.

    Кастомизация служб доставок

    Кастомизация

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

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

    class SimpleHandler extends BitrixSaleDeliveryServicesBase
    {
        protected static $isCalculatePriceImmediately = true;
    	protected static $whetherAdminExtraServiceShow = true;
    	
    	/**
    	* @param array $initParams
    	* @throws BitrixMainArgumentTypeException
    	*/
        
        public function __construct(array $initParams)
        {
            parent::__construct($initParams);
        }
    }
    
    

    Система будет искать обработчик в следующих директориях:

    self::$handlersDirectories = array(
    	'LOCAL' = > '/local/php_interface/include/sale_delivery',
    	'CUSTOM' = > '/bitrix/php_interface/include/sale_delivery',
    	'SYSTEM' = > '/bitrix/modules/sale/handlers/delivery'
    )
    

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

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

    Дополнительно стоит отметить, что для служб доставок имеется событие onSaleDeliveryServiceCalculate, которое позволяет вмешаться в расчеты стоимости доставки (например, вы можете увеличить стоимость доставки на 100 единиц):

    EventManager::getInstance()->addEventHandler(
        'sale',
        'onSaleDeliveryServiceCalculate',
        'myCalc'
    );
    
    function myCalc(BitrixMainEvent $event)
    {
    	/** @var DeliveryCalculationResult $baseResult */
    	$baseResult = $event->getParameter('RESULT');
    	$shipment = $event->getParameter('SHIPMENT');
    	
    	$price = $baseResult->getDeliveryPrice() + 100;
    	$baseResult->setDeliveryPrice($price);
    	
    	$event->addResult(
    		new EventResult(
    			EventResult::SUCCESS, array('RESULT' => $baseResult)
    		)
    	);
    }
    

    Важно! При создании службы доставки учитывайте тот факт, что сайт может быть как в кодировке utf-8, так и в кодировке cp-1251. Если производится обмен данными со службой доставки, то необходимо правильно менять кодировку при отправлении и получении данных. Здесь вам поможет метод BitrixMainTextEncoding::convertEncoding().

    Примечание: о работе с REST служб доставки читайте [ds]в отдельной главе[/ds][di]
    В данной главе рассмотрим принципы работы с REST служб доставки:

    • Настройка обработчика службы доставки;
    • Настройка службы доставки;
    • Настройка дополнительных услуг для службы доставки;
    • Работа со свойствами отгрузки;
    • Типовой сценарий работы REST служб доставки: работа с доставкой в центре продаж.

      Стартовой точкой для менеджера может являться функционал принятия оплаты («Принять

      оплату в сделке») или создание дела на доставку.

    Подробнее…[/di].

    Примеры служб доставок

    В качестве примера служб доставок лучше всего подходят следующие:

    • SaleHandlersDeliverySimpleHandler (/bitrix/modules/sale/handlers/delivery/simple/handler.php) — простейший пример обработчика.
    • SaleHandlersDeliverySpsrHandler (/bitrix/modules/sale/handlers/delivery/spsr/handler.php) — вариант посложнее с использованием всех возможностей текущей архитектуры.

    Для служб доставок существует механизм автоматического отслеживания идентификаторов отправления (трэкинг-номеров) (пример, как это реализовано для службы доставки СПСР: SaleHandlersDeliverySpsrTracking).

    Рекомендации

    Для запросов к сервису службы доставки рекомендуется использовать встроенный класс BitrixMainWebHttpClient вместо сторонних расширений. Оптимальный формат обмена — json, так как возможно использовать встроенный класс BitrixMainWebJson.

    При обмене информацией с сервисами служб доставок зачастую необходимо передавать идентификаторы местоположений. Сопоставление идентификаторов местоположений интернет-магазина с идентификаторами местоположений служб доставок — задача нетривиальная. Как пример, используйте SaleHandlersDeliverySpsrLocation::mapStepless().

    Чтобы не порождать лишних запросов к службе доставки и не замедлять работу сайта, желательно при возможности кешировать полученную от сервисов служб доставок информацию. Однако, делать это надо аккуратно во избежание побочных эффектов. Используйте SaleHandlersDeliverySpsrCache.

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

    Кастомизация платежных систем

    Средства системы позволяют кастомизировать и добавлять свои собственные платежные системы, причем в магазине на ядре D7 они представляют из себя классы. Следовательно, можно использовать механизм наследования:

    • если необходимо сделать платежную систему, похожую на входящую в состав продукта, то можно унаследоваться от соответствующего класса;
    • если требуется написать платежную систему с нуля, то можно унаследоваться от базового класса BitrixSalePaySystemBaseServiceHandler или BitrixSalePaySystemServiceHandler (первый вариант подходит для платежных систем, которые не являются автоматизированными, например, квитанция Сбербанка).

      Чаще всего при кастомизации используется класс BitrixSalePaySystemServiceHandler, который является наследником класса BitrixSalePaySystemBaseServiceHandler. Класс BitrixSalePaySystemServiceHandler поддерживает методы, реализовав которые возможна обработка ответа от платежной системы.

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

    Пример наследования для платежных систем:

    class YandexHandler extends ServiceHandler implements IReturn, IHold
    {
        public static function initiatePay(Payment $payment)
        {
            $params = array('URL' = > $this->getUrl($payment, 'pay'));
            $this->setExtraParams($params);
            
            return $this->showTemplate($payment, "template");
        }
    
        public static function getIndicativeFields()
        {
            return array('BX_HANDLER' => 'YANDEX');
        }
    }
    

    Система будет искать обработчик в следующих директориях:

    protected static $handlerDirectories = array(
    	'CUSTOM' =>  путь берется из опции path2user_ps_files (по умолчанию "/php_interface/include/sale_payment/")
    	'LOCAL' => '/local/php_interface/include/sale_payment/',
    	'SYSTEM' => '/bitrix/modules/sale/handlers/paysystem/'
    	'SYSTEM_OLD' => '/bitrix/modules/sale/payment/'
    )
    

    Важно помнить!

    Если при копировании не изменить имя (оставить /bitrix/php_interface/include/sale_payment/yandex), то в настройках платежных систем можно будет использовать только кастомный обработчик. Системный (тот, который копировался) не будет доступен, то есть кастомный обработчик подменяет системный.

    Из этого вытекает следующее: если при копировании системного обработчика в свое пространство имен его имя меняется, то необходимо переименовать класс. Например, если мы скопировали системный yandex в /bitrix/php_interface/include/sale_payment/yandexnew, то в файле handler.php наследование должно быть так:

    class YandexNewHandler extends PaySystemBaseServiceHandler

    Обратите внимание! Имя папки обработчика не может содержать слово handler, т.к. оно присутствует в названии самого класса внутри обработчика. То есть в приведенном примере в имени /bitrix/php_interface/include/sale_payment/yandexnew конечная папка yandexnew не должна содержать слово handler. Название папки должно быть в нижнем регистре.

    Ограничения использования платежной системы

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

    Требования к файлу .description.php обработчика платежной системы

    Структура массива с описанием настроек обработчика платежной системы хранится в переменной $data и имеет вид:

    $data = array(
    	'NAME' => 'название_платежной_системы',
    	'SORT' => 500,
    	'CODES' => array( // массив параметров, необходимых для настройки
    		"КОД_ПАРАМЕТРА" => array(
    			"NAME" => 'НАЗВАНИЕ_ПАРАМЕТРА',
    			"DESCRIPTION" => 'ОПИСАНИЕ_ПАРАМЕТРА',
    			'SORT' =>100,
    			'GROUP' => 'КОД_ГРУППЫ',
    			'DEFAULT' => array( // значение по умолчанию
    				'PROVIDER_KEY' => 'КЛЮЧ', // тип значения: (PAYMENT, ORDER, SHIPMENT, USER, COMPANY, VALUE)
    				'PROVIDER_VALUE' => 'DATE_BILL' // значение: поля из конкретной сущности, либо произвольное значение
    			)
    		),
    		...
    	)
    );
    

    Для вывода описания при создании обработчика необходимо объявить переменную $description:

    $description = array(
    	'MAIN' => 'ОПИСАНИЕ ОБРАБОТЧИКА'
    );
    

    Кастомизация шаблона платежной системы

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

    Для этого нужно скопировать шаблон из платежной системы /bitrix/modules/sale/handlers/paysystem/<имя_платежной_системы>/template/ в шаблон сайта /bitrix/templates/<шаблон_сайта>/payment/<имя_платежной_системы>/template/ и отредактировать его так, как вам необходимо. За поиск HTML-шаблона отвечает метод BitrixSalePaySystemBaseServiceHandler::searchTemplate().

    Собственный обработчик онлайн-кассы

    Не подходит стандартный обработчик онлайн-кассы? Используйте API продукта и напишите свой обработчик. Для этого вам необходимо:

    • Унаследовать класс BitrixSaleCashboxCashbox и реализовать необходимые методы:

      Примечание: дополнительно вы можете использовать следующие интерфейсы:

      • BitrixSaleCashboxIPrintImmediately — необходим для отправки чека на печать сразу же после его создания;
      • BitrixSaleCashboxICheckable — необходим, если требуется запрашивать информацию о результатах печати чека.
      use BitrixSaleCashboxCashbox,
             BitrixSaleCashboxCheck,
             BitrixSaleCashboxIPrintImmediately,
            BitrixSaleCashboxICheckable;
      
      class CashboxCustom extends Cashbox implements IPrintImmediately, ICheckable
      {
      	/**
      	 * @param Check $check
      	 * @return array
      	 */
      	public function buildCheckQuery(Check $check)
      	{
      		// построение запроса с информацией по чеку
      	}
      
      	/**
      	 * @param $id
      	 * @return array
      	 */
      	public function buildZReportQuery($id)
      	{
      		// построение запроса на печать z-отчета
      		// если печать z-отчета не требуется, возвращается пустой массив
      	}
      	
      	public function printImmediately(Check $check)
      	{
      		// алгоритм отправки чека на печать
      	}
      	
      	public function check(Check $check)
      	{
      		// алгоритм запроса состояния чека
      	}
      
      	/**
      	 * @return string
      	 */
      	public static function getName()
      	{
      		// название обработчика
      		return LocalizationLoc::getMessage('SALE_CASHBOX_CUSTOM_TITLE');
      	}
      	
      	/**
      	 * @param array $data
      	 * @throws MainNotImplementedException
      	 * @return array
      	 */
      	protected static function extractCheckData(array $data)
      	{
      		// извлечение данных по чеку дальнейшего сохранения
      	}
      	public static function getVersion() ?: float
      	{
      		// версия ФФД, с которой работает обработчик
      		return null; 
      	}
      }
    • Подключить обработчик кассы к системе с помощью события OnGetCustomCashboxHandlers. Обработчик события должен возвращать массив вида: array(полное_имя_класса => путь_к_файлу):
      AddEventHandler("sale", "OnGetCustomCashboxHandlers", 'myCashboxFunction');
      
      function myCashboxFunction()
      {
          return new BitrixMainEventResult(
             BitrixMainEventResult::SUCCESS,
             array(
                 'CashboxCustom' => '/bitrix/php_interface/include/cashboxcustom.php',
             )
         );
      }
      

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

    Список ссылок по теме:

    • Подготовка платёжной системы
    • Методы реализации кассы

    Принцип печати чеков через платёжную систему

    В [ds]предыдущем уроке[/ds][di]
    Не подходит стандартный обработчик онлайн-кассы? Используйте API продукта и напишите свой обработчик.

    Подробнее…[/di] Вы узнали, как написать собственный обработчик онлайн-кассы. В этом уроке ознакомьтесь с алгоритмом, по которому происходит печать чеков через платёжную систему:

    1. Создание оплаты

      В процессе создания оплаты подготавливаются данные для чека. Обычно это:

      • состав корзины;
      • система налогообложения;
      • НДС;
      • тип чека.

      Эти данные отправляются вместе с запросом на создание оплаты.

      В системе на этом этапе чек не создаётся, так как покупатель ещё не выполнил оплату.

    2. Оплата

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

      При обработке уведомления меняется статус оплаты на Оплачено.

    3. Чек

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

      • определяется тип чека, который нужно напечатать (совпадает с типом, который был передан при создании платежа);
      • вызывается метод buildCheckQuery, который подготавливает данные по чеку;
      • выбирается подходящая касса для печати;
      • вызывается метод printImmediately. Так как данные по чеку уже есть в платёжном шлюзе, они не передаются повторно;
      • чек в статусе Печатается добавляется в систему.
    4. Статус чека

      После того, как чек добавится в систему, получить данные о статусе этого чека можно либо через агент BitrixSaleCashboxManager::updateChecksStatus(), либо менеджер может вручную запросить статус из интерфейса.

      Примечание: Если первый чек был чеком «полной оплаты», на этом этапе работа с чеком завершается. Если чек был другого типа («аванс» или «предоплата»), нужно напечатать второй закрывающий чек в момент отгрузки заказа.

    5. Печать закрывающего чека

      В момент отгрузки заказа начинают собираться данные для второго чека:

      • определяется тип чека, который нужно напечатать (в этом случае тип «полная оплата»);
      • вызывается метод buildCheckQuery, который подготавливает данные по чеку;
      • выбирается подходящая касса для печати;
      • вызывается метод printImmediately, и теперь данные по чеку отправляются платежному провайдеру;
      • чек в статусе Печатается добавляется в систему.
    6. Статус закрывающего чека

      После того, как чек добавится в систему, получить данные о статусе этого чека можно либо через агент BitrixSaleCashboxManager::updateChecksStatus(), либо менеджер может вручную запросить статус из интерфейса.

    Список ссылок по теме:

    • Подготовка платёжной системы
    • Методы реализации кассы

    Работа с REST служб доставки

    В данной главе рассмотрим принципы работы с REST служб доставки:

    • Настройка обработчика службы доставки;
    • Настройка службы доставки;
    • Настройка дополнительных услуг для службы доставки;
    • Работа со свойствами отгрузки;
    • Типовой сценарий работы REST служб доставки: работа с доставкой в центре продаж. Стартовой точкой для менеджера может являться функционал принятия оплаты («Принять оплату в сделке») или создание дела на доставку.

    Процесс создания и настройки службы доставки

      Настройка обработчика службы доставки

    Обработчик службы доставки представляет из себя шаблон, по которому в дальнейшем можно будет создавать экземпляры служб доставки. Поэтому перед созданием службы доставки необходимо добавить обработчик с помощью метода sale.delivery.handler.add:

    {
      "CODE":"uber",
      "NAME":"Uber",
      "DESCRIPTION":"Uber Description",
      "SETTINGS":{
        "CALCULATE_URL":"http://gateway.bx/calculate.php",
        "CREATE_DELIVERY_REQUEST_URL":"http://gateway.bx/create_delivery_request.php",
        "CANCEL_DELIVERY_REQUEST_URL":"http://gateway.bx/cancel_delivery_request.php",
        "HAS_CALLBACK_TRACKING_SUPPORT":"Y",
        "CONFIG":[
          {
            "TYPE":"STRING",
            "CODE":"SETTING_1",
            "NAME":"Setting 1"
          },
          {
            "TYPE":"STRING",
            "CODE":"SETTING_2",
            "NAME":"Setting 2"
          }
        ]
      },
      "PROFILES":[
        {
          "NAME":"Taxi",
          "CODE":"TAXI",
          "DESCRIPTION":"Taxi Delivery"
        },
        {
          "NAME":"Cargo",
          "CODE":"CARGO",
          "DESCRIPTION":"Cargo Delivery"
        }
      ]
    }
    

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

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

    • предварительного расчета стоимости доставки (CALCULATE_URL)
    • создания заказа на доставку (CREATE_DELIVERY_REQUEST_URL)
    • отмены заказа на доставку (CANCEL_DELIVERY_REQUEST_URL)

    Если интеграция с заказами на доставку не предполагается, то CREATE_DELIVERY_REQUEST_URL и CANCEL_DELIVERY_REQUEST_URL можно не передавать. В этом случае флаг HAS_CALLBACK_TRACKING_SUPPORT должен быть установлен в значение «N».

    При создании обработчика службы доставки есть возможность задать набор доступных параметров (CONFIG), значения которых могут быть уникально заданы для каждого экземпляра конкретной службы доставки. Параметры могут быть использованы для хранения API ключей, номеров договоров и других авторизационных данных для конкретного экземпляра службы доставки.

    Создание обработчика возможно только при наличии как минимум одного профиля доставки (PROFILES).

    Также доступны методы обновления и удаления существующих обработчиков и получения списка обработчиков.

      Настройка службы доставки

    После успешного создания обработчика службы доставки можно приступить к добавлению экземпляра самой службы доставки с помощью метода sale.delivery.add:

    {
      "REST_CODE":"uber",
      "NAME":"Uber Taxi",
      "DESCRIPTION":"Uber Taxi Description",
      "LOGOTYPE":"/9j/4AAQSkZJRgABAQEBLAEsAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wgARCAIVAyADASIAAhEBAxEB/8QAGwABAAIDAQEAAAAAAAAAAAAAAAYHAwQFAQL/xAAaAQEAAwEBAQAAAAAAAAAAAAAAAwQFBgEC/9oADAMBAAIQAxAAAAGfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHz4+tPUw4lrP8Y2dPkY3jIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjIxjJ9YXre3eJ93oe0xZdyqH34AAAAAAAAAAAAA5W9yceyGLZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA++zw+hqQbo3qgAAAAAAAAAAAAGho7mnzN4KcgAAADT2qsvxWSrZeisnaqyyKcm6M6ZrbMFtfEuVs04LJzVhLK/1KRlWGPJwZfnpK2bFeyfqtN/49sYYtkfPrVVs3Ktkq2Fp/WjvYtkPj3W+IhxditZKtkvln5opK8mwEH1j1+fBtOCyVbLXxZe1XFj50wU5AAAAGxr5ZvnsDrM8AAAAAAAAAAAADnae5p8xfCp9gAAAfNWWnVm1WDYrLIreyMqxujCtILOoLow8QdDTSyJyylLKRzV1we9wbPxCB1NBv6G/F7Yw5LRfP18+qsHY5oFj72jvcjohH7BOL2uL1WeFj5lUrikr5q8FKThwacwboaYaMO9Y9cWPhWwypwAAAGXFll87A63OAAAAAAAAAAAAA52nuafMXwqfYABz67vxWoqtajtKrPu0CrFpvVWWRt1z4shVYtSC8WcEGWm9VZLJPFPEtVWLU4MJ7hwlpvVWb9iaB0VVvFqfNW+nytN6qxaY096uNLxaiqx2eLOe16qxaYi0rikW8WoqsTiDdyblWLTeq6sfn134tRVYtRVlpU5fRSkAAZcWWXzsDrc4AAAAAAAAAAAADnae5p8xfCp9gAc6u7ErveqBqQe2pVdqY1kMayraya21q+mNyqnMGnOdN3Bz1xEpbErsUXHS0nd4Xdrfc4HLX3P6HPl+a6HW573z3xag4/SDxW+lu6XXZwSeTrt8Tt8rfCv9xSKyqK9NRC5H251BZ1z1wM6bQrmxq53agasC1KrtTGs+jGsgAMuLLL52B1ucAAAAAAAAAAAABztPc0+YvhU+wAOdXdpaOlBXaxFyOvLU53RoTBQlVtZOhdjrlYjRgruc7e3VkzDLnRKW61j4rNYjWr133ZPmi+tsY1lz+h8fflWrEbdWu/bDHRGDbArfSsb53KtdrEfXmr28ObGshF9RSK2Zr69eu1iJ/iLTrT3MuwFX70K5tLR0oa7WIuRV3anP6NCYKEoADLiyy+dgdbnAAAAAAAAAAAAAc7T3NPmL4VPsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABlxZZfOwOtzgAAAAAAAAAAAAOdp7mnzF8Kn2ABx4FPIFvVPXjRh9eD14PXg9eD2z6utHJscOFTWF2Pjx6vRePR49Hj0ePR7atVWrj2YvEZdEbcYXYgDNkNVsa4BsWdWFn4tmJRaUxa/F7YVfXZbjiiVxY+WpIDlRyzsZSrLiN+x63sjEtBlzgAMuLLL52B1ucAAAAAAAAAAAABztPc0+YvhU+wAOPAp7AugputybY0oYJo3HCCCA6W3LJSU/zbLrkw2hV9oZFjiQuaQux8OnzLYvxQBbIqbBcAo7y5q5I+D21KrtTGsxeJS2JXI2zrWNdig65BUy1vCqtS4OeVCDPZ1Y2diWolF5RF78P1eFH3hcjVTa1UnD3NPZLpBUnI6nLN6x64sfCthlTgAMuLLL52B1ucAAAAAAAAAAAABztPc0+YvhU+wAOPAp7AugprYqe2NKHtQibwgggLJk8VlBHa4sauTDaFaWVlWOJDZnE5vj4tSrbPvxdzW2IMSrdpboFuYMvyU3h7vEPm0azsvGsxiKSyM3I8dgQGfXYpW8jBJfKW2C4udUuY+H0MtlVvZGJaiUck8bvw/F20tdFyP2FzQV3IpEGplrI4XyG9Y9cWPhWwypwAGXFll87A63OAAAAAAAAAAAAA52nuafMXwqfYAHHgU9gXQU1sVPbGlD2oRN4QQQFjSmLSgj1b2HXgsqtbKybHHh8tic/wAfVn1XaV+LsQec8cqjdmXYOs+uMQbje+H1Z1Y2bkWY1GJPF7UWCxK7sW7HKoxJtIqHYsP0rVZMENEGxY9cWPjWYtHpFGb0X1dVK3Pbj++F16vLB6tQ28cmo7yqg4gN6x64sfCthlTgAMuLLL52B1ucAAAAAAAAAAAABztPc0+YvhU+wAOPAp7AugprYqe2NKHtQibwgggLJlEXlJFK4sitz6s2r7QyLHEhkzhdj4y2nU9sX4u157DiXqk3C0YRM8hR3vc4Jls+rLUxrMaiEsiVyNY9cWPdilPvvBO981gLQqXa4hi9+RtWXWNnYlqJRyRxe/DmuukLwuR/FWWtVJw7WqmTFmRqS+FHN7RN6x64sfCthlTgAMuLLL52B1ucAAAAAAAAAAAABztPc0+YvhU+wAOPAp7AugprUquf6UMzhc055Trcyk7k2tskSrqYQ88tCr7QyLHEhc0hdj4WxU9sX4u1A55CyAt/fJ32cWUgMLlkTPbUqu1MazF4lLYlcjWPXFj3YpXFZVFitn2Ph9+HyDPZ1Y2diWolF5RF78PtiV0uR2LC+cH18iw/quh3+AG9Y9cWPhWwypwAGXFll87A63OAAAAAAAAAAAAA52nuafMXwqfYAHHgU9gXQU3Q57Shu7JU9gHYeeH1q8uvjU1w8tCr7QyLHEhc0hdj4WxU9o34pC1hstb4NzDw4KaeqHtqVXamNZi8SlsSuRrHrix7sUrAA5fU5ZUIM9nVjZ2JaiUXlEXvwhcjAAAA3rHrix8K2GVOAAy4ssvnYHW5wAAAAAAAAAAAAHO09zT5i+FT7AA48CnsC6CmGlCB9eeAADy0Kws/IscSFzSF2Ph56vxePR49AAHtqVZaeLZi8SlsSuxvr5XYvt8D7fA+/PkAZ7OrKzcS1EovKYtfhC5GAAABvWPXNjYVsMqcABlxZZfOwOtzgAAAAAAAAAAAAOdp7mnzF8Kn2AB49evHo8ejx6PHo8ejz0PHo8ejx6PHo8ejx6PPQ8ejx6PHo8ejx6PHo89Dx6PHo8ejx6PHo8ejz0A8AAMuLLL52B1ucAAAAAAAAAAAABztPV4mBbkqNK/3JUaElRoSVGhJUaElRoSVGhJUaElRoSVGhJUaElRoSVGhJUaElRoSVGhJUaElRoSVGhJUaElRoSVGhJUaElRoSVGhJUaElRoSVGhJUaElRoSVGhJUaElRoSVGhJcsV3ZPJqOjpAAAAAAAAAAAAAQSEWrVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7/AnpNQAAAAAAAAAAADEZWjgOrVM60itm9ogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzGW4In1juuTnN9gzgAAAAAAAADn9DhHxhAAD54EhEE1rE9K5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5WMK5zz/wi8izAABkxjpdOP90yAAAAAAAAAwanSHEwSIRlI8RwXYxnLb+M1Gf4Mb3wAAAAAAAAAAAAAAAAAAAAAAAAAAHp4+8hgbeQ0HTyHId3KcDP3By9raAAAAAAAAAAAAAAAAD5+hi+NganzujQ+eiOZ89Ucn57A4vnbHD+e8OB5IBHkhEd8kYjaSCNpII2kgjaSCNpII2kgjnsiEeSER/3vjg+90cT3tDj+9ccr3qDm/XQGl9bY1/rMPj7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//xAAyEAAABQEGBQMDBAMBAAAAAAAAAQIDBAUQERITFDQVIDIzQDAxNQYhYBYkQUMiI1Cg/9oACAEBAAEFAv8AxgqfIgbyzGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqGNQxqBOrIJkAjJReeZkkluGv8DSo0mhZLLznV41fgiVGhRHeXmOqwt/g0dX28yR6urjjVxxq441ccEZKK1b7TatXHGrjjVxwh1t3kWtDZauONXHGrjhMllSuTVxxq441ccauOCMlFat9ptWrjjVxxq44Q627yLWltOrjjVxxq44KSwpXqMn/s8yR1emftyRNnbVd7bRum2rbO2FvbT9uSHs7arvraNyVbZWw976jXc8yR1emftyRNnbVd7bRum2rbO2FvbT9uSHs7arvraNyVbZWw976jXc8yR1emftyRNnbVd7bRum2rbO2FvbT9uSHs7arvraNyVbZWw976jXc8yR1emftyRNnbVd7bRum2rbO2FvbT9uSHs7arvraNyVbZWw976jXc8yR1c87Y3mLzF5gjO+4hcQuIXEJR/u7zF5i8xS/vCuIXELiFY+yrzF5i8xSvvMuIXELiE0v2V5i8xeYIzvuIXELiFxCWf7y8xeYvMUv7wriFxC4hWPsLzF5i8xS/vMuIXELiEwv2d5i8xeYvMF7c7Xc8yR1c87Y2l78kveW0rZW1nqtpO8tnbG0vfkl7y2lbK2s8lK3ts3Zche3O13PMkdXPO2Npe/JL3ltK2VtZ6raTvLZ2xtL35Je8tpWytrPJSt7bN2XIXtztdzzJHVzztjaXvyS95bStlbWeq2k7y2dsbS9+SXvLaVsrazyUre2zdlyF7c7Xc8yR1c6kpWnQxRoYo0MUaGLyqhx1q0MUaGKNDFDbaGk2usNPDQxRoYo0MUNxmWVWqSladDFGhijQxRoYvKqHHWrQxRoYo0MUNtoaTa6w08NDFGhijQxQ3FYaVapJLToYo0MUaGKNDF9FrueZI6vwZrueZI6vwZrueZI6ueprU3B1cgauQNXIGrkDVyBq5A1cgauQNXIGrkDVyBq5Aa+7NYWtuLqpA1UgaqQNVIGqkDVSBqpA1UgaqQNVIGqkBMl/EKy640jVyBq5A1cgauQNXIGrkDVyBq5A1cgauQNXIDEp85ArLrjatVIBSpF+IxiMYjGIxiMYjE9L7Y1UgaqQIUh5U30Gu55kjq56psLhcLhcLhcLhcLhcLhcGuzWNrcLhcLhcLhcLhcLhcCL/IVnouFwuFwuCGsQuZBtJuuFwuDBfuBWeq4JIsWWgZaBLqrUWTx1oQ5kWaMtAWw04241lO3CFvfQa7nmSOrnqexsapkt9rg84SIb8S2PAkym+DzhIgSYrdjXZrG1sap0t5vhU4cKnDhU4Lp8tAMrjtLqFZ6LG47zpaKUNFKC4km448hI0kgjciSEWsbgVnqCeqyr/KCI6bEuyqFhqYhb30Gu55kjq56nsbKR8WPqL2soGwFdLFDMiNIa7NY2tlJ+L5H4jElNRpioR2F1Cs9FlA2Nn8WVD42xjcCs9QT1WVf5QR0G7JsqKsdRELe+g13PMkdXPU9jZSPix9Re1lA2F4rW1/qDXZq+1IrxlqFJ+MDj7TI10YJWlZB1tLzTzC2XCQoxhMjFZ6CSahgUKDsiBquGYgZiRjbE9aVQDQoEhRhpJk+Kz1ZahhNKrJ1HelTP0/IFPpSIShJfTGjGZqUIW99BrueZI6uep7GykfFj6i9rKDsBWdr/AFJ+xN9qr/eMMBYaV8YYrxXqwkIkpcd6yrJwVL3Cf8FCsdJ/cywpFB2Qrl2iUeJX9pFefUd1w6iZ/wAXxVzuPBebf2dL25HXW2W6lUTmrshb30Gu55kjq56nsbKR8WPqL2soOwIVvaf1f0t9uqdn+DK8Ur700x9QWIQa5HsQqTmZUT7Zdv8Amrj+RQdkQrplollcr+5vuf1oH9THUKt3PvdfhOyRVo0Z+JUGJpipRtVCthb30Gu55kjq56nsbKR8WPqL2soWwFcvOEof0t9ur7YXoFL+N/iZBRMHAGBHp0eK4J81MOOk7wfbLt/zVjIk9KBQdkJMZqW2dEhmODxMRUWGQltJZfR7/wBTHUKuV54iMESTCh7Cr/KU+TpZtlVjaadZC3voNdzzJHVz1PY2Uj4sfUXtZQNhcK99oAJRpDXarG1IzIZhilfem2XC6yp0lagR3GZmYQq4xV1GSPeygbG4XWXWVMz4gR3GajUI53Pis9WYoYzM/cYRV/lBSZOpgitRs6HZC3voNdzzJHVz1PY2Uj4sfUXtZQNgK/sLGuzWNrZSfixW5L0dXEpoj1mU04hZONirxyjzwXUKz0WUDYirSnYkXjc0cbmjjU0OuqedsY3ArPUE9VlX+UFEk5MwGRKKZHOLKELe+g13PMkdXPU9jZRlX0sfUKf9VlCK6nD6gV+zsa7NY2tlJ+LH1F12Ur4wfUJf7AXUKz0WUDYiv7DmY3ArPUC+x/qCMP1BGE59MqYCM0qT9QMYf1BGFUmMTViFvfQa7nmSOrnqexs+n3r2RNilMivRH4640GRKXHZTHYH1A9ifsa7NY2tlJ+LFdYdeVopQj0iU+tttLTYr7mKYC6hWeiygbEV4jODhUMKhhUMKrWNwKz1enC3voNdzzJHVz1PY2QpRw5TbiXW+SQ+iMw+8qQ/Y12axtbKT8XyuuoYakvqkyAXUKz0WUDY8tS+NsY3ArPV6cLe+g13PMkdXPU9jbAqTsI49UiSCIyMGoklIq8Vgpk52a5a12axtbKW80mm6hkahkahkKlx0h6tRGim1B6aqwuoVnosoGx5al8bYxuBWer04W99BrueZI6uep7HlIzIGZnzNdmsbX0y6hWeiy8yGJQxKGJQxKGJQxKtY3ArPV6cLe+g13PMkdXPU9j6jXZq+29MuoVno9NjcCs9Xpw976DXc8yR1ehcLhcLhcLhcLhcLhcLv+m13PMkdX4M13PMkdX4M13PMqU5MV7jDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDY4w2OMNjjDYiVRD0rzPqFH+X4NRkY6n5lXj6iB+DfT7FyPNqkLRyfwSNHXKfZaSwz5JuISDlIByzByHDD6dQ3JirjK/AmmlvLiRyiIJ9wgUpQKUkE82rxXJNwU4tfMaSUT1LSoLgSUDIeIZLoyXRkujJdGS6Ml0ZLoyXRkujJdGS6Ml0ZLoyXRkujJdGS6Ml0ZLoyXRkujJdGS6Ml0ZLoyXRkujJdGS6Ml0ZLoyXRkujJdGS6Ml0ZLoyXRkujJdGS6Ml0ZLoyXRkujJdGS6Ml0ZLoyXRkujJdGS6Ml0ZLoyXRkujJdGS6Ml0ZLoTDkKDVKMNtIZTypWpIRKBHeXguMqb/EUNqWEJwI8JTKFA4gOO4QNCi/CSSZgmHDBRDCY7afJNKTBsNmDioGkGlUNM4MlwYFF/2cKhlODTuDSrBRAUVAKO2QJCS/42FIy0GMhsadsaVsaVI0hDSDSGNKoaVY0zg07g07gyHBkuDKcGWsZaxgUMKhhMXGLjFxi4xcYuMXGLjFxi4xhMYVDAoZaxlrGU4MlwZLgyHBp3BpnBpVjSrGkUNININIkaVA0zYyGxlNjCn/wAf//EACsRAAAEBAUFAQACAwAAAAAAAAABAgMREyAyBBAUMVESITBBYSJQgDNCUv/aAAgBAwEBPwH+miGP+h0kIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEIEOkgtgj2BlDsfnYT/ALfwb6e0fO1YVTrnQNQYbcNZ5OL6SiNQYQ8alQyUcCiNQYJ8456gxqDBdyyU+ZHAagwhXUmOTjppOA1BhtfWVS7T87VhVYj1lh98n7cmb8nLTyTvSnbJdx5M2ZP3ZYfapVp+dqwqHHeg4DUfB/mGn+iEnuNR8HVN/I0/0S5f6Go+Cb1/kaf6JHT3iNR8Go+DT/Rp/onw7QGo+CV1/oaf6Jkv8jUfB0Tf0NP9EZPYaj4G3OuhVp+dqwqMRdlh/eWI2LJi7J6zJu4slWnSrfJu0snrzyYtyxG+WH90KtPztWFQ40ajiNOoNNmjfJ1BrLsNOoNtGk45OJ6kwGnUEsmRxyMokNOoadWZsHEadQSUChktk1KiNOoNp6Shk62ajGnUGmzRvQq0/O1YX8Gq0/O1YVD5/oRMRMRMRMRMMH3MPH+hExExExExExh/YeM+sRMRMRMRMRMMHuHTPrMRMRMRMRMRMMbUKtPztWFQ/dVh9zD19WH9h6+rD+w7edWH2oVafnasKh+6pj2HrqsP7D19WH9h286sPtQq0/O1YVD91WH3D19WH9h6+rDh286sPtQq0/O1YVD91WH3MPX1Yf2Hr6sP7Dt51YfahVp+dqwqH7qsPuYevqw/sPX1Yf2Hbzqw+1CrT87VhUP3VYfcw9fVh/Yevqw/sO3nVh9qFWn52rCofuqw+5h6+rD+w9fVh/YdvOrD7UKtPztWF/BqtPztuJJMBNRyJqORNRyJqORNRyJqORNRyJqORNRyJqORNRyJqORNRyJqORNRyJqORNRyJqORNRyJqORNRyJqORNRyJqORNRyJqORNRyJqORNRyJqORNRyJqORNRyJqOQp1MP7+f/xAA2EQAAAwQHBgYCAgIDAAAAAAAAAQIDBAUzERMUFSBRUhASMDRxgSExMkJioSNBIlCA8DVDYf/aAAgBAgEBPwH/AAzMyIqTDxElGdDIKbtFeahWLzFYvMVi8xWLzFYvMVi8xWLzFYvMVi8xWLzFYvMVi8xWLzFYvMVi8xWLzFYvMVi8xWLzFYvMVi8xWLzFYvMVi8xWLzFYvMVi8xWLzFYvMVi8wTZoXkoMIktPg08SCFkst5PHibf/AKi/o4a3NK6s/I+O/HS8KxObol4ppPyF1I1B8c0sEkZHsdGBN2m6YupGoPMPSyZGsj2MWdY0JGYupGoLhiEpNVO26kahdSNQWW6oy2MYahbMlU+YupGoPLImTU0FsdHFLdnvmYupGoPjsTBRER4nc6GqevHfZ6sUJ9/bZFfQnZDJ3bZEOXV/v72Ok9OxtLV0wtZh7HWSnpsf+YV/v62QyR32RWYXTEwmp68d9nqwOrlaE71NAun5/Q/4/wCW92F7fD7G/eH8PTQLp+f0Kiw/lpp/Qvb4fYtVs/BRRSLp+f0LDZ/zb1NAvb4fYvKs/Hu+fgLp+f0Lp+f0L2+H2L2+H2LtrP573mLp+f0LfUfi3aaPAXt8PsWS1/npopF0/P6FfYfxUUi9vh9irt/8/TQLp+f0Ht0s9HjTTgYTU9eO+z1YIVLPrsi3s77IV61bInI77IfzCdj3IVsYzE9cLKWWx6nq67HDl07InP7bIVLPrsi3s74GE1PXjvs9WBzfEMEGlRC9WWRh9ekt6N39bHJ5SwUZqF6ssjD2/IbM90i2OzUmTUlmL1ZZGG0RZtGZpIvPYzVurJQvVlkYvVlke1ETZpSRUGL1ZZGGyyW0NRfvY7RBDJkSDIXqyyMPbcmzTeLY5viGCTJRC9WWRh9ekt6N0vLAwmp68d9nq/o2E1PXjvs9WCGoSbHxL9irRkKtGQq0ZCrRkKtGQiiSIk0CHJSbDxIVachVpyFWnIVachVpyEVSRbtH/ocEJNgVJCrRkKtGQq0ZCrRkKtGQiiSJSaA4oSbumkhVoyFWjIVaMhVoyFWjIRRJE0KjAwmp68d9nqwQuT3xRb0pENkYot7O4h/LliivqSHDl04orMLpgYTU9eO+z1YIXJ74ooXgkQ6Rii3t7iH8uWKK+aQ48unFFZhdMDCanrx32erBC5PfFFfQkQ2RiivtDhy5Yor5kHKQnFFZhdMDCanrx32erBC5PfFFvSkQ2Rii3s7iH8uWKK+pIcOXTiiswumBhNT1477PVghcnvii3pSIbIxRb2dxD+XLFFfUkOHLpxRWYXTAwmp68d9nqwQuT3xRb0pENkYot7O4h/LliivqSHDl04orMLpgYTU9eO+z1YIXJ74ot6UiGyMUW9ncQ/lyxRX1JDhy6cUVmF0wMJqevHfZ6v6NhNT14706NltlKSkWF40iwvGkWF40iwvGkWF40iwvGkWF40iwvGkWF40iwvGkWF40iwvGkWF40iwvGkWF40iwvGkWF40iwvGkWF40iwvGkWF40iwvGkWF40iwvGkWF40iwvGkWF40iwvGkWF40iwvGkWF40iwvGkWF40iwvGkMnNuTQjNP+fn/8QAPxAAAQICBgcECQMDBAMAAAAAAQACAzMQESAxMpISITBxcoGRQEFRcwQTImBhgqGxwTRCUiNDUBRioKJTY9H/2gAIAQEABj8C/wCGD7OtXrEViKxFYisRWIrEViKxFYisRWIrEViKxFYisRWIrEViKxFYisRWIrEViKxFYisRWIrEViKxFYisRWIrEViKxFYisRWIrEViKxFYisRWIrEViKxFYisRWIrEViKxFYisRWIrEViKvXtBav8AAVlfD3DrH+A+A9xawq/eQt7a0bWdD6qfD6qfD6qfD6oEGsGxoviNafAlT4fVT4fVT4fVHQe11XgbFb3Bo+Knw+qnw+qnw+qDWxWEnursz4fVT4fVT4fVT4fVAg1g2NF8RrT4EqfD6qfD6qfD6o6D2uq8DYre4NHxU+H1U+H1U+H1QDYrCT8dqO2jaGzB4BY+UWI3Kx84sQeKwbMHhFg8IsRuVj5hYg8W1b20btobMHgFj5RYjcrHzixB4rBsweEWDwixG5WPmFiDxbVvbRu2hsweAWPlFiNysfOLEHisGzB4RYPCLEblY+YWIPFtW9tG7aGzB4BY+UWI3Kx84sQeKwbMHhFg8IsRuVj5hYg8W1b20bthG4VeVeVeUNZVwVwVwVwUbjKvKvKvK1/yKuCuCuCg1fFXlXlXla/4lXBXBXBRuFXlXlXlXlXBXBXBXBRuIq8q8q8rX/Iq4K4K4KDV8VeVeVeVr/iVcFcFcFG4VeVeVeVedi3to3bCNw2BZjcZsfMbEHnY+Q2I3DsY3GbHzGxB52PlNiNw7VvbRu2EbhsCzG4zY+Y2IPOx8hsRuHYxuM2PmNiDzsfKbEbh2re2jdsI3DYFmNxmx8xsQedj5DYjcOxjcZsfMbEHnY+U2I3DtW9tG7YFrhW03hSGqQ1SGqS2yXOgtJN6kNUhqkNWjDaGjwsD1jA6rxUhqkNUhq0ocMNNgtcKwe5SGqQ1SGqS2yXOgtJN6kNUhqkNWjDbojwsD1jA6q6tSGqQ1SGrShww02C1wrBvUhqkNUhqkN2Le2jd7jt7aN3uO3to3bB7mOLTWNYU+JmU+JmU+JmU+JmU+JmU+JmU+JmU+JmU+JmU+JmU+JmU+JmTOEJhY4tOn3FT4mZT4mZT4mZT4mZT4mZT4mZT4mZT4mZT4mZT4mZT4mZD+tEzUQtB7m6zcVPiZlPiZlPiZlPiZlPiZlPiZlPiZlPiZlPiZlPiZlPiZlDBjRKtIfuog6D3NrruKnxMyH9eJmV6vV6vV6vXrYUaJo940rlPiZlPiZlBDoryNK7S2Le2jdsH7xtWcITOPaCiDvNqvuorZYh8Qog86BvWBvRYG9E+D/pQ7R761+j+qOg0B4vaQsDeicwsbU4VXJ8M3tNVEHi2Le2jdsH7xSIkOFW03HSCk/8AYJvrmaOldrp04MPSbXVepP8A2C040PRbXVfSzhCZx0iJDgktNxrC/TnqF+nPUL9OeoXtejxOQrVRvsCiDvNNcOE948WtX6aLkX6aLkTWCBFyqpvosTKg4ejxdd40U4mBEDR36NMPiFEHnQN9Mbl9qIURvc6mPvog8Wxb20btg/eKYG4/ej0fnS7zDQwf+wfYqsUM4QmcdMDd+bNUWGHfHvWm32oJ7/CkUQd5pf5lkBekcBph8Qog86BvpjcvtRDYLy4Uxz/uqog8Wxb20btg/eKYG4/ej0fnS7zDRD8z8FHfQzhCZxrVRA3fmgesiNbX4lT4eZVscHD4UOhvFbXCop7HftNVArFEHeVqof5lGtYx1WMdVib1UcBwJ0D30w6/5CiDzVyFY76XxmxIYDvFTYX1XrHO04vj4UPiu/aESbzrog8Wxb20btg/eKYG4/ej0fnS7zDRD8z8FHei7wTK/wCITONaDear0lA3fmiBrqFRXsu1psRpuPtjxFMVzT4V9FpOch3tNEHeVoDUAtRrKf5lDa//ACDuRK5KpVXNC0mOuWk46lDINbS4UQj366l7R1oNGsV2i+I4NaO8rRbWILbh4/GmDxbFvbRu2D94pgbj96PR+dLvMP2oZ5n4KO9HemcIUPjUTxQ/gFBPw/K5KBzoLG3u1IUekkd2romocVEDiUSh/mLkm1iv+oiFyQR3o7kN6Z5go9H5p1V9etBo8dZpdCfp6TfBqcIWlW3xFD2fuHtN32IPFsW9tG7YP3imBuP3o9H50u8w0NA/mEGjuR3pvCEzjWm3mrioFXh+aG6byNFTYn0Re0Ev/k6gu/uHCE8m+pNQ4qIWqvWUa73UP8ygMi16INeoqstfmVdT8yB0X5lHhswtdUEdyG9M8wUQh366l7Y1rVXqpjcvsmRP23O3UuqwP9oUweLYt7aN2wfvFMDcfvR6Pzpd5hobr/uCjUmbgmca1LuUA/D82jHhPc897XfhalrXOiFvNL/MtRx/uVYWtQ+IUQea8UB8aY3L7UNrPts9l1HrBiha+XfTB4ti3to3bB+8UwNx+9Ho/Ol3mGhvmClnCEzjpgbvzRB9VEcyuuupfqXoGI/1jO8FNe3C4Vih2iKmv9oUCiDvNL/Moa+EQHadWsLG3Ksbcqxtyp0R+J2s0w+IUQedA30xuX2o9WcMXVzoINxUSF3A6t1EHi2Le2jdsH7xTC+FY+tEB3g4imvxeaIbfF/4pZwhM46YG780ej7jTAr/AI0QD8DQKIO80v8AMob5gtw+IUQedAUuL9FLi/RPjMBAd40BwvGsIaUKJX31VKXF+iY+Gx7XDUa6IPFsW9tG7YP3imLB72nSFDoR1HuPgVoxIThy1INZDNXe4jUEyE25oqohwR+wVnnSzhCZx0wN35og+rhvfVXhC/TRcqAdDMNne5ybDbhaKhQxn8WUCiDvNL/MobUP7gWF3RYXdFhd0WE9KYfEKIPPaQeLYt7aN2wfvFLYo1i5w+Ca9hra7WDZdFiHUE+K+9xrpZwhM46YG782nRIhqa1PjO/caBRB3ml/mWvSOA0w+IUQee0g8Wxb20btg/eLFWKEb2rVFDXfxdqWpVk1I1P9Y7wYq36mi5o7rDOEJnHTBDojAau8qazMprMymszLXHhj5l7LjEPg1e37LBcwUiiDvNL/ADLXpHAaYfEKIPPaQeLYt7aN2wfvFrUSFrNdpnCEzj2gog7zTqJWJ3VYndVid1WJ3VYndViPWmHxCiDz2kHi2Le2jdsH7xtWcITOPaCiDvO0h8Qog89pB4ti3to3e47e2jd7jt7aN3uO3trGuY41tr1KU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqU9SnqHDENwLj22A/ePceH/ALQT211WJntD3HiekH93sjt3sj+k/W3/AOe4rYTLz3+CbCZhaKu1a3BagStTQr0WRCSFr1t7ne4egwVleyfbN7liWsBawQsXZama/itbrVThWFXBdo/A3KXXw61KflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VSn5VKflUp+VaoTuepVxn8mrRY2oWtRqXt9VWOxeI8fdHUEG9jwr2XdVdWtbT7k6gVhq3r2ndFdXv7TrAWFd61PV4VwWArCen+ZwlYCrleFretZKwrU0f4a4LAFhVy71eVjWP6LEFiC7l3K76rCsKwFYCsDuiwHosJ6LCeiuKuKuKuKuVyuVyuKuKuKuKwnosJ6LCeiwO6LAVgKwrCrvqu7qu5XhYgsf0WP6LEV3q76rCsAWEf8AD/xAAtEAABAgMGBgIDAQEBAAAAAAABABExUfEQIUFhofAgMHGRscFQgUBg0eGQoP/aAAgBAQABPyH/AMYBIAclgrsDqwUgdFX1XVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVi09VgfYE+CcfAHJLkbvuwD9Ddyn2I4j8+7BlfoowgQwgfzXhom4fo7hGF4/NPyc2jVRKolUShlgHBGPBiw24FUSqJVErOEr5uC9djOTKiVRKolXGEgF88NEqiVRKolDLAOCMeDFhtwKolUSqJWeNXzcGDIXJlRKolUSjJSYADfzWs6783TczTcO4S4IvBaz34IeyPIzRc1QN/75BaTzdV8Tmm4dwlwReC1nvwQ9keRmi5qgb/3yC0nm6r4nNNw7hLgi8FrPfgh7I8jNFzVA3/vkFpPN1XxOabh3CXBH4LWe/BD2R5GaLmqBv8A3yC0nm6r4bArCrCrCzETVIVIVIVIRgIEtMzVYVYVYQgBBziKkKkKkKULoLpKsKsKsI3BeGN9KkKkKkIQMAAUrCrCrCYJk1SFSFSFSEQABIE5VhVhVhCAEHOIqQqQqQr6BlukqwqwqwjLJOMb6VIVIVIQgYAAvwVYVYVYVYUDkar4nI1Q4dgnwQ+C0HrwR9kOQEDrycQ+Aj24cG66cGo8MDpyNV8TkaocOwT4IfBaD14I+yHICB15OIfAR7cODddODUeGB05Gq+JyNUOHYJ8EPgtB68EXZDkBA68nEPgI9uHBuunBqPDA5Gq+FwbYBiY8EzLXDloE5HE8EzN64XZwFSw9JwTM3dNZxLgG2CYljyZmbLQJyOPBMyy467ODwrRwTM3DtZxwD7BMBx5szM6r9kzVfsmar4XBUCwRjFV0q6VdKulXSrpV0q6VdKulXSrpESSXJIT9IoGAHawKqNVGqjVRqo1UaqNVGqjVRqo0XMp7CGdLl08FVaqtVWqrVVqq1VaqtVWqrVVo3kQiCV99l2mnvB4Ks0dOJ1n+6z/dZ/us/wB1n+6z/dCGh92f2GSrNVmj5aLkTHk6r4XNgmmSCZIJkgmSCZIJkgmSCZIJkgmSCZIIgxuC2SSB9iBTJBMkEyQTJBMkEyQTJBMkEyQTJBMkEKAIiwQdhcEyQTJBMkEyQTJBABzBWbmfNGXYiSZIJkgmSCBBH+9gguDD1TJBQGIFTCphHbdEAO4eSy/b/ENAaOHfrmqYT4NCuYppIdndIpkggEIQ8nVfC5vE7RLsOCy+7NDijEAu3S0oaF40X/ay+7NBDwYLTf8AVhgVsklsMjbeOHBearc3tbm9rc3tA3ZT9BEIAgIghuDWCzYJC0nFpZwQ6qpVUrkEY30XewM6YeC45UcSyRsBbs87NB62aNbuMlhYGIHzBuItGCMRPcCzQeTqvhc3idu0TWar1t2iQszWUMXIYtFGBWySWwyNus+XDlLzLnQp+pMYHE5H+26wWbBIWk3UeAgXUQiCcwTl4F1DIvK7jxW7POzQetmjW7jJYGdw2trbw8V3qzQeTqvhc3idu0TWar1t2iQWQIn6RA0CMCnQaC3GRRCYHKyx0dAwjcQXlYeAhBcIF/gWf5CcW2wALTk5EII7XZrDw4vs2CQUKOiIEsLpFa54CIxi4QRdgGZCJcE9C6J0KlEZ9AXAgg7OMkBcC6ZR/C6P92aD1QIHudULlly1mUzBz3ACzkGCYMYdNkIZwCZwCOG5HHOzQeTqvhc3idu0TWar1tgtjCwe2QNAgBbo6oXMUepBPmCPgou8KZXoIMWREkj/AKIlnAuiBNIzI+QMAEM9E3BsVFXAUYdQTNuTwWIu1BxJC4t1yG8MWifJGGgy3mQUfMEMfA8AoTBFNdu2Cb5kbwCncXjFcmhOAXL/AFQJYkIErBoUIV+AvTiC5PxEVOhEPhKXkSmt0Hk6r4XN4nbtE1mq9bdikUf0uxAoCGx0W9SWn+ChsZq/CWFcmAgwew+y9ye8keiBYghAae4DMpmMghh19IEMeEAvNsMxKDrerTeZBepAXIYGfIpnIBYNoWUaZR9dFDfZrH6CxcG8gmIEfsIBIK0CHdHvBB0c5gCQ1dYywcf0ttwaDydV8Lm8Tt2iazVetsdsYLcViJL2OBRgAhxEZlbHRb5JGx8ngou8Z4E7hdCjEYA7yUXSRdpgRc16MWGjQ9kOORdgjgnUA3E8tHoiPDkRJmvNsMxjojreXTlF7Atc8BbighFgJenBwOdT6aeSOic60Loi8hfBa2ihvs1j9BZFD0IxCzEIy7RIG/qiYKJbjInqLEfube1vi2pxHe3QeTqvhc3idu0TWar1t2iQWQp95f4GwAXRUVifEthkURciFkl9J12C/AZkAwZEfSzFdSIdCxevOc/b0ivRI65IogwIZcRY1pjgZIkk5LmzWPAXUs19nVciHCKNNw9QiPDFNDoIoVxBuyM1iVoPVBi8jqCFygDIDNEIE3GSx0vaFA9rH8O6/r6t0Hk6r4XN4nbtE1mq9bdokLN0kbDArZJLYZG3WfKwdtnOQW5CNgeuYdsijZuATI2XZDaGcdbNYLNgkLdY8CwhYAd5cxVHqj1T6N2CZwBrdnnZoPWzRrdxksvnuOzD2LBlOBiJojZrMoWaDydV8Lm8TtYmwVhjhO4D/LTvBGjQerADEO9isMCtkkthkbdZ8rNhytMnHf0bBTzeos1gs2CQt1jwLN0kePZ52aD1sJ1IuqH/AEqH/SbMEwjuAFh0WM5IrAhwGP3VD/pBXGSiLxh7s0Hk6r4XN4naMidCH/RrYaZy+UQRoUzvF0KASugyJreTzsAWRet/ga2GBWySWwyNus+VhrbD3S0FXayqSLDIITrAAyFggRndSf8ALNYLNgkLdY8CwRkMgZFV4q8VeIgDkI6rdnnZoPXmaDydV8Lm8TtFMx1BRQIojDHhbwDhiTILEizLKwwK2SS2GRt1ny4myqclXBheaQwFmsFmwSFuseByRbPOzQevM0Hk6r4XN4nwMOHjlOokgB4ZpAHIEZJqIJkshYEcL7WCHj9Bv6NpgVsklsMjaJ6CcBBiVRyo5Ucgb9eFHsthu7lBTgU8IMzM26wWbBIW6x4HJFs87NB68zQeTqvhc3ifFo6FlETqL8JgVsklsMjawkmEgmEgmEhxawWbBIWgZguhZV4q8VeKvFXiJgxKOq3Z52aD15mg8nVfC5vE+YYFbJJbzIpjIpjIpjIpjIpjIpjIpjIpjIpjIpjIpjIoTAMRZsEgmTJkyZMmTJkyZbPOzQeqYyTGSYyTGSYyTGSYyTGSYyTGSYyQ9jydV8NjJDsmSHZMkOyZIdkyQ7Jkh2TJDsmSHZMkOyZIdkyQ7Jkh2tYSTCSYSTCSYSTCSYSTCSYSTCSYSTCVjJhIJhIJhIJhIJhIJhIJhIJhIJhIJhIJhIJhIWsJBMJBMJBMJBMJBMJBMJBMJBMJBMJBMJBMJcnVfsmar9kzVfmnyN8ITVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwVeCrwTH2DkiX5sJ4H0P8Af0d8NcR7N7/NKzvpUdH/AEcorF9Yjr4/OKQ3gSyn+ih9zcAYkoMjMn5UMCE/gLArqVgUdAnNnnAzCYouwBcf9/QwssnYdU0j4Fc+XRTZ1CDitEdpl6hAvnco/iDMiBTQUcmlhxG4hYgi4o8XHroQjGZISGPdKulXSrpV0q6VdKulXSrpV0q6VdKulXSrpV0q6VdKulXSrpV0q6VdKulXSrpV0q6VdKulXSrpV0q6VdKulXSrpV0q6VdKulXSrpV0q6VdKulXSrpV0q6VdKulXSrpV0q6VdKukXuWf9EcQABuvTV2VjxEb9DOwOJEABHB/BIcMixuyX6iTwp4K/B2H4d7kAZi5fyQUMDoKgY+v0mJroF78LCA6FMBAAAw/Iiq6hYFHS5EwI/aP+gI4Z0Q4nQoh9CMcNGIiD8wDQL9IGQD4R1KBohRP4BBRamTqVBz9fDE0e0jHdlEuFE+MfaOR+0cNeY7IlgijWbm96yu5ZdNohZuxKTw4E6VGVOVIVIWY7LMdlmOyzHZUhUhU5Ubhgo2KDYGfW8QsumSjN77LoSBYgjJkGIkTT+0A4igHhQBDspqHYQAEB/3/wD/2gAMAwEAAgADAAAAEPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOU4gQQQQQQQQQQQQQQQQQQQQQQQQQQQQQUM9vPPPPPPPPPPPPPKAwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww4vPPPPPPPPPPPPPIQwwwwyABQ0gAwxwBgxQACQ6wCgwwCgwwwwx/PPPPPPPPPPPPPAAwwww6AFAwgAA1wFw1QAKg6wKAwgKAwwww1vPPPPPPPPPPPPPAAwwxDYgFjSgBjXAEDXQAJDYAJDQgBjQAww1vPPPPPPPPPPPPPAAww6AKQ1QFg1wAgwwAgQwQAwwQKwwwKgww1vPPPPPPPPPPPPPAAww8waA1gVg0wQAw4VQwyASwyAag0AYgww1vPPPPPPPPPPPPPAAwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww1vPPPPPPPPPPPPPAAww6QAAAAGqQAAAAKssMOOMKwJCADACQww1vPPPPPPPPPPPPPAAwww0CAJJPQVDDAAKaBIIEAKK1KEPIKAww1vPPPPPPPPPPPPPAAwwwwKAHNON5MJKAJ9PBBNLComPHIAKAww1vPPPPPPPPPPPPPAAwwwwKAOEFreJCJHBO0PCFPFVsIFDAKAww1vPPPPPPPPPPPPPAAwwwwKABIP0ZDJHIGSgLMIMCq1KFKAKAww1vPPPPPPPPPPPPPAAwww1ODHCPQVPAPIKaAKABAKK8IAMIKAww1vPPPPPPPPPPPPPAAwww0GNPAPQVPOJIKaAPPKAKKwAAAAKAww1vPPPPPPPPPPPPPAAwwxwAEAANRXPPDDK7DDDHDIJTDDDDIAww1vPPPPPPPPPPPPPAAww4QQQQQQccccccYYQQQQQQYQQQQQYQww1vPPPPPPPPPPPPPDzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz3PPPPPPPPPPPPPPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPPPPPPPPPPPPOOHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPMOPPPPPPPPPNHPPPKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANPPPLHPPPPPPPPPDLOOPOMPPPPPPPPPPPPPPPPPPPPPPPPPPOOMNNDHHPPPPPPPPPPPPPPPDHPDDDLHPLHPDDDDPDHPPDHHPDHLPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP/EACoRAAEDAgMHBQEBAAAAAAAAAAEAEWEgMRBxoSFAQbHB4fAwUFGB8YCR/9oACAEDAQE/EP4zAJLBAAf/AAgOwCiUSiUSiUSiUSiUSiUSiUSiUSiUSiUSiUSiUSiUSiUSiUSJbgIE+yUQuJ649pfXsYSzceuLVBGMLqFHAIwM2UKEDGBzhwUKLANjCoUbBwI2WUKMA8GWChRyEioHBG6i/wC2F3Dn4WPOFGs5jA2xs5Ya7Cx5xw5OF3OrSHcxYDryfsv8N93T/wALMP8AS8n7I8M3FP8Awvluy8n7I8Izp/4ToW1eT9l4P2T/AMJ/4X0kryfsn3Gfan/hfHdv1eT9l8pk/wDCE1/peT9ltWxmo0h3MWMsOlQOThe+ueGqw0lN3PDTYeDLDn4WcsOlRpDuYEgKkCcPlgEBJAn+OBCApAh8iNmDwFIFIMSyLhSBHGXDA0AqQI77AKIKkCcPlRpD7kNIdzBBsngplMplMpkYuFGBsVMplMplMiJ2oQBMVMplMplMjEOQjBUymUymUyMk3+aNIdzHLrF6rpK5VYnkyqu50aQ7mOXUe2t9JXKrK0u50aQ7mOXVcV6suVWGsXc6NIdzHLrF6rpK5VYnkyqu50aQ7mOXWL1XSVyqxPJlVdzo0h3McusXqukrlVieTKq7nRpDuY5dYvVdJXKrE8mVV3OjSHdAyZMmTekyZMmTeppD64KR2+xySSSSSSSSSSSSSSSSEmAeH9+f/8QAKhEAAQIDCAMBAQADAQAAAAAAAQARYZGxECAhMaHB0fBBUXEwgVCA8eH/2gAIAQIBAT8Q/wBMyIjAI4wx78n567kiTmP9KjZlRsyo2ZUbMqNmVGzKjZlRsyo2ZUbMqNmVGzKjZlRsyo2ZUbMqNmVGzKjZlRsyo2ZUbMqNmVGzKjZlRsyo2ZUbMqNmVGzKIuQf0o2PqeRz3FApHB/cwYueJ2G8v8G8L0QP/o/cziFBexjDBlF+FHaIqYuWxsdGwZ8P4o7REykhvXkgWCJFgTKO0QyHgCfHgWBR2ijtEED4JFhFmCAPhR2iKk4DagGx2MFyMFHaInYuHx+3iFj0r++u2F7J082a7azP+qiyios1os1ylgzt1prZp1LKaiyo2s7UTe0Cv767YXDlyRbJ9wndOUDlOwab5pnThHKDyPm/iCd05QzMEzZ+Xx9ekzpwiOBHO7Njkw9e07pygf2LMztFzRM6cJni+Z8nwfJO6cpwx6TTOnCZ04T/AAczNk+PtO6cpn9ydnbyzGqZ04RyUOzs2Gbj16TunKGd9ru2cMfXtM6cI5M+Bs4w9p3TlfRnhsmifdzQK/vrthc60BZm6eLNNvZQb2VVDZoTZplbDlbpRSzWq2V1TZSb2dKAs6P5c0Cv767YXDoCSXwb0IrpDlYfEYs4txYBIlw2C6Q5TywLg4tGNg83AfKIIXSHKOMcG8c2ANZAgyK6Q5R/4XNpY4QA8crpDlZaxE2GzEh8m8kn2ukOU1sgMBjYdoXL4N6+rpDlY/Bizby3FzQK/vrth/g9Ar++u2FwyIDi8QChZBQsgoWQULIKFkE38DE5fxBBAcT4UFIKCkFBSCgpBQUgibTf8IxgJx8RKhZBQsgoWQULIKFkE1hsCiAAnHxEqBkFAyCgZBQMgoGQQFBsNzc0Cv767YXM36oL2pOyyfpvZunhV9Te0p2VVU3u1E3NAr++u2FzN+qC8Av+zsmAG9m8GfrJV9TeHGgULf0qb3aibmgV/fXbC5m/VBe1B2WT9N4HAiOyFg/am9oTVG5vtTe7UTc0Cv767YXM36oL2pOyyfpvEQenhESUxqbxliB2VVU3u1E3NAr++u2FzN+qC9qTssn6b2bp4VfU3tKdlVVN7tRNzQK/vrthczfqgvak7LJ+m9m6eFX1N7SnZVVTe7UTc0Cv767YXM36oL2pOyyfpTp06dOs3Twq2pvaU7KqqU6dOnTrpRNzQK/vrthcdOU5TlOU5tdOnTp7HTlOU5TlObHTlOU5TlObugV/d4ACYevqiZjlRMxyomY5UTMcqJmOVEzHKiZjlRMxyomY5UTMcqJmOVEzHKiZjlRMxyomY5UTMcqJmOVEzHKiZjlRMxyomY5UTMcqJmOVEzHKiZjlRMxyomY5UTMcqJmOVEzHKiZjlRMxyomY5UTMco6hgR69/f8Afz//xAAtEAABAgMGBgMBAQEBAQAAAAABABExUfAhQWGRwfEQIDBxgaFAsdFg4VCQoP/aAAgBAQABPxD/AOMAiABaSSwCLFyms/0j9gjIWRL+lbtW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btW7Vu1btRCzCA6txY6bEGDzh/wG0we1bkybHWZ/g7Jg3i491DAPg/z55SltGxGf8AC3QURMSRd3A4+ac0m99/n8OYo0BjWPzbfuF1CQASSwEeUoIIIfYhdwBgQeSwzgY2DAseQIIIIQxgMFpB25Bk5gzg5gHPIEEETDjKJJAchIAcwHKEEEEPswu4CYPISBwBjYMCx5AggrqQslpB27Hktd4GcHMA55AggnwuHCUgOq1rhJZPp82nx6ntfpGPJTZOSu48lGlyKpLkev8Aoq7j7j65aNLkqcjyQd3IxaY8lfxV3UpMPm02PU9r9Ix5KbJyVXHko0uRRJcj1/0Vdx9x9ctGlyVOR5IO7kYtMeSv4q7qUmHzabHqe1+kY8lNk5KrjyUaXIokuR6/6Ku4+4+uWjS5KnI8kHdyMWmPJX8Vd1KTD5tNj1Pa/SJDxTiacTTiapsnJTcU4mnE04mqNLkVSSOJpxNOJo8v9FCHH3H0nE04mnE04mqNLkqcinE04mnE1aHdyNg6YpxNOJpxNHXzV3UpMPm02PQMgoJBfaO4Rd1q3at2rDlvzQZ0q2YtmLZiFRgAAEAE3at2rdqEgjYOYia2YtmLZixRfvrZFu1btW7UQ8L0xOIzLZi2YtmIWMGxAAItC3at2rdqwfL81s1bMWzFsxCgwQAIAWrdq3at2oTiNg5iJrZi2YtmLFUOvvFbtW7Vu1CJEJJxmWzFsxbMQuIZIAELdq3at2oOidOvQHQpMPm02PQ9v9hGPGtTQh0LqDMclWnyKZPke5+wr+Ppvvlqk+SnzHJ6PIweVfv4iI7r0H10KTD5tNj0Pb/YRjxrU0IdC6gzHJVp8imT5HufsK/j6b75apPkp8xyejyMHlX7+IiO69B9dCkw+bTY9D2/2EQXgmMkxkhNVFCHIKcxkmMkxkqDMclSmjGSYyTGSrk+R7n7CYvBMZJjJAfG++UaS9MZJjJMZKnzHJ6KWMkxkmMlB5V9i8ExkmMkAXHdegProUmHzabHoXb1QBIrbT+rbT+rbT+oEAgZBcWH95TzJCi5Ik2rbT+rbT+rbT+pvklcXMTyF0AkGBii1uAW2n9W2n9W2n9RYZybl3REcByXblyBIrbT+rbT+rbT+oCIIfFsD+8pp1hRcjEm1baf1baf1baf1DoNK4uYnkvOWwMTO1uAW2n9W2n9W2n9VszTMuxiI8hLLoUBuK20/q20/q20/q20/qAYMIdCkw+bTY/w9Jh82mx+OxkUxkUxkUxkUxkUxkUxkUxkUxkUxkUxkUxkfg+F45/C8Lx0qTD5tNj0AlKSYHF7Qq71Vd6qu9VXeqrvVV3qq71Vd6qu9VXeqrvVO1ftHDFALkkg5R2+QwyITi5VDqqh1VQ6qodVUOqqHVVDqqh1VQ6qodVUOqDZOCLjF78BChzPZBjtFVzqq51Vc6qudVXOqrnVVzqq51Vc6qudVXOqJOXDAEXBtRie6Fo5lsUWOxtVY6oRFkhm90XDzd73veVkyDJHEilcfVY6qsdUGroaAkQTb0aTD5tNj0ABAQCL3tWzLZlsy2ZbMtmWzLZlsy2ZbMsCRuVQkQgSAQ2KtmWzLZlsy2ZbMtmWzLZlsy2Zeq1iODxAGeEbMtmWzLZlsyMhhXsLU9d4hRgYKLAStmWzLZkLl1hRie6aMM4Ypsy9AjEKitFRWiIwYcNhA9ooPBxC9l26qhREjWBiIXqitEZcBYeYGNyNbKsiJA/p1syDKBGSruhSYfNpsehWZeIrhkkcAEg2EvEHhiGJEwbDjxFojiGIUM7OAJDEDeOGIGgEK7IEgMRMAeHoFVCReh4wB/kFAHIgQMQeOLERrReKAOWIZiomFGIdwbeSjTHLsdLggKRwI2qrdEQAk2NMEywMxiCGwV5xRjzuT8b3DxZkRf0Hsc5LMAONelRie6r0+CgTHH3+HP8AAcAWdAjAglBGCCWw8qUfZ4UzFXdCkw+bTY9Csy8L+IGvy5b8BMsHDdxGltUC58V6BVQkXoeEI8rBkZWLYjOy2jNTM6JIM+r2Bjwo0xy7BuCZIDrADguiAQG8MgJsdWggRhr2RAAhi7GCEgpsJJCGPCvSoxPdV6fBQJjj7/DtimR4EnwAT4QRghmgIGBF7RwKZiruhSYfNpsehWZeF/EDX5cl+bLkInAJtkMYaq9JegUNkPEIFxG9xZsU7zIAVpfYLogkXEDEcAJRCRE5ox7oywP2InuSWbvdxweq4GQN7zEe4T3EFO7Fn8hj5TDtuEgHTnICaC0RF/HYUIOyJuHlHaQA5YLBTpDisKwgGWB7RG9AjqLOFb5KtJsvDw9pyNndxDnFAYDWdtRQLYYFWt3RVZkSYLKzNnGHtN+YIXEKMT3VsYqKAhMGDwE6MBYMREX8bo+Vzo7BrkHLWu34Ra9isNGLXNpmbpW8DpB2R2wdywTyx5oi5OZ4UzFXdCkw+bTY9Csy8L+IGvy4ydk4BGmbEIgPMzId+0gjgjEACD4or0kIt2MJvL8QDwSRBysW4ILAgg8SAROGXrOb3Mgrdh7UEyCOckngE5CYDAsTfLFGFLIjoC1ll5RJykYE4exRC+YTFFiyBIEDcU8IpAVYEAzOHDgC6yaM5ggKGINoIvdkW5wGQADkt9BE5AiQbMQ9lxTEiFyTIwqJAMkIcAJyMC0SVaq4EK2a9yt7BIBJAsPijnwRAReWAogFYCEAaZ4oMEA5AQQtFZnawY4koCUgoBYQJ4hOIxiwHPgAhLVZIg9mwiaNjx2NhwQWBzwS1xQYsG7Th3NyJ2mICLGcOfCIkHoco0dOwDsJnAWp8YizQTHaAutvPGmYq7oUmHzabHoVmXhfxA1+XHco/ipmQ3ZEAUwIYBFk0xAyRVUGVNtYKnd4MokEgIAkDHDuisMhICL9EF5l5nZ6RjhiC4R8tjF7H6g0S4YfsFH2FJMCICQYkh7BXv8A3XoPoL6f0qzJPrP3w9yu7RFHKydYY6AZWlmToIvcP0Vn+B/3/wBKsyI1u6buEgN0ehxY/wCET9yQCvtFgw4nzMZgAYvIhAbHz8kSHFpe0W9xwFdGg5ZJAHcP5L1340zFXdCkw+bTY9Csy8L+IGvy4xa5WhkzJTix2BixY3YyQtYQzC5r83BC6OCC8RVUeVEAFiDBR2OAubsb3wKeOTDdYqJ1EHIKC4mOSLidxR1EQAseReYEMhHAQiDFoiFpYCWQAADuz4r6z9IVJ/cA4SRPZkaMmgiRLknyvf8AuvQfQTiBYSQfwh3MLmCWFrBOCMgYgO5PngFvB7SRDadi1gIusEfoQSSwB2ACI3xKNxzFViu1hIDtxSigJrgeMw2m+PCvv/pVmRGt3QQJYcQtcEaaDiRie4Q6MpgQIIYjgA71mnLgxhh2Xvrm7NtwyfBbwQLh+ALjTiwOOQ7wRxpmKu6FJh82mx6FZl4X8QNflyX5mAJRCsRaAIFnDbkYLQQ4KIkguSJnYXoVPgmBQMbSpkU4hIYAgAAgEFyQXGLXoBcHBgAhdeR2FvlMGci8EXIG4y9MCJvGF7xJCxmY0EFOGDAAMAhfLktQdxaiHckkkl3xR8G9rY8COCiRJPAHGU5uggJG1AAQSJCD3cJXXGwkhEBVu47ABkDjAgUeYEQABgEPIeZ0IVa4S5JRscR1kAAABBoo6nLoAHsIYMbGvQAXJe+xe+vGwwUzgkSQ9jD3fgJ9TkWiTsDJuJTMVd0KTD5tNj0KzLwv4ga/LnnzfoFVCReh4QjxYDxpQGICx3BmVuf4TCFraLzABB7uFjwYQDj74NTAYQCRAjwJbHhRpjn2VzFxQAuIbD2HIsttYLFoEsBYLoca9KjE91Xp8FAmOPv8O7NRrcA4T8+wcAEzBUAIYhDKNoJijys7g8KZiruhSYfNpsehWZeIBUTN4/TgIYLkPZI4jwoAqmBqFwMqDDs5++HoFVCReh4QjxYUGfCIolES4A8gIem4CiRkdgTXhRpjrbK5uvSoxPdV6fAMjAJeC/GrVsZvUADo7EiI4HliwLguDmFYstsIsLWeB+FUMoqQR4CbQch4UzFXdCkw+bTY9Csy8QNmAjEjY5cAFK1PDxBdrjgShJoSAJYkAYhHRMAQHvJJj2FpTl5BAoleWJLnzwC4IARcQAA5nB6BVQkXoeEI8WAdaMdoi12hAqu9E8gwaOvL0mUBMrAJ2gGHBy8XMJRbIM+FGmOfZXM43mMT41UWiqLRVFoioAWkkAHrjXpUYnuq9PqKZiruhSYfNpsehWZeJCDoGMMGMCMQEBP0jYBqHLajGYcFMJNiOAHoAbAgAwAAHjh6BVQkXoeEI87AQgyd9CZJsAvJQ2XUHu1Z4AAOFGmOnsr0SSMeFelRie6r0+opmKu6FJh82mx6FZl5C6CgzG8t70faAoctKBmVth8EoLNUCThEQfiEHtR+BIPOMIefCEGPL4977CfDcfQKqEi9DwhFNmGlE4Eqr9VV+qq/VFoUA+7VkE7RtYtAe1bMAEvTPYYXAcaNMdPZXokkY8K9KjE91Xp9RTMVd0KTD5tNj0KzLym0MbQgjCpHD0V7Sr7cvoFVCReh4xJED4WwLYFsCAAgAPHLRpjl2NocWOHoqotVUWqqLVVFqqi1REQLCCYH3xr0qMT3Ven1FMxV3QpMPm02PQpMqYyOSYyOSYyOSYyOSYyOSYyOSYyOSYyOSYyOSYyOSYyOSAwDA3KoSISWgTZgraFtC2hbQtoW0LaFtC2hbQtoXpJYjgJLQWOkck6RyTpHJOkck6RyTpHJOkck6RyTpHJOkck6RyQGGe1hRie6ElkE4WKYjJYjJYjJYjJYjJYjJYjJYjJYjJYjJYjJGhnsd1d0KTD5tNj0CAQxAIxWzFsxbMWzFsxbMWzFsxbMWzFsxbM4EAxAKwmSwmSwmSwmSwmSwmSwmSwmSwmSwmSwmSwGXAgYgHuFti2xbYtsW2LbFti2xbYtsW2LaOBAMQD3C2hbQtoW0LaFtC2hbQtoW0LaEAQDLo0mHzabH+HpMPm02P8AD0mHzT+UAEADg1qrfVVvqq31Vb6qt9VW+qrfVVvqq31Vb6qt9VW+qrfVVvqq31Vb6qt9VW+qrfVVvqq31Vb6qt9VW+qrfVVvqq31Vb6qt9VW+qrfVVvqq31Vb6qt9VW+qrfVVvqq31Vb6qt9VW+qrfVVvqq31Vb6qt9VW+qrfVVvqq31Vb6qt9VW+qrfVVvqq31Vb6qt9VW+qrfVVvqq31Vb6qt9VW+qrfVVvqq31Vb6pqlGfAtG1uyu+YcAkxJZA/hxLVYbMYew+aAX6BgWm0wdy9F2h/DN2WCm8jn2sDy+aQCCCAQbij2GwCw4kxF2DSP8LaCq2FjyQ9lhemleB7XnElycT8qP5J3OQVjm+zPaN9wH8UEBw9UO3Zw9q6kEJ/4z1iZYMv4ONDa4MyuCOSARGMCQS8qEmGEVZnbyS1VjYswEdAFK629oEA4IIN4+IDgbDAHCaKFyVwtkHMI7rPwMQixSW23gMR7RGDLoByt9JsERTBUDoqB0VA6KgdFQOioHRUDoqB0VA6KgdFQOioHRUDoqB0VA6KgdFQOioHRUDoqB0VA6KgdFQOioHRUDoqB0VA6KgdFQOioHRUDoqB0VA6KgdFQOioHRUDoqB0VA6KgdFQOioHRUDoqB0VA6KgdFQOioHRUDoqB0VA6KgdFQOioHRUDoqB0VA6KgdFQOioHRAxYOU3IeAY3IDMgr6CrUnuVg8AoZD9pERTJiT35nNhwNmUEGA8hYjuPxAwBOCLx8EBCgQyMTwwA9mMuk5mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc05mc+kNg5vHYHlAHQJ5n4ZslHFekK0kHcewrXBVxZZ75Nv4k03eQqSEwBGW4I57KaCTFeT+oICAABAAQ+R6R0p1cg3mfonR9wA/YQ7ymRQb+WcaKEdg6qOk8D9L3UEjADuGTiY/6riYQBMAT2C9fBo/Z5Q32o32ihT4M5OiuQMP1K9AJA0UycYqLJMCgAAw/4hAIYh1EF3FRw+KiQ+zj6UE7A0RA9j/KLg+7HRG6D3FXI8/6Rucyjdk7uERwP5fiIYF7f5RnvgiGft+yIdMj9RF/j+ogUI/2IjjmkRxzCII5hM/vW8Vu1bsW+lvJbyW8luxbtW8VutAkM0gaGcQLDPIH/AEIGXBfxCBv3H6geXv8AssO8EDRH3QFiPz/EDRHmdELzNLRj/SF/4Q/UDEdmGiDj3wjRRDuiUCN3JOq0U0IY9hQBgDsP/f8A/9k=",
      "CURRENCY":"RUB",
      "SORT":"500",
      "ACTIVE":"Y",
      "CONFIG":[
        {
          "CODE":"SETTING_1",
          "VALUE":"SETTING_1 value"
        },
        {
          "CODE":"SETTING_2",
          "VALUE":"SETTING_2 value"
        }
      ]
    }
    
    • Символьный код обработчика (REST_CODE) обязателен.
    • Логотип с изображением службы доставки можно передать в виде base64-encoded строки.
    • При создании конкретного экземпляра доставки можно указать значение параметров конфигурации (CONFIG).

    В случае успешного создания службы доставки будет получен её числовой идентификатор. Пример ответа:

    {
      "result":{
        "parent":{
          "ID":"622",
          "PARENT_ID":null,
          "NAME":"Uber Taxi",
          "ACTIVE":"Y",
          "DESCRIPTION":"Uber Taxi Description",
          "SORT":"500",
          "CURRENCY":"RUB",
          "LOGOTYPE":"954"
        },
        "profiles":[
          {
            "ID":"688",
            "PARENT_ID":"622",
            "NAME":"Taxi",
            "ACTIVE":"Y",
            "DESCRIPTION":"Taxi Delivery",
            "SORT":"500",
            "CURRENCY":"RUB",
            "LOGOTYPE":null
          },
          {
            "ID":"689",
            "PARENT_ID":"622",
            "NAME":"Cargo",
            "ACTIVE":"Y",
            "DESCRIPTION":"Cargo Delivery",
            "SORT":"500",
            "CURRENCY":"RUB",
            "LOGOTYPE":null
          }
        ]
      },
      "time":{
        "start":1642404734.307061,
        "finish":1642404734.582061,
        "duration":0.27500009536743164,
        "processing":0.08100008964538574,
        "date_start":"2022-01-17T09:32:14+02:00",
        "date_finish":"2022-01-17T09:32:14+02:00"
      }
    }
    

    В ответе возвращается корневая служба доставки, а также все созданные профили. Корневая служба доставки используется как контейнер для служб доставки конкретных профилей (их список указывался при создании обработчика). В процессе настройки (настройка дополнительных услуг, привязка свойств и т.п.) понадобятся идентификаторы профилей корневой службы доставки. Получить список созданных профилей можно либо из ответа после создания корневой службы, либо позже (например, методом sale.delivery.getList):

    {
      "FILTER":{
        "PARENT_ID":622
      }
    }
    

    Пример ответа:

    {
      "result":[
        {
          "ID":"688",
          "PARENT_ID":"687",
          "NAME":"Taxi",
          "ACTIVE":"Y",
          "DESCRIPTION":"Taxi Delivery",
          "SORT":"500",
          "CURRENCY":"RUB",
          "LOGOTYPE":null
        },
        {
          "ID":"689",
          "PARENT_ID":"687",
          "NAME":"Cargo",
          "ACTIVE":"Y",
          "DESCRIPTION":"Cargo Delivery",
          "SORT":"500",
          "CURRENCY":"RUB",
          "LOGOTYPE":null
        }
      ],
      "time":{
        "start":1638544721.243672,
        "finish":1638544721.621672,
        "duration":0.37800002098083496,
        "processing":0.019999980926513672,
        "date_start":"2021-12-03T17:18:41+02:00",
        "date_finish":"2021-12-03T17:18:41+02:00"
      }
    }
    

    Видно, что в процессе создания корневой службы доставки создались две дочерние службы для профилей с идентификаторами 688 и 689. Именно они и будут использоваться для дальнейшей настройки. Корневая служба является лишь контейнером для служб доставок профилей.

    Если в дальнейшем понадобится указать настройки или загрузить кастомный логотип для конкретной службы доставки профиля, необходимо воспользоваться методом sale.delivery.update.

    Описание остальных методов для работы со службами доставок можно посмотреть в разделе Службы доставки.

      Настройка дополнительных услуг службы доставки

    Если тот или иной профиль подразумевает наличие дополнительных услуг, их можно добавить с помощью метода sale.delivery.extra.service.add.

    • Пример добавления дополнительной услуги типа checkbox (да / нет) «Доставка до двери»:
      {
        "DELIVERY_ID":688,
        "ACTIVE":"Y",
        "CODE":"door_delivery",
        "NAME":"Door Delivery",
        "TYPE":"checkbox",
        "PRICE":59.99,
        "SORT":100
      }
      
    • Пример добавления дополнительной услуги типа enum (список) «Тип груза»:
      {
        "DELIVERY_ID":688,
        "ACTIVE":"Y",
        "CODE":"cargo_type",
        "NAME":"Cargo Type",
        "TYPE":"enum",
        "ITEMS":[
          {
            "TITLE":"Small Package(s)",
            "CODE":"small_package",
            "PRICE":129.99
          },
          {
            "TITLE":"Documents",
            "CODE":"documents",
            "PRICE":69.99
          }
        ],
        "SORT":100
      }
      

    При создании дополнительной услуги можно указать символьный код (CODE), чтобы затем идентифицировать выбранные дополнительные услуги при получении запросов к веб-хукам расчета стоимости доставки и создания заказа на доставку. В ответе возвращается числовой идентификатор созданной дополнительной услуги. Его также можно использовать как альтернативу символьному коду для идентификации выбранных дополнительных услуг при запросах к веб-хукам.

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

      Работа со свойствами отгрузки

    Если для расчета стоимости доставки и/или создания заказа на доставку необходимо предоставлять дополнительную информации, то сделать это можно путем прикрепления свойств к службам доставки профилей. Ниже приведён пример прикрепления двух свойств типа «Адрес» (Откуда и Куда) и свойства типа «Строка» для передачи текстового комментария. Для создания свойств отгрузки можно воспользоваться методом sale.shipmentproperty.add.

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

    {
      "fields":{
        "personTypeId":"3",
        "propsGroupId":"11",
        "name":"Address From",
        "active":"Y",
        "sort":"100",
        "description":"",
        "type":"ADDRESS",
        "required":"Y",
        "isAddressFrom":"Y"
      }
    }
    

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

    {
      "fields":{
        "personTypeId":"3",
        "propsGroupId":"11",
        "name":"Address To",
        "active":"Y",
        "sort":"100",
        "description":"",
        "type":"ADDRESS",
        "required":"Y",
        "isAddressTo":"Y"
      }
    }
    

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

    {
      "fields":{
        "personTypeId":"3",
        "propsGroupId":"11",
        "name":"Comments",
        "active":"Y",
        "sort":"100",
        "description":"",
        "type":"STRING",
        "required":"N"
      }
    }
    

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

    Тип плательщика (personTypeId) можно получить методом sale.persontype.list. Свойство нужно создавать отдельно для каждого требуемого типа плательщика (физ. лицо, юр. лицо).

    Группу свойств (propsGroupId) можно получить методом sale.propertygroup.list.

    Каждое созданное свойство теперь нужно привязать к службе доставки профилей. Сделать это можно с помощью вызова метода sale.propertyRelation.add.

    Пример привязки:

    {
      "fields":{
        "propertyId":"437",
        "entityId":"688",
        "entityType":"D"
      }
    }
    

    В данном примере привязывается свойство с идентификатором 437 к службе доставки профилей с идентификатором 688.

    Если планируется создать несколько служб доставки, которые разделяют некий общий набор свойств (к примеру, им всем нужны адреса «Откуда» и «Куда», а также комментарий), то создавать отдельные свойства для каждой службы доставки не требуется. Можно просто привязать новые службы доставки к уже существующим свойствам. Получить список свойств отгрузки можно с помощью метода sale.shipmentproperty.list.

    На этом процесс установки и настройки завершен. Следующим этапом рассмотрим процесс использования установленной и настроенной службы доставки.

    Процесс использования службы доставки в сценариях центра продаж

    Рассмотрим один из полноценных типовых сценариев работы REST служб доставки — работу с доставкой в центре продаж. Стартовой точкой для менеджера тут может являться функционал принятия оплаты («Принять оплату в сделке») или создание дела на доставку.

      Работа со стороны менеджера

    Предварительный расчет стоимости доставки

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

    При расчете доставки отправляется запрос на URL, указанный в значении свойства CALCULATE_URL обработчика службы доставки. В запросе передаются все необходимые для расчета стоимости доставки параметры (свойства отгрузки, вес, стоимость товаров, требуемые дополнительные услуги, значения настроек службы доставки и т.д.). В ответе служба доставки должна сообщить предварительную стоимость доставки в валюте заказа. В случае, если расчет стоимости доставки невозможен, служба доставки должна сообщить текст ошибки, который будет показан менеджеру (см. веб-хук предварительного расчета стоимости доставки).

    Если обработчик службы доставки поддерживает возможность создания заказов на доставку и их дальнейшего отслеживания (значение параметра HAS_CALLBACK_TRACKING_SUPPORT у обработчика выставлено в «Y»), то после создания отгрузки будет создано [dw]дело[/dw][di]

    [/di] на доставку данной отгрузки.

    Создание заказа на доставку

    Посредством дела на доставку менеджер имеет возможность инициировать процесс создания заказа на доставку. При клике на Заказать доставку ([dw]Request Delivery[/dw][di]

    [/di]) отправляется [dw]запрос[/dw][di]

    [/di] на URL, указанный в значении свойства CREATE_DELIVERY_REQUEST обработчика службы доставки. В запросе, помимо информации о самой отгрузке, присутствует информация о контактах отправителя и получателя для связи с ними.

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

    После успешного создания заказа на доставку дело на доставку будет выглядеть следующим образом:

    Отмена заказа на доставку

    Менеджер имеет возможность в любой момент попытаться отменить ранее созданный заказ до тех пор, пока он не был завершен службой доставкой. При клике на Отменить заявку (Cancel Request) отправляется запрос на URL, указанный в значении свойства CANCEL_DELIVERY_REQUEST обработчика службы доставки. В случае, если заказ может быть отменен, служба доставки отменяет его и сообщает об успешной отмене в ответе. Дело на доставку в этом случае возвращается в начальный вид, и менеджер имеет возможность повторно оформить заказ на доставку. В случае, если отмена заказа невозможна, служба доставки должна сообщить причину, по которой отмена невозможна. [dw]Причина[/dw][di]

    [/di] будет выведена менеджеру при попытке отмены заказа (см. веб-хук отмены заказа на доставку).

      Работа со стороны службы доставки

    Обновление заказа на доставку

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

    Сразу после создания заказа на доставку требуется как минимум обновить его статус, чтобы менеджер понимал, что в данный момент происходит с заказом. Например, если поиск исполнителя по заказу занимает некоторое время, то можно сообщить об этом менеджеру путем обновления [dw]статуса заказа[/dw][di]

    [/di] методом sale.delivery.request.update:

    {
      "DELIVERY_ID":723,
      "REQUEST_ID":"4757aca4931a4f029f49c0db4374d13d",
      "STATUS":{
        "TEXT":"Searching performer",
        "SEMANTIC":"process"
      }
    }
    

    Предположим, что исполнитель заказа найден, и теперь требуется зафиксировать в заказе [dw]новый статус и информацию об исполнителе[/dw][di]

    [/di]. Для этого можно воспользоваться функционалом набираемых свойств для заказа на доставку (метод sale.delivery.request.update):

    {
      "DELIVERY_ID":723,
      "REQUEST_ID":"4757aca4931a4f029f49c0db4374d13d",
      "STATUS":{
        "TEXT":"Performer found",
        "SEMANTIC":"process"
      },
      "PROPERTIES":[
        {
          "NAME":"Car",
          "VALUE":"Gray Skoda Octavia, a777zn"
        },
        {
          "NAME":"Driver",
          "VALUE":"John Smith"
        },
        {
          "NAME":"Phone Number",
          "VALUE":"+79097996161",
          "TAGS":[
            "phone"
          ]
        }
      ]
    }
    

    Набор свойств может быть произвольным. Если требуется, чтобы значение свойства было обработано особенным образом, то можно отметить его тегом. В примере выше значение номера телефона исполнителя отмечено тегом «phone». Это позволяет вывести его как ссылку и открывать либо через IP телефонию (если она настроена у клиента), либо стандартными средствами браузера.

    Допустим, служба доставки передала заказ исполнителю (отгрузила заказ), и теперь нужно [dw]обновить его статус[/dw][di]

    [/di] методом sale.delivery.request.update:

    {
      "DELIVERY_ID":723,
      "REQUEST_ID":"4757aca4931a4f029f49c0db4374d13d",
      "STATUS":{
        "TEXT":"Parcel on its way",
        "SEMANTIC":"process"
      }
    }
    

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

    • добавление новых свойств и значений (OVERWRITE_PROPERTIES = N, это режим по умолчанию)
    • перезапись всего набора значений свойств (OVERWRITE_PROPERTIES = Y)

    Когда заказ на доставку завершен, то необходимо его финализировать методом sale.delivery.request.update:

    {
      "DELIVERY_ID":723,
      "REQUEST_ID":"4757aca4931a4f029f49c0db4374d13d",
      "FINALIZE":"Y"
    }
    

    Дело на доставку в этом случае переходит в [dw]статус выполненного[/dw][di]

    [/di], и его отмена со стороны менеджера уже невозможна.

    Отправка сообщений по заказу на доставку

    Предположим, что в процессе выполнения заказа на доставку необходимо оповестить ответственного за доставку менеджера или грузополучателя о том или ином событии. Допустим, требуется сообщить клиенту о факте того, что посылка передана в службу доставки, и о времени доставки (метод sale.delivery.request.sendmessage):

    {
      "DELIVERY_ID":723,
      "REQUEST_ID":"4757aca4931a4f029f49c0db4374d13d",
      "ADDRESSEE":"RECIPIENT",
      "MESSAGE":{
        "SUBJECT":"We will soon deliver your parcel to you!",
        "BODY":"Your order has been dispatched and will be delivered to you in 23 minutes. Thank you!",
        "STATUS":{
          "MESSAGE":"Dispatched",
          "SEMANTIC":"success"
        }
      }
    }
    

    [dw]Оповещение[/dw][di]

    [/di] будет отправлено клиенту по одному из каналов связи, настроенных в CRM. Это может быть SMS-оповещение или сообщение в один из мессенджеров (например, WhatsApp). Для отправки сообщения менеджеру необходимо установить адресата сообщения (ADDRESSEE) в значение «MANAGER».

    Отправка сообщений по заказу на доставку

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

    Примеры

    Пример создания собственной службы доставки

    Чтобы добавить собственную службу доставки, следует создать файл handler.php и в нем написать класс <Ваше_название>Handler для вашей службы доставки. Файл должен быть размещен в отдельной директории <ваше_название> в /local/php_interface/include/sale_delivery/ или в том разделе, который задан в настройках модуля Интернет-магазин с помощью параметра Путь к собственным обработчикам расширенных систем доставки (по умолчанию используется /bitrix/php_interface/include/sale_delivery/). Из данного файла система подключает вашу службу доставки автоматически.

    Например, добавим службу доставки, зависящую от веса.

    Для этого файл handler.php расположим в директории /bitrix/php_interface/include/sale_delivery/custom/ и в нем составим описание для класса соответственно CustomHandler. Класс наследуем от базового класса служб доставки:

    <?
    namespace SaleHandlersDelivery;
    
    use BitrixSaleDeliveryCalculationResult;
    use BitrixSaleDeliveryServicesBase;
    
    class CustomHandler extends Base
    {
    	public static function getClassTitle()
    		{
    			return 'Доставка по весу';
    		}
    		
    	public static function getClassDescription()
    		{
    			return 'Доставка, стоимость которой зависит только от веса отправления';
    		}
    		
    	protected function calculateConcrete(BitrixSaleShipment $shipment)
    		{
    			$result = new CalculationResult();
    			$price = floatval($this->config["MAIN"]["PRICE"]);
    			$weight = floatval($shipment->getWeight()) / 1000;
    		
    			$result->setDeliveryPrice(roundEx($price * $weight, 2));
    			$result->setPeriodDescription('1 день');
    		
    			return $result;
    		}
    		
    	protected function getConfigStructure()
    		{
    			return array(
    				"MAIN" => array(
    					"TITLE" => 'Настройка обработчика',
    					"DESCRIPTION" => 'Настройка обработчика',"ITEMS" => array(
    						"PRICE" => array(
    									"TYPE" => "NUMBER",
    									"MIN" => 0,
    									"NAME" => 'Стоимость доставки за грамм'
    						)
    					)
    				)
    			);
    		}
    		
    	public function isCalculatePriceImmediately()
    		{
    			return true;
    		}
    		
    	public static function whetherAdminExtraServicesShow()
    		{
    			return true;
    		}
    }
    ?>
    

    В классе определяем необходимые нам методы. Так, методы getClassTitle и getClassDescription содержат название и описание службы доставки. Метод calculateConcrete вызывается при расчете стоимости доставки, принимая в качестве параметра отгрузку. Метод getConfigStructure описывает параметры, которые необходимо спросить в интерфейсе у администратора.

    Примечание: если класс с вашей службой доставки имеет произвольное название, лежит в произвольном файле, то для подключения вашей службы в файл init.php необходимо добавить следующий код:

    function addCustomDeliveryServices()
    {
        return new BitrixMainEventResult(
            BitrixMainEventResult::SUCCESS,
            array(
                'SaleHandlersDeliveryCustomHandler' => '/ваш_путь_до_обработчика/имя_обработчика.php'
            )
        );
    }
    
    BitrixMainEventManager::getInstance()->addEventHandler('sale', 'onSaleDeliveryHandlersClassNamesBuildList', 'addCustomDeliveryServices');
    

    Как добавить логотип сразу при создании службы доставки?

    Дополнительные рекомендации

    • Обязательно учитывайте кодировку сайта. Если производится обмен данными со службой доставки, необходимо при этом правильно менять кодировку данных при отправлении и получении данных. В этом вам поможет метод BitrixMainTextEncoding::convertEncoding().
    • При разработке в качестве примеров используйте следующие службы доставки:
      • SaleHandlersDeliverySimpleHandler (/bitrix/modules/sale/handlers/delivery/simple/handler.php) — простейший пример обработчика.
      • SaleHandlersDeliverySpsrHandler (/bitrix/modules/sale/handlers/delivery/spsr/handler.php) — вариант посложнее с использованием всех возможностей ядра D7.
    • Если вам необходимо наладить механизм автоматического отслеживания идентификаторов отправления (трэкинг-номеров), то используйте как пример службу доставки СПСР: SaleHandlersDeliverySpsrTracking.
    • Для запросов к сервису службы доставки рекомендуется использовать встроенный класс BitrixMainWebHttpClient вместо сторонних расширений, например curl. Оптимальный формат обмена — json, так как возможно использовать встроенный класс BitrixMainWebJson.
    • При обмене информацией с сервисами служб доставок зачастую необходимо передавать идентификаторы местоположений. Сопоставление идентификаторов местоположений интернет-магазина с идентификаторами местоположений служб доставок — задача нетривиальная. Как пример можно использовать SaleHandlersDeliverySpsrLocation::mapStepless();
    • Чтобы не порождать лишних запросов к службе доставки и не замедлять работу сайта, желательно по возможности кешировать полученную от сервисов служб доставок информацию. Однако, делать это надо аккуратно во избежание побочных эффектов. Как пример: SaleHandlersDeliverySpsrCache.
    • В случае возникновения ошибок и для отладки желательно иметь возможность записывать события, связанные с получением информации от служб доставок, в системный журнал. Для этого можно воспользоваться классом CEventLog.

    Пример создания заказа через API

    Рассмотрим пример создания кода, с помощью которого будет добавлен заказ в систему. Допустим, у нас есть некие товары, в данном случае заданные массивом:

    $products = array(
    	array('PRODUCT_ID' => 1811, 'NAME' => 'PRODUCT_PROVIDER_CLASS' => 'BitrixCatalogProductCatalogProvider', 'Товар 1', 'PRICE' => 500, 'CURRENCY' => 'RUB', 'QUANTITY' => 5)
    			);
    

    В первую очередь создадим объект корзины при помощи метода create, в параметрах которого указываем идентификатор сайта, поскольку корзина привязывается к сайту. Затем наполняем корзину, пробегая по массиву товаров. Элемент корзины создается с помощью createItem:

    $basket = BitrixSaleBasket::create(SITE_ID);
    
    foreach ($products as $product)
    	{
    		$item = $basket->createItem("catalog", $product["PRODUCT_ID"]);
    		unset($product["PRODUCT_ID"]);
    		$item->setFields($product);
    	}
    

    Теперь необходимо создать сам заказ. Опять используем метод create, но уже для класса заказа. В параметрах передаем идентификатор сайта и код пользователя (для простоты создадим заказ для администратора). После этого на объекте заказа можем вызывать необходимые методы: например, установить тип плательщика с помощью метода setPersonTypeId. Привязка корзины к заказу осуществляется с помощью метода setBasket:

    $order = BitrixSaleOrder::create(SITE_ID, 1);
    $order->setPersonTypeId(1);
    $order->setBasket($basket);
    

    При вызове метода setBasket происходит не только привязка корзины к данному заказу, но еще и пересчет заказа. Заказ актуализируется в соответствии с теми параметрами, которые в него передаются. Таким образом, после вызова данного метода у заказа появляется стоимость заказа, которая в данном случае численно равна стоимости корзины.

    Дальше нужно создать отгрузки. Получаем коллекцию отгрузок с помощью метода getShipmentCollection. Поскольку наш заказ новый, то создается новая коллекция отгрузок (если бы это был существующий заказ, то коллекция отгрузок была взята бы из базы данных). На этой коллекции мы можем создавать конкретные отгрузки с помощью createItem, в который передается объект службы доставки:

    $shipmentCollection = $order->getShipmentCollection();
    $shipment = $shipmentCollection->createItem(
    		BitrixSaleDeliveryServicesManager::getObjectById(1)
    	);
    

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

    $shipmentItemCollection = $shipment->getShipmentItemCollection();
    
    foreach ($basket as $basketItem)
    	{
    		$item = $shipmentItemCollection->createItem($basketItem);
    		$item->setQuantity($basketItem->getQuantity());
    	}
    

    Примечание: несмотря на то, что мы создаем одну отгрузку, на самом деле будет создано 2 отгрузки. Одна из отгрузок особая, она не видна в административном интерфейсе и является системной. Данная особенность связана с тем, что архитектурно товары не могут оставаться «подвешенными» и обязательно должны быть распределены по каким-то отгрузкам. Если товар не распределен по созданным вами отгрузкам, то он будет лежать в системной отгрузке до тех пор, пока вы его не перераспределите в некоторую вашу отгрузку.

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

    Теперь создадим оплату. С помощью метода getPaymentCollection получаем коллекцию оплат, а с помощью метода createItem создаем конкретную оплату:

    $paymentCollection = $order->getPaymentCollection();
    $payment = $paymentCollection->createItem(
    		BitrixSalePaySystemManager::getObjectById(1)
    	);
    

    Чтобы настроить свойства оплаты, используем метод setField. В нашем примере мы выставим счет на полную стоимость заказа:

    $payment->setField("SUM", $order->getPrice());
    $payment->setField("CURRENCY", $order->getCurrency());
    

    Для системы заказов характерна следующая особенность: если мы производим манипуляции с некоторым объектом заказа, то происходит перестройка всех других связанных с ним объектов. Так, мы ничего не делали с суммой заказа, но мы можем обратиться к ней, чтобы выставить платеж именно на сумму заказа, потому что система посчитала это самостоятельно. Система учла и стоимость корзины, и стоимость доставки, и скидки, и т.д.

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

    $result = $order->save();
    	if (!$result->isSuccess())
    		{
    			//$result->getErrors();
    		}
    

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

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

    Полный код примера создания заказа

    Пример изменения заказа через API

    Рассмотрим небольшой пример изменения заказа через API. Допустим, у нас есть заказ с идентификатором 38:

    Составим код, с помощью которого будет добавлен комментарий и изменен флаг разрешения отгрузки в нашем заказе. Для этого нам достаточно с помощью метода load поднять заказ из базы данных по его идентификатору. Это действие сразу позволит нам манипулировать со всеми свойствами заказа. Так, с помощью метода setField добавим необходимый нам комментарий. Затем с помощью метода getShipmentCollection получим коллекцию отгрузок и для всех отгрузок, кроме системной, пометим флаг разрешения отгрузки (метод allowDelivery). После чего сохраним заказ с помощью метода save. Данный метод сохранит и заказ, и все связанные объекты (сохраняет именно те поля, которые были изменены):

    BitrixMainLoader::includeModule('sale');
    
    $order = BitrixSaleOrder::load(38);
    $order->setField("USER_DESCRIPTION", "Доставить к подъезду");
    
    $shipmentCollection = $order->getShipmentCollection();
    
    /** @var SaleShipment $shipment */
    
    foreach ($shipmentCollection as $shipment)
    {
    	if (!$shipment->isSystem())
    		$shipment->allowDelivery();
    }
    
    $result = $order->save();
    if (!$result->isSuccess())
    {
    	//$result->getErrors();
    }
    

    После выполнения вышеуказанного кода через командную php-строку наш заказ будет изменен соответствующим образом:

    Пример разделения оплаты на 2 части

    В уроке мы рассмотрим, как средствами API продукта разделить на части оплату в уже существующем заказе. Допустим, у нас есть заказ с идентификатором 21, а его документ оплаты имеет идентификатор 22:

    Составим код, с помощью которого оплата будет разбита на 2 части.

    • В первую очередь загружаем заказ из базы и получаем коллекцию оплат:

      BitrixMainLoader::includeModule('sale');
      $order = BitrixSaleOrder::load(21);
      $paymentCollection = $order->getPaymentCollection();
      
    • Затем из коллекции выбираем оплату, которую необходимо изменить (напомним, что у нас оплата имеет идентификатор 22):

      $payment = $paymentCollection->getItemById(22);
      
    • Сумму данной оплаты заменяем, например, на величину стоимости одной из позиций заказа (у нас это 1979 рублей):

      $payment->setField('SUM', 1979);
      
    • Добавляем новую оплату. Минимальный набор полей при создании оплаты — это платежная система (идентификатор и название платежной системы) и сумма. Для примера будем использовать платежную систему [dw]Сбербанк[/dw][di][/di] с идентификатором 7.

      Создать объект оплаты можно двумя способами:

      • передать объект платежной системы в параметре, из которого возьмется вся необходимая информация:

        $service = BitrixSalePaySystemManager::getObjectById(7);
        $newPayment = $paymentCollection->createItem($service);
        
      • либо установить необходимую информацию по платежной системе явно:

        $newPayment = $paymentCollection->createItem();
        $newPayment->setField('PAY_SYSTEM_ID',7);
        $newPayment->setField('PAY_SYSTEM_NAME', "Сбербанк");
        
    • Устанавливаем сумму новой оплаты:

      $newPayment->setField('SUM', $order->getPrice() - 1979);
      
    • Сохраняем заказ:

      $result = $order->save();
      

      Метод save() сохранит и заказ, и все связанные объекты.

    После выполнения вышеуказанного кода через командную php-строку наш заказ будет иметь 2 оплаты:

    Примечание: разделение оплаты на этапе создания заказа делается аналогично.

    Видео

    Смотреть

    Работа с модулем Push & Pull

    Push & Pull

    Детально о работе и настройке модуля смотрите в [ds]соответствующей главе[/ds][di]Push-уведомления – это небольшие всплывающие окна, которые появляются на экране мобильного телефона или обычного компьютера и сообщают о важных событиях и обновлениях.

    Подробнее …[/di] курса Администратор.Модули.

    Режимы работы

    Модуль Push & Pull работает в двух режимах:

    • постоянное подключение к специальному серверу Сервер очередей;
    • в режиме опроса сервера (60-20-10).

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

    Второй режим используется когда по каким-то причинам невозможно использовать первый. В таком случае модуль будет каждые 60 секунд обращаться на сервер и проверять, есть ли данные. Если есть, то следующий хит будет через 10 секунд. Если данных больше нет, то хит будет сначала через 20 секунд, а потом через каждые 60 секунд. Тем самым будет создан эффект интерактивности.

    Какой способ выберет клиент на своем сайте (сервер очередей или опрос сервера), работа с модулем останется одинаковой (кроме работы с общим каналом).

    Само API делится на PHP и JS часть, ниже описаны принципы работы с ним.

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

    PHP и JS

    Для начала работы вам необходимо подключить модуль:

    if (!CModule::IncludeModule('pull'))
       return false;

    и зарегистрировать зависимость на модуль PULL. Регистрируем обработчик зависимости:

    RegisterModuleDependences("pull", "OnGetDependentModule", "your_module", "CYourModulePullSchema", "OnGetDependentModule" );

    Далее создайте свой класс. Код класса

    class CYourModulePullSchema
    {
        public static function OnGetDependentModule()
        {
            return Array(
                'MODULE_ID' => "your_module",
                'USE' => Array("PUBLIC_SECTION")
         );
         }
    }

    Если ваш код работает в публичной части, необходимо указать:

    'USE' => Array("PUBLIC_SECTION")

    Если кроме публички, нужна еще и админка, то укажите:

    'USE' => Array("PUBLIC_SECTION", "ADMIN_SECTION")

    Перед использованием своего кода необходимо провести проверку подключения методами класса CPullOptions.

    Далее в работе можно использовать классы АПИ:

    JS методы

    Пример

    Код для работы с выше описанными методами PHP

    BX.addCustomEvent("onPullEvent", function(module_id,command,params) {
        if (module_id == "test" && command == 'check')
        {
            console.log('Work!');
        }
    });

    Мы подписываемся на событие получение команд (onPullEvent), в функции получаем module_id, command, params которые мы указали при отправке команды из PHP, обрабатываем свои команды с учетом вашей логики.

    Пример компонента, который работает с самым сложным методом подписок BX.PULL.extendWatch.

    Оптимизация количества запросов к серверу

    Если проект находится на отдельном сервере, то необходимо использовать настройку путей в модуле Push & Pull, позволяющая современным браузерам ходить напрямую на сервер очередей.

    До появления этой опции в версии 15.5.1 приходилось делать проксирование запросов. (Запрос отправлялся на сервер сайта, так как старые браузеры не поддерживают прямые запросы AJAX на другие домены, и уже оттуда запрос перенаправлялся на сервер очередей через внутренние правила). Это создавало нагрузку и лишний трафик.

    Теперь все новые браузеры обращаются напрямую на сервера. Но из-за особенности работы нашей реализации JS с сервером, им приходится делать дополнительно на каждое подключение OPTIONS запрос. Чтобы избавится от него, необходимо изменить конфигурационные файлы сервера.

    Откройте файл bx/conf/im_subscrider.conf, в описании локейшена location ^~ /bitrix/sub { проверьте есть ли у вас такое условие:

    if ($arg_time) {
        push_stream_last_received_message_time "$arg_time";
    }

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

    push_stream_subscriber            long-polling;
     push_stream_allowed_origins "*";
     push_stream_channels_path        $arg_CHANNEL_ID;
     push_stream_last_received_message_tag    $arg_tag;
     if ($arg_time) {
        push_stream_last_received_message_time "$arg_time";
     }

    После этого воспользуйтесь методом COption::SetOptionString:

    COption::SetOptionString("pull", "nginx_headers", "N");

    После этого, при подключении, все пользователи будут генерировать к серверу на один запрос меньше (это произойдет не сразу, а по мере обновления JS кода у клиента, перезапуска страницы).

    Push & Pull для гостей

    Запустить Push & Pull для гостей можно, но необходимо реализовать свою логику фиксации сессии за гостем. Для этого необходим уникальный числовой идентификатор: без него невозможно выдать гостю один и тот же адрес канала и невозможно отправлять ему персонализированные команды. С версии Push & Pull 15.5.1 такая возможность появилась.

    Для реализации функционала разработчику необходимо реализовать методы определения гостя, присвоение ему внутреннего числового идентификатора. Допустим этот идентификатор у вас определился как 1. Чтобы не путать авторизованных и не авторизованных пользователей, передавать этот идентификатор в модуль Push & Pull его нужно со знаком минус:

    $guestId = -1;
    
    CModule::IncludeModule('pull');
    CPullStack::AddByUser($guestId, Array(
       'module_id' => 'test',
       'command' => 'check',
       'params' => Array(),
    )); 
    

    Метод который определяет идентификатор, нужно реализовать и выполнить в прологе, до момента инициализации Push & Pull. Для этого необходимо будет зарегистрировать зависимость:

    RegisterModuleDependences("main", "OnProlog", "main", "", "", 2, "local/scripts/pull_hit.php");

    Внутри указанного файла должна быть ваша логика определения и указание константы PULL_USER_ID которая будет использоваться для корректного формирования канала гостю. Для каждого гостя должен быть свой идентификатор!

    $guestId = -1; // эту цифру должна вернуть ваша функция, определения идентификатора для гостя
    define('PULL_USER_ID', $guestId);

    После этого ваши гости могут получать Push & Pull команды наравне с авторизованными.

    Для удобства отладки, вы можете воспользоваться следующими JS командами:

    JS Команды
    BX.PULL.getDebugInfo(); Данная команда отображает состояние подключение к серверам P&P;
    BX.PULL.capturePullEvent(); Данная команда логирует все команды приходящие для данного пользователя;

    Подписка на события модуля

    Подписка

    До версий pull 18.5.7 (для десктопов) и mobile 18.5.10 (для мобильных устройств) для разработчиков существовали три проблемы при подписке на события модуля Push&Pull:

    1. Невозможно было одним кодом реализовать подписку на обоих типах устройств.
    2. В силу первого пункта приходилось обрабатывать много разных команд, получая «лапшу» из if’ов, что, помимо сложной ориентации в коде, выдавало неинформативный результат в отладчике в случае возникновения ошибки (вывод стек-трейса).
    3. Если требовалось обработать всего одну команду, то использование конструкции с одним if или целом классом было слишком многословным.

    Начиная с указанных версий подключение к событиям модуля Push&Pull реализовано с помощью метода BX.PULL.subscribe. Как работать с этим методом, рассказано ниже.

      Подключение библиотеки

    В рамках компонентов которые используются только в браузере, нужно обязательно проверить наличие модуля Push & Pull, а так же подключить библиотеку pull.client:

    • Укажите CoreJS зависимость pull.client в описании вашего расширения или вызовите BitrixMainUIExtension::load('pull.client');
    • В рамках компонентов которые используются в мобильном приложении, нужно подключить необходимые зависимости для вашего контекста.

    • Для веб-страницы укажите CoreJS зависимость mobile.pull.client в описании расширения или вызовите BitrixMainUIExtension::load('mobile.pull.client');
    • Для JaNative component, укажите зависимость pull/client/events в файле deps.php.
    • Для Offline WebComponent укажите зависимость pull/client/events в файле config.php в разделе deps.

    В рамках мобильного расширения вы можете использовать методы:
    BX.PULL.subscribe(…),
    BX.PULL.extendWatch(…),
    BX.PULL.clearWatch(…),
    BX.PULL.capturePullEvent(),
    BX.PULL.getDebugInfo().

    Для подписки есть три формата, вы можете выбрать для себя подходящий в зависимости от ваших задач.

      Одна команда

    Подписка на одну команду:

    BX.PULL.subscribe({
    	type: BX.PullClient.SubscriptionType.Server,
    	moduleId: 'im',
    	command: 'messageChat',
    	callback: function (params, extra, command) {
    		console.warn('Receive message:', params.message.text)
    	}.bind(this)
    });

    Где:

    type — тип подписки (Server, Client, Online) — можно не указывать, по умолчанию будет тип Server,
    moduleId — модуль отправивший команду
    command — команда на которую осуществляется подписка
    callback — функция обработчик.

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

    • params — объект, параметры команды,
    • extra — объект, дополнительные данные такие как версия модуля, имя и время сервера, время с момента отправки команды,
    • command — строка, название команды.

    Результатом выполнения метода будет функция c помощью которой вы сможете отписаться от указанной команды в будущем:

    let unsubscibe = BX.PULL.subscribe({...}); // подписка
    unsubscibe(); // отписка

      Несколько команд

    Подписка на множество команд с помощью функции маршрутизатора:

    BX.PULL.subscribe({
    	type: BX.PullClient.SubscriptionType.Server,
    	moduleId: 'im',
    	callback: function (data) {
    		if (data.command == 'messageAdd')
    		{
    			this.doSomething();
    		}
    	}.bind(this)
    });

    Где:
    type — тип подписки (Server, Client, Online) — можно не указывать, по умолчанию будет тип Server,
    moduleId — модуль отправивший команду,
    callback — функция обработчик для всех поступающих команд.

    В параметре data в указанной callback функции будет предоставлен следующий объект:

    {
    	command: '...', // название команды
    	params: {...}, // параметры команды
    	extra: {...} // дополнительные данные такие как версия модуля, имя и время сервера, время с момента отправки команды
    }

    Результатом выполнения метода будет функция c помощью которой вы сможете отписаться от команд модуля в будущем.

    let unsubscibe = BX.PULL.subscribe({...}); // подписка
    unsubscibe(); // отписка

      Класс маршрутизации

    Подписка с помощью класса маршрутизации:

    BX.PULL.subscribe(new CommandHandler(options));

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

    Сам класс маршрутизации выглядит так (обратите внимание: класс написан на ES6, но возможно и применение класса в формате ES5)

    class CommandHandler
    {
    	constructor(options = {})
    	{
    	}
    
    	getModuleId()
    	{
    		return 'im';
    	}
    
    	getSubscriptionType()
    	{
    		return BX.PullClient.SubscriptionType.Server;
    	}
    	
    	getMap()
    	{
    		return {
    			message: this.handleMessage.bind(this), 
    			messageChat: this.handleMessageChat.bind(this),
    			startCall: this.handleStartCall.bind(this),
    		}; 
    	}
    
    	handleMessage(params, extra, command)
    	{
    		console.log('exec command - message', params);
    	}
    
    	handleMessageChat(params, extra, command)
    	{
    		console.log('exec command - messageChat', params);
    	}
    
    	handleStartCall(params, extra, command)
    	{
    		console.log('exec command - startCall', params);
    	}
    }

    Метод getModuleId() должен возвращать идентификатор модуля, команды которого будет обрабатывать данный класс. (Обязательный метод).

    Метод getSubscriptionType() должен возвращать тип подписки (Server, Client, Online). (Не обязательный метод, если не указать, будет тип Server)

    Метод getMap() должен возвращать карту соответствия команды поступающей от сервера и метода который будет его обрабатывать.

      Форматы объекта

    Возможные форматы объекта возвращаемого функцией getMap().

    В формате ссылки на функцию, рекомендованный вариант, т.к. в IDE можно будет быстро перейти к функции просто кликнув на неё:

    {
    	startCall: this.handleStartCall.bind(this),
    }

    В формате строки:

    {
    	startCall: 'handleStartCall',
    }

    В формате callback функции:

    {
    	startCall: function(params, extra, command) {
    		console.log('exec command - startCall', params);
    	}.bind(this),
    }

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

    • params — объект, параметры команды
    • extra — объект, дополнительные данные такие как версия модуля, имя и время сервера, время с момента отправки команды
    • command — строка, название команды

    Упрощенный вариант описания

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

    class CommandHandler
    {
    	constructor(options = {})
    	{
    	}
    
    	getModuleId()
    	{
    		return 'im';
    	}
    
    	handleMessage(params, extra, command)
    	{
    		console.log('exec command - message', params);
    	}
    
    	handleMessageChat(params, extra, command)
    	{
    		console.log('exec command - messageChat', params);
    	}
    
    	handleStartCall(params, extra, command)
    	{
    		console.log('exec command - startCall', params);
    	}
    }

    Результатом выполнения метода будет функция, c помощью которой вы сможете отписаться от команд модуля в будущем.

    let unsubscibe = BX.PULL.subscribe({...}); // подписка
    unsubscibe(); // отписка

    Гибридный вариант описания

    Вы можете использовать одновременно getMap() и методы по стандартам именования CommandHandler. Такой вариант подойдет, если вы хотите сделать alias к устаревшему формату команд или если вы отправляете команды в формате который невозможно описать в названии метода.

    class CommandHandler
    {
    	constructor(options = {})
    	{
    	}
    
    	getModuleId()
    	{
    		return 'im';
    	}
    	
    	getMap()
    	{
    		return {
    			'Application::send': this.handleApplicationSend.bind(this),
    			messageChatAdd: this.handleMessageChat.bind(this) 
    		}; 
    	}
    
    	handleMessage(params, extra, command)
    	{
    		console.log('exec command - message', params);
    	}
    
    	handleMessageChat(params, extra, command)
    	{
    		console.log('exec command - messageChat', params);
    	}
    
    	handleStartCall(params, extra, command)
    	{
    		console.log('exec command - startCall', params);
    	}
    	
    	handleApplicationSend(params, extra, command)
    	{
    		console.log('exec command - applicationSend', params);
    	}
    }

    Внимание!. Если команда описана в getMap() и у вас есть метод для этой команды названный по стандартам именования CommandHandler, то приоритет вызова будет отдан getMap().

    Результатом выполнения метода будет функция c помощью которой вы сможете отписаться от команд модуля в будущем.

    let unsubscibe = BX.PULL.subscribe({...}); // подписка
    unsubscibe(); // отписка

    Агенты и их использование

    Агенты

    Агенты — технология, позволяющая запускать произвольные PHP функции (агенты) с заданной периодичностью. Технически агент — это запись в специальной таблице:

    • какой код надо выполнить,
    • когда выполнить,
    • с каким периодом выполнять,
    • каким способом назначать время следующего запуска агента (строго периодический или нестрого периодический агент).

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

    Как выполнялась проверка наличия агента до версии 20.5.0 Главного модуля.

    Примечание: Временная точность запуска агентов напрямую зависит от равномерности и плотности посещаемости сайта. Реальное время запуска агента обычно чуть-чуть позже, чем время, на которое назначен агент (при равномерной посещаемости). Момент запуска — это когда кто-то зашел на страницу сайта.
    Если вам необходимо организовать запуск каких-либо PHP функций в абсолютно точно заданное время, то необходимо воспользоваться стандартной утилитой cron, предоставляемой большинством хостингов.

    Кроме этого, не рекомендуется вешать на агенты ресурсоёмкие операции, для них существует фоновый запуск по cron‘у.

    Для того чтобы агент выполнился в заданное время, его необходимо зарегистрировать в системе при помощи метода CAgent::AddAgent. Удалить регистрацию агента можно с помощью функции CAgent::RemoveAgent.

    Если функция-агент принадлежит модулю, то перед ее выполнением этот модуль будет автоматически подключаться, а именно будет подключаться файл /bitrix/modules/ID модуля/include.php. В этом случае необходимо убедиться, что функция-агент будет доступна после подключения этого файла.

    Если функция-агент не принадлежит ни одному из модулей, то ее необходимо разместить в файле /bitrix/php_interface/init.php. Этот файл автоматически подключается в прологе.

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

    Список используемых в системе агентов приводится на странице Список агентов (Настройки > Настройки продукта > Агенты).

    Периодические и непериодические

    Исторически агенты называются «периодические» и «непериодические», хотя правильнее было бы сказать: «повторяющиеся» и «неповторяющиеся». Сложившиеся названия решили не менять.

    Так же решили не менять и интерфейс настройки агента в административном разделе сайта:

    Тип агента зависит от программиста, который написал код агента. Программист может сделать агента, который повторится бесконечное число раз. Или только 2-3 раза в зависимости от условий. Пример функций агентов повторяющихся бесконечное число раз:

    // пример функций агентов
    
    function TestAgentPeriod()
    {
       AddMessage2Log( "Периодический BX_CRONTAB:".BX_CRONTAB." BX_CRONTAB_SUPPORT:".BX_CRONTAB_SUPPORT );   
       return "TestAgentPeriod();";
    }
    
    
    function TestAgentNotPeriod()
    {
       AddMessage2Log( "Непериодический BX_CRONTAB:".BX_CRONTAB." BX_CRONTAB_SUPPORT:".BX_CRONTAB_SUPPORT );
       return "TestAgentNotPeriod();";
    }

    Тип агента определяется по способу вычисления времени следующего запуска агента:

    • Периодические, назначенное время следующего запуска вычисляется так:
      Старое назначенное время агента + интервал

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

    • Непериодические, назначенное время следующего запуска вычисляется так:
      Время завершения последнего запуска агента (выполнение return агента) + интервал

      Например, агент пересчета рейтингов запускается раз в час. Но если за сутки не было ни одного посетителя, то логичнее выполнить агент один раз и сдвинуть назначенное время следующего запуска на час вперед после запуска. В случае строго периодического агента, этот код выполнился бы все 24 раза в сутки.

      Абсолютное большинство агентов непериодические.

    Ограничения

    При использовании технологии учтите, что:

    • Переменная USER в агентах отсутствует. Точнее она может быть создана на сервере хостинга, но нет никакой гарантии, что это будет объект класса CUser.
      При необходимости рекомендуется создавать в агенте объект $USER, что-то с ним сделать и уничтожить.
    • В агентах нельзя проводить авторизацию методом Authorize.
    • Отсутствует константа SITE_ID, так как агент может выполниться и на странице административного раздела.
    • На многоязычных сайтах нельзя заранее узнать какой будет язык.
    • Агенты выполняются в однопоточном режиме с блокировкой на MySQL на 10 минут. Новый вызов агента возможен только после того, как отработает предыдущий вызов. Блокировка БД может потеряться если закроется соединение с ней. 10 минут ожидания это — дополнительная защита от повторного запуска, агентов которые не корректно отработали.

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

    Список ссылок по теме:

    • Примеры использования агентов
    • Выполнение всех агентов на cron. Чтобы почта была на хитах (сообщение в блоге)

    Примеры агентов

      Пример создания агента

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

    Агента создаем на странице Настройки > Настройки продукта > Агенты по команде Добавить агента на контекстной панели:

    О параметрах, значение которых может быть неясно из названия:

    • дата последнего запуска — выводится время последнего запуска (при редактировании агента);
    • дата и время следующего запуска – время старта работы агента, если он не периодический то выполнится 1 раз в это время;
    • модуль — этот модуль будет автоматически подключаться, а именно будет подключаться файл /bitrix/modules/ID модуля/include.php, в этом случае необходимо убедиться, что функция-агент будет доступна после подключения этого файла;
    • функция агента — это основное поле, у нас функция называется testAgent();
    • ID пользователя – это фильтр выполнения на хите для определенного пользователя;

    Сама функция будет выглядеть так:

    function testAgent()
    {
            mail('mail@gmail.com', 'Агент', 'Агент');
            return "testAgent();";
    }

    Функцию добавить в файл /bitrix/php_interface/init.php.

    Для активизации агента выполните в php-консоли админки следующий код:

    CAgent::AddAgent("testAgent();");

    Если письмо пришло, то агент работает и можно писать свой функционал.

      Простые примеры агентов

    <?
    // добавим агент модуля "Статистика"
    CAgent::AddAgent(
        "CStatistic::CleanUpStatistics_2();", // имя функции
        "statistic",                          // идентификатор модуля
        "N",                                  // агент не критичен к кол-ву запусков
        86400,                                // интервал запуска - 1 сутки
        "07.04.2005 20:03:26",                // дата первой проверки на запуск
        "Y",                                  // агент активен
        "07.04.2005 20:03:26",                // дата первого запуска
        30);
    ?>
    <?
    // добавим агент модуля "Техподдержка"
    CAgent::AddAgent(
        "CTicket::AutoClose();",  // имя функции
        "support",                // идентификатор модуля
        "N",                      // агент не критичен к кол-ву запусков
        86400,                    // интервал запуска - 1 сутки
        "",                       // дата первой проверки - текущее
        "Y",                      // агент активен
        "",                       // дата первого запуска - текущее
        30);
    ?>
    <?
    // добавим произвольный агент не принадлежащий ни одному модулю
    CAgent::AddAgent("My_Agent_Function();");
    ?>
    
    <?
    // файл /bitrix/php_interface/init.php
    
    function My_Agent_Function()
    {
       // выполняем какие-либо действия
       return "My_Agent_Function();";
    }
    ?>
    <?
    // добавим произвольный агент принадлежащий модулю
    // с идентификатором my_module
    
    CAgent::AddAgent(
       "CMyModule::Agent007(1)", 
       "my_module", 
       "Y", 
        86400);
    ?>
    
    <?
    // данный агент будет запущен ровно 7 раз с периодичностью раз в сутки, 
    // после чего будет удален из таблицы агентов.
    
    Class CMyModule
    {
       public static function Agent007($cnt=1) : string
       {
          echo "Hello!";
          if($cnt>=7)
             return "";
          return "CMyModule::Agent007(".($cnt+1).");";
       }
    }>

      Курсы валют

    Практичный пример: Обновление курса валют на сайте. Запуск данного агента рекомендуется повесить на cron.

    <?// Обновление курса валют
    
    function AgentGetCurrencyRate()
    {
       global $DB;
    
       // подключаем модуль "валют"
       if(!CModule::IncludeModule('currency'))
          return "AgentGetCurrencyRate();";
    
       $arCurList = array('USD', 'EUR');
       $bWarning = False;
       $rateDay = GetTime(time(), "SHORT", LANGUAGE_ID);
       $QUERY_STR = "date_req=".$DB->FormatDate($rateDay, CLang::GetDateFormat("SHORT", SITE_ID), "D.M.Y");
       $strQueryText = QueryGetData("www.cbr.ru", 80, "/scripts/XML_daily.asp", $QUERY_STR, $errno, $errstr);
    
       // данная строка нужна только для сайтов в кодировке utf8
       $strQueryText = iconv('windows-1251', 'utf-8', $strQueryText);
    
       if (strlen($strQueryText) <= 0)
          $bWarning = True;
    
       if (!$bWarning)
       {
          require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/classes/general/xml.php");
          $objXML = new CDataXML();
          $objXML->LoadString($strQueryText);
          $arData = $objXML->GetArray();
          $arFields = array();
          $arCurRate["CURRENCY_CBRF"] = array();
    
          if (is_array($arData) && count($arData["ValCurs"]["#"]["Valute"])>0)
          {
             for ($j1 = 0; $j1<count($arData["ValCurs"]["#"]["Valute"]); $j1++)
             {
                    $arFields = array(
                        "CURRENCY" => $arData["ValCurs"]["#"]["Valute"][$j1]["#"]["CharCode"][0]["#"],
                      'DATE_RATE' => $rateDay,
                      'RATE' => DoubleVal(str_replace(",", ".", $arData["ValCurs"]["#"]["Valute"][$j1]["#"]["Value"][0]["#"])),
                      'RATE_CNT' => IntVal($arData["ValCurs"]["#"]["Valute"][$j1]["#"]["Nominal"][0]["#"]),
                   );
                    CCurrencyRates::Add($arFields);
              }
    
        }
    }
    
    return "AgentGetCurrencyRate();";
    }?>

    Указанный код добавляется в файл /bitrix/php_interface/init.php. Не забудьте добавить агента AgentGetCurrencyRate(); на странице Настройки > Настройки продукта > Агенты.

      Элементы без цен

    Агент, проверяющий наличие элементов инфоблока без заполненных цен.

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

    1. Создадим:
      • Почтовое событие и шаблон.
      • «Агента», который будет запускать функцию 1 раз в день.
    2. Убедимся, что агент запускается, производится запись в журнал и приходят письма на почту.
    3. Размещение функции должно быть в отдельном файле, который подключается в init.php.
    function AgentChekPrice()
    {
    	if(CModule::IncludeModule("iblock"))
    	{
    		$arSelect = Array("ID", "NAME", "PROPERTY_PRICE");
    		$arFilter = Array("IBLOCK_ID"=> 2, "PROPERTY_PRICE" => false);
    		$rsResCat = CIBlockElement::GetList(Array(), $arFilter, false, false, $arSelect);
    		$arItems = array();
    		while($arItemCat = $rsResCat->GetNext())
    		{
    			$arItems[] = $arItemCat;
    		}
    	
    		CEventLog::Add(array(
    				"SEVERITY" => "SECURITY",
    				"AUDIT_TYPE_ID" => "CHECK_PRICE",
    				"MODULE_ID" => "iblock",
    				"ITEM_ID" => "",
    				"DESCRIPTION" => "Проверка цен, нет цен для ".count($arItems)." элементов",
    		));
    	
    		if(count($arItems) > 0)
    		{
    			$arFilter = Array(
    					"GROUPS_ID" => Array(2)
    			);
    			$rsUsers = CUser::GetList(($by="personal_country"), ($order="desc"), $arFilter);
    			$arEmail = array();
    			while($arResUser = $rsUsers->GetNext())
    			{
    				$arEmail[] = $arResUser["EMAIL"];
    			}
    
    			if(count($arEmail) > 0)
    			{
    				$arEventFields = array(
    						"TEXT" => "Проверка цен, нет цен для ".count($arItems)." элементов",
    						"EMAIL" => implode(", ", $arEmail),
    				);
    				CEvent::Send("INFO_PRICE", "s1", $arEventFields);
    			}
    		}
    	}
    	
    	return "AgentChekPrice();";
    }

    Запуск агентов из cron

    Об агентах

    Агент может вносить ощутимое ожидание на хите пользователя. Но тут надо взвесить различные факторы. Например, агент выполняющийся 0.5 секунд раз в сутки не причинит ощутимого вреда, а такой же агент раз в 30 минут уже будет досаждать. Всецело можно сказать одно: если агенты занимают несколько десятых долей секунды, то уже стоит задуматься о переносе агентов на cron. «Тяжелым» считается агент, который выполняется более 10 секунд.

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

    Механизм запуска

    Перейдите на страницу Настройки > Инструменты > Командная PHP-строка и исполните следующий код:

    COption::SetOptionString("main", "agents_use_crontab", "Y");
    echo COption::GetOptionString("main", "agents_use_crontab", "N");

    Увидели «Y». С этой секунды на хитах будут исполняться только [ds]периодические агенты[/ds][di]Тип агента зависит от программиста, который написал код агента. Программист может сделать агента, который повторится бесконечное число раз. Или только 2-3 раза в зависимости от условий.

    Подробнее …[/di].

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

    В cron добавьте команду на выполнение:

    */10 * * * * /usr/bin/php -f /home/bitrix/www/bitrix/modules/main/tools/cron_events.php

    Где: */10 * * * * — означает [dw]раз в десять минут[/dw][di]Обратите внимание, это лишь пример. Если вашему агенту требуется запуск хотя бы раз в 2-3 минуты, то он никогда не будет идти в ногу со временем. Обычно (в зависимости от разрешений хостера и личных предпочтений) частота времени запуска разнится от 1 до 5 минут.[/di].

    Файл для cron в BitrixEnv

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

    @set_time_limit(0);
    ignore_user_abort(true);

    Если set_time_limit разрешен, то время выполнения может превышать то, что стоит в настройках файла php.ini.

    Но необходимо помнить, что есть ограничения со стороны хостера: на объем памяти, время выполнения, периодичность запуска и т.д.

    Пример кода запуска старых рассылок на кроне.

    Обобщённое решение

    Обобщенное решение для выполнения всех агентов из-под cron.

    Для начала полностью отключим выполнение агентов на хите. Для этого выполним следующую команду в php консоли:

    COption::SetOptionString("main", "agents_use_crontab", "N"); 
    echo COption::GetOptionString("main", "agents_use_crontab", "N"); 
    
    COption::SetOptionString("main", "check_agents", "N"); 
    echo COption::GetOptionString("main", "check_agents", "Y");
    

    В результате выполнения должно быть «NN».

    После этого убираем из файла /bitrix/php_interface/dbconn.php определение следующих констант:

    define("BX_CRONTAB_SUPPORT", true);
    define("BX_CRONTAB", true);

    И добавляем в этот файл:

    if(!(defined("CHK_EVENT") && CHK_EVENT===true))
       define("BX_CRONTAB_SUPPORT", true);

    Создаем файл проверки агентов и рассылки системных сообщений /bitrix/php_interface/cron_events.php:

    <?php
    $_SERVER["DOCUMENT_ROOT"] = realpath(dirname(__FILE__)."/../..");
    $DOCUMENT_ROOT = $_SERVER["DOCUMENT_ROOT"];
    
    define("NO_KEEP_STATISTIC", true);
    define("NOT_CHECK_PERMISSIONS",true);
    define('BX_NO_ACCELERATOR_RESET', true);
    define('CHK_EVENT', true);
    define('BX_WITH_ON_AFTER_EPILOG', true);
    
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
    
    @set_time_limit(0);
    @ignore_user_abort(true);
    
    CAgent::CheckAgents();
    define("BX_CRONTAB_SUPPORT", true);
    define("BX_CRONTAB", true);
    CEvent::CheckEvents();
    
    if(CModule::IncludeModule('sender'))
    {
        BitrixSenderMailingManager::checkPeriod(false);
        BitrixSenderMailingManager::checkSend();
    }
    
    require($_SERVER['DOCUMENT_ROOT']."/bitrix/modules/main/tools/backup.php");
    CMain::FinalActions();
    ?>

    И добавляем данный скрипт в cron:

    */1 * * * * /usr/bin/php -f /home/bitrix/www/bitrix/php_interface/cron_events.php

    После этого все агенты и отправка системных событий будут обрабатываться из-под cron, [dw]раз в 1 минуту[/dw][di]Обратите внимание, некоторым системным агентам может быть недостаточно такой частоты запуска.[/di].

    Примечание: Время выполнения можно скорректировать в соответствие с проектом. Кроме того, есть возможность через установку большого значения mail_event_bulk сделать более «быстрой» доставку почтовых уведомлений. Установка проверки раз в минуту вместе с отправкой за раз 100 сообщений, сделает для пользователей незаметным данную задержку.

    Чтобы не увеличивалась очередь отправки почтовых сообщений, рекомендуется изменить параметр отвечающий за количество почтовых событий обрабатываемых за раз. Для этого выполняем в php консоли следующую команду:

    COption::SetOptionString("main", "mail_event_bulk", "20"); 
    echo COption::GetOptionString("main", "mail_event_bulk", "5");

    Если очередной запуск cron_events.php произошёл до завершения работы ранее запущенного скрипта, то запуска агентов не произойдет и скрипт завершит свою работу. (Так как агенты блокируются на время выполнения.) В данном случае обработка ничем не отличается от обработки на хите, новый хит может произойти в тот момент когда еще не отработали агенты на предыдущем.

    Как правило, скрипты выполненные из-под cron, не имеют ограничения на время исполнения. Но если в скриптах используются методы для работы с БД, то можно столкнуться с ошибкой выполнения вложенных скриптов. Для избежания этой ошибки можно подправить значение в dbconn.php:

    // если скрипт выполняется кроном, то лимит подключения к БД - 600 секунд, иначе - 60
    @set_time_limit(php_sapi_name() == "cli" ? 600 : 60);

    Права доступа

    Интерпретатор php из консоли на сервере должен быть запущен с такими же настройками, как и веб-сервер. Иначе возможна ситуация, что при запуске php из-под cli параметры будут отличаться. Это приводит к трудно-отлаживаемым ошибкам.

    Пользователь, из-под которого выполняется скрипт агентов, должен быть тем же самым, что и пользователь веб-сервера. Часто возникают ошибки, связанные с правами, если например, агент создает кеш, а веб-сайт не может его прочитать или удалить.

    Если задание необходимо добавлять от имени другого пользователя (например, root), то в записи crontab нужно указать имя этого пользователя:

    */1 * * * * USERNAME /usr/bin/php -f /home/bitrix/www/bitrix/php_interface/cron_events.php

    Некоторые проблемы

    Стандартная надпись

    Вопрос:
    А нужно ли при данных настройках отключать в кроне стандартную запись?

    * * * * * bitrix test -f /home/bitrix/www/bitrix/modules/main/tools/cron_events.php && { /usr/bin/php -f /home/bitrix/www/bitrix/php_interface/cron_events.php

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

    Отправка писем агентом через postfix

    После настройки может возникнуть проблема: postfix каждую минуту отправляет письма примерно такого содержания:

    Nov 21 15:39:02 s052d79c2 postfix/pickup[10895]: C15E8BE8D9: uid=600 from=
    Nov 21 15:39:02 s052d79c2 postfix/cleanup[10914]: C15E8BE8D9: message-id=<20201121053902.C15E8BE8D9@example.com>
    Nov 21 15:39:02 s052d79c2 postfix/qmgr[10896]: C15E8BE8D9: from=, size=858, nrcpt=1 (queue active)
    Nov 21 15:39:02 s052d79c2 postfix/local[10916]: C15E8BE8D9: to=, orig_to=, relay=local, delay=0.4, delays=0.4/0/0/0, dsn=2.0.0, status=sent (delivered to mailbox)
    Nov 21 15:39:02 s052d79c2 postfix/qmgr[10896]: C15E8BE8D9: removed

    Решение проблемы следующее:

    Изначально строка запуска скрипта из cron из инструкции выглядит следующим образом:

    */1 * * * * bitrix /usr/bin/php -f /home/bitrix/ext_www/example.com/bitrix/php_interface/cron_events.php

    Соответственно каждый раз при вызове скрипта отправляется письмо. Чтобы отключить вывод, необходимо перенаправить вывод в «никуда», добавив директиву > /dev/null 2>&1 в конец строки.

    Ещё об агентах

    Осторожнее с периодичными агентами

    На проектах с низкой посещаемостью возможна ситуация, когда большое количество агентов может тормозить работу сайта. Монитор производительности показывает низкую (в районе 1) оценку. Статистика выполнения страниц показывает время генерации от 1 секунды и больше, а некоторые страницы вообще отказываются открываться.

    При таких симптомах посмотрите внимательнее статистику выполнения страницы. Если обнаружится что 90% времени генерации страницы занимает пролог сайта, то вероятно проблема в агентах. Достаточно 5-и агентов, у которых выставлено свойство Периодический. При выставлении этого свойства система при пропуске выполнения агента в следующие разы пытается компенсировать пропуски, что приводит к перегрузке сервера.

    Решение: Выключить периодичность агентов.

    События

    Иногда бывает необходимо повлиять на ход выполнения какой-нибудь API функции. Но если ее изменить, то эти изменения будут утеряны при очередном обновлении. Для таких случаев и разработана система событий. В ходе выполнения некоторых API функций, в определённых точках установлены вызовы определённых функций, так называемых обработчиков события.

    Примечание: С обработчиками следует обращаться очень внимательно. Поскольку событийная модель BitrixFramework довольно богатая, то при небрежности в коде обработчика могут появиться трудноуловимые ошибки. Они могут основательно попортить нервы разработчику.

    Какие функции-обработчики должны быть вызваны в каком месте (при каком событии) — нужно устанавливать вызовом функции, регистрирующей обработчики. В данный момент их две: BitrixMainEventManager::addEventHandler и BitrixMainEventManager::registerEventHandler. Сам набор событий для каждого модуля описан в документации по каждому модулю. Вот, например, ссылка на события главного модуля.

    registerEventHandler — функция для регистрации обработчиков, расположенных в модулях и использующихся для взаимодействия между модулями системы. Эту функцию необходимо вызвать один раз при инсталляции модуля, после этого функция-обработчик события будет автоматически вызываться в определённый момент, предварительно подключив сам модуль.

    Удаляется с помощью BitrixMainEventManager::unRegisterEventHandler при удалении модуля.

    Пример

    $eventManager = BitrixMainEventManager::getInstance();
    
    // функции обработчики модуля компрессии подключаются дважды - в начале и в конце каждой страницы
    $eventManager->registerEventHandler("main", "OnPageStart", "compression", "CCompress", "OnPageStart", 1);
    $eventManager->registerEventHandler("main", "OnAfterEpilog", "compression", "CCompress", "OnAfterEpilog");
    
    // инсталлятор модуля рекламы регистирует пустой обработчик
    // при возникновении события OnBeforeProlog модуль рекламы будет просто подключаться на каждой странице
    // что сделает возможным выполнять его API функции без предварительного подключения в теле страницы
    $eventManager->registerEventHandler("main", "OnBeforeProlog", "advertising");

    Каждый модуль может предоставить другим модулям интерфейс для неявного взаимодействия — набор событий. Такое взаимодействие позволяет сделать модули максимально независимыми друг от друга. Модуль ничего не знает об особенностях функционирования другого модуля, но может взаимодействовать с ним через интерфейс событий.

    AddEventHandler — функция предназначена для регистрации произвольных обработчиков, которые не расположены в модулях. Ее необходимо вызывать до возникновения события на тех страницах, где требуется его обработать. Например, если событие нужно обработать на всех страницах, где оно возникает, то функцию можно вызвать в /bitrix/php_interface/init.php.

    Пример

    // регистрация обработчика в /bitrix/php_interface/init.php
    $eventManager = BitrixMainEventManager::getInstance();
    $eventManager->addEventHandlerCompatible("main", "OnBeforeUserLogin", "MyOnBeforeUserLoginHandler");
    
    function MyOnBeforeUserLoginHandler($arFields)
    {
       if(strtolower($arFields["LOGIN"])=="guest")
       {
           global $APPLICATION;
           $APPLICATION->throwException("Пользователь с именем входа Guest не может быть авторизован.");
           return false;
       }
    }

    Анонимные функции хорошо подходят для «решить быстро и безболезненно». Чтобы не засорять код именными функциями или классами делаю так:

    use BitrixMainEventManager;
    
    $eventManager = EventManager::getInstance();
    
    $eventManager->addEventHandlerCompatible("main", "OnAfterUserLogin", function(&$fields) {
    // Мой код
    });

    Различия в использовании функций

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

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

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

    • Предназначенные для отмены дальнейшего выполнения метода. Например, событие OnBeforeUserDelete позволяет отменить удаление пользователя при заданных условиях (наличие критических связанных объектов), событие OnBeforeUserLogin — запретить авторизацию пользователю;
    • Позволяющие выполниться в определённых методах, при завершении их исполнения. Например, OnAfterUserLogin — после проверки имени входа и пароля, событие OnUserDelete — перед непосредственным удалением пользователя из БД, позволяет удалить связанные объекты;
    • Возникающие во время исполнения страницы, для того чтобы включить свой код в определённые места на странице. Например, OnBeforeProlog, (подробнее см. порядок выполнения страниц).

    Совет от Антона Долганина.

    Если необходимо расширить журнал событий новыми видами событий (например, при действиях с инфоблоками), то нужно использовать обработчик событий OnEventLogGetAuditTypes. Массив, который он вернет, приплюсуется к стандартному массиву в админке. Именно с помощью него дописывает типы модуль Форум, например.

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

    Список ссылок по теме:

    • Примеры использования событий
    • Использование событий при работе с компонентами
    • Использование событий при кастомизации административного меню

    События в D7

    В D7, по сравнению со старым ядром, снижены требования к данным, которые должен иметь код, порождающий событие. Пример отправки события:

    $event = new BitrixMainEvent("main", "OnPageStart");
    $event->send();

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

    foreach ($event->getResults() as $eventResult)
    {
        switch($eventResult->getType())
        {
            case BitrixMainEventResult::ERROR:
                // обработка ошибки
                break;
            case BitrixMainEventResult::SUCCESS:
                // успешно
                $handlerRes = $eventResult->getParameters(); // получаем то, что вернул нам обработчик события
                break;
            case BitrixMainEventResult::UNDEFINED:
                /* обработчик вернул неизвестно что вместо объекта класса BitrixMainEventResult
                его результат по прежнему доступен через getParameters
                */
                break;
        }
    }

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

    Список ссылок по теме:

    • События в ORM.

    Использование событий

    В главе приводятся примеры работы с событиями.

    В большинстве случаев действия в системе имеют три рода событий:

    • OnBefore;
    • On;
    • OnAfter.

    Важно понимать какой из типов лучше использовать в вашем конкретном случае. События типа OnBefore отрабатывают в любом случае. А вот события типа OnAfter только тогда, когда будет проверена правильность введенных данных (соответствие пароля, e-mail и т.п.). Соответственно, если что-то в данных не так, то событие не отработает.

    Кроме примеров в этой главе можно ещё посмотреть пример использования событий в разделе Модификация тестов и в главе Модули.

    Как написать обработчик события

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

    Анализ вызова события

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

    Вызов обработчиков всегда одинаковый, меняются только переменные и логика обработки ответа. Для анализа необходимо обратиться к исходным кодам. Это можно сделать просмотрев файлы системы, а лучше с использованием модуля Живое АПИ.

    Выберем для примера событие OnBeforeSocNetGroupAdd:

    Количество переменных. В этом событии переменных всего одна ($arFields). Именно столько же переменных нам надо будет вызвать в нашем обработчике. Переменных также может быть две или больше, например в событии OnSocNetGroupAdd:

    Переопределение переменных. Если перед одной из переменных стоит &, значит ее можно переопределить (это называется передача по ссылке).

    Отмена действия. В нашем случае для события OnBeforeSocNetGroupAdd есть такая возможность:, если мы в нашем обработчике сделаем return false, группа создана не будет. А, к примеру, в OnSocNetGroupAdd возможности отмены действия нет. Ибо действие уже произведено.

    Создание обработчика события

    Напомним теорию: для обработки событий в ваших модулях вам надо использовать RegisterModuleDependences. А для обработки в иных случаях вам надо использовать AddEventHandler.

    Имя модуля нам известно (socialnetwork), имя события известно (OnBeforeSocNetGroupAdd), пишем функцию/метод по правилам из теории и не забываем про:

    • количество переменных
    • возможность переопределения
    • отмену действия

    Как узнать что содержится в переменных, какие ключи массива и так далее?

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

    echo '
    '; print_r($arFields); echo '

    '; die();

    Отмена действий

    Отмена действия с передачей ошибки в систему:

    if ($GLOBALS['USER']->GetID() == 2) {
       $GLOBALS['APPLICATION']->throwException('Вы не можете создавать группы.');
       return false;
    }

    Результат

    Мы собрали абстрактный обработчик, который добавляет к описанию группы правило, и запрещает пользователю с ID=2 создавать группы в принципе.

    AddEventHandler('socialnetwork', 'OnBeforeSocNetGroupAdd', 'TestHandler');
    function TestHandler(&$arFields) {
       $arFields['DESCRIPTION'] .= ' Ругаться матом запрещено!';
       if ($GLOBALS['USER']->GetID() == 2) {
          $GLOBALS['APPLICATION']->throwException('Вы не можете создавать группы.');
          return false;
       }
    }

    Совет от Антона Долганина: Лучше не плодить функции, а создать класс ваших обработчиков (в идеале по одному классу на каждый модуль), и писать обработчики внутри классов. Например, CForumHandlers::onBeforeTopicAdd();.

    Добавление закладки в социальную сеть

    Как добавить произвольную закладку в социальную сеть без изменения стандартных компонентов? Для этого необходимо написать обработчики специальных событий.

    Обработчики можно расположить в файле /bitrix/php_interface/init.php.

    // Событие происходит при формировании списка дополнительного 
    // функционала соц.сети
    // В обработчике можно изменить или дополнить список
    AddEventHandler("socialnetwork", "OnFillSocNetFeaturesList", "__AddSocNetFeature");
    
    // Событие происходит при формировании списка закладок
    // В обработчике можно изменить список закладок
    AddEventHandler("socialnetwork", "OnFillSocNetMenu", "__AddSocNetMenu");
    
    // Событие происходит в комплексном компоненте при работе в ЧПУ
    // режиме при формировании списка шаблонов адресов страниц 
    // комплексного компонента
    AddEventHandler("socialnetwork", "OnParseSocNetComponentPath", "__OnParseSocNetComponentPath");
    
    // Событие происходит в комплексном компоненте при работе в 
    // не ЧПУ режиме при формировании списка псевдонимов переменных
    AddEventHandler("socialnetwork", "OnInitSocNetComponentVariables", "__OnInitSocNetComponentVariables");
    
    // При формировании списка дополнительного функционала 
    // добавим дополнительную запись superficha
    function __AddSocNetFeature(&$arSocNetFeaturesSettings)
    {
       $arSocNetFeaturesSettings["superficha"] = array(
          "allowed" => array(SONET_ENTITY_USER, SONET_ENTITY_GROUP),
          "operations" => array(
             "write" => array(SONET_ENTITY_USER => SONET_RELATIONS_TYPE_NONE, SONET_ENTITY_GROUP => SONET_ROLES_MODERATOR),
             "view" => array(SONET_ENTITY_USER => SONET_RELATIONS_TYPE_ALL, SONET_ENTITY_GROUP => SONET_ROLES_USER),
          ),
          "minoperation" => "view",
       );
    }
    
    // При формировании списка закладок добавим дополнительную 
    // закладку для функционала superficha
    function __AddSocNetMenu(&$arResult)
    {
       // Достуна для показа
       $arResult["CanView"]["superficha"] = true;
       // Ссылка закладки
       $arResult["Urls"]["superficha"] = CComponentEngine::MakePathFromTemplate("/workgroups/group/#group_id#/superficha/",
                                                                                array("group_id" => $arResult["Group"]["ID"]));
       // Название закладки
       $arResult["Title"]["superficha"] = "Моя фича";
    }
    
    // При формировании списка шаблонов адресов страниц 
    // комплексного компонента в режиме ЧПУ добавим шаблон 
    // для superficha
    function __OnParseSocNetComponentPath(&$arUrlTemplates, &$arCustomPagesPath)
    {
       // Шаблон адреса страницы
       $arUrlTemplates["superficha"] = "group/#group_id#/superficha/";
       // Путь относительно корня сайта,
            // по которому лежит страница
       $arCustomPagesPath["superficha"] = "/bitrix/php_interface/1/";
    }
    
    // Если компонент соц.сети работает в режиме 
    // ЧПУ, то этот обработчик не нужен 
    function __OnInitSocNetComponentVariables(&$arVariableAliases, &$arCustomPagesPath)
    {
       }
    

    По пути /bitrix/php_interface/1/ должен лежать файл superficha.php, который содержит код страницы:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
    <?
    $APPLICATION->IncludeComponent(
       "bitrix:socialnetwork.group_menu",
       "",
       Array(
          "GROUP_VAR" => $arResult["ALIASES"]["group_id"],
          "PAGE_VAR" => $arResult["ALIASES"]["page"],
          "PATH_TO_GROUP" => $arResult["PATH_TO_GROUP"],
          "PATH_TO_GROUP_MODS" => $arResult["PATH_TO_GROUP_MODS"],
          "PATH_TO_GROUP_USERS" => $arResult["PATH_TO_GROUP_USERS"],
          "PATH_TO_GROUP_EDIT" => $arResult["PATH_TO_GROUP_EDIT"],
          "PATH_TO_GROUP_REQUEST_SEARCH" => $arResult["PATH_TO_GROUP_REQUEST_SEARCH"],
          "PATH_TO_GROUP_REQUESTS" => $arResult["PATH_TO_GROUP_REQUESTS"],
          "PATH_TO_GROUP_REQUESTS_OUT" => $arResult["PATH_TO_GROUP_REQUESTS_OUT"],
          "PATH_TO_GROUP_BAN" => $arResult["PATH_TO_GROUP_BAN"],
          "PATH_TO_GROUP_BLOG" => $arResult["PATH_TO_GROUP_BLOG"],
          "PATH_TO_GROUP_PHOTO" => $arResult["PATH_TO_GROUP_PHOTO"],
          "PATH_TO_GROUP_FORUM" => $arResult["PATH_TO_GROUP_FORUM"],
          "PATH_TO_GROUP_CALENDAR" => $arResult["PATH_TO_GROUP_CALENDAR"],
          "PATH_TO_GROUP_FILES" => $arResult["PATH_TO_GROUP_FILES"],
          "PATH_TO_GROUP_TASKS" => $arResult["PATH_TO_GROUP_TASKS"],
          "GROUP_ID" => $arResult["VARIABLES"]["group_id"],
          "PAGE_ID" => "group_superficha",
       ),
       $component
    );
    ?>
    
    Полезный код страницы...

    Учет регистрации нового пользователя в статистике

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

    Для решения задачи используется обработчик события OnAfterUserRegister. Код обработчика будет таким:

    AddEventHandler("main", "OnAfterUserRegister", "OnUserEmailLoginRegisterHandler"); 
     
    function OnUserEmailLoginRegisterHandler(&$arFields) 
    { 
     
     
            if(CModule::IncludeModule("statistic") && intval($_SESSION["SESS_SEARCHER_ID"]) <= 0)
            { 
                $event1 = "register";
                $event2 = "new_user";
                $event3 = $arFields["EMAIL"];
                CStatistic::Set_Event($event1, $event2, $event3);      
            }
            return $arFields; 
    }

    В результате в отчетах модуля статистики появятся данные о регистрациях:

    Зацикливание обработчиков событий

    Задача: при изменении элемента инфоблока модифицировать другой элемент. Кейс может быть какой угодно — это и логирование, и деактивация основного товара, когда нет активных предложений, и изменение даты активности связанного элемента. Если создать обработчик, использующий метод CIBlockElement::Update, и повесить его на события OnBeforeIBlockElementUpdate / OnAfterIBlockElementUpdate, то:

    вызов обработчика OnBeforeIBlockElementUpdate/OnAfterIBlockElementUpdate
         ....
    CIBlockElement::Update
    вызов обработчика OnBeforeIBlockElementUpdate/OnAfterIBlockElementUpdate
             ....
             CIBlockElement::Update
    
                        ...
                    итог - 500 (Internal Server Error)

    Причина в том, что происходит рекурсивный вызов обработчика. Ниже приведен код, который избавляет от подобных проблем. В качестве примера взят обработчик OnAfterIBlockElementUpdate.

    class myClass 
    { 
       protected static $handlerDisallow = false; 
     
       public static function iblockElementUpdateHandler(&$fields) 
       { 
          /* проверяем, что обработчик уже запущен */
          if (self::$handlerDisallow) 
               return; 
           /* взводим флаг запуска */
          self::$handlerDisallow = true;           
          /*  наш код, приводящий к вызову CIBlockElement::Update */ 
          ... 
          CIBlockElement :: Update (..., ...); 
     
          /* вновь разрешаем запускать обработчик */ 
          self::$handlerDisallow = false;
       } 
    } 

    Пояснение. За основу взят класс, так как подобные решения в основном используются в собственных модулях. В классе имеется статическая булевая переменная — $handlerDisallow. По умолчанию она имеет значение false — нет запрета. В самом начале обработчика необходимо проверять ее значение. Если обработчик уже запущен, она будет равна true и выполнение необходимо прервать. Если же выполнять обработчик можно, необходимо присвоить этой переменной true на время выполнения всего обработчика. В конце необходимо флаг сбросить ($handlerDisallow), иначе до конца хита ваш обработчик не выполнится больше ни разу.

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

    Можно дополнить класс возможностью блокировать работу обработчика «снаружи». Для этого измените тип переменной и добавьте три метода:

    class myClass
    {
       protected static $handlerDisallow = 0;
    
       public static function disableHandler()
       {
          self::$handlerDisallow--;
       }
    
       public static function enableHandler()
       {
          self::$handlerDisallow++;
       }
    
       public static function isEnabledHandler()
       {
          return (self::$handlerDisallow >= 0);
       }
    
       public static function iblockElementUpdateHandler(&$fields)
       {
          /* проверяем, что обработчик уже запущен */
          if (!self::isEnabledHandler())
             return;
          /* взводим флаг запуска */
          self::disableHandler();
          /*  наш код, приводящий к вызову CIBlockElement::Update */
          ...
          CIBlockElement :: Update (..., ...);
    
          /* вновь разрешаем запускать обработчик */
          self::enableHandler();
       }
    }

    Совместная работа пары событий

    Рассмотрим использование пары обработчиков событий на примере событий метода CCatalogProduct::GetOptimalPrice.

    Метод имеет два события: OnGetOptimalPrice и OnGetOptimalPriceResult, различающихся набором передаваемых параметров. Предположим, что для реализации логики обработчика OnGetOptimalPriceResult необходимо знать список групп пользователя, переданный в метод. Но эти данные доступны только в обработчике OnGetOptimalPrice.

    Необходимо сохранить параметры вызова метода в одном обработчике (onGetOptimalPrice), а использовать в другом (onGetOptimalPriceResult), после чего очистить кеш параметров внутри обработчика. Реализация работы обработчиков «в паре»:

    class myClass
    {
       protected static $handlerDisallow = 0; 
     
       protected static $handlerParams = array();
    
       public static function disableHandler()
       {
       self::$handlerDisallow--;
       }
    
       public static function enableHandler()
       {
       self::$handlerDisallow++;
       }
    
       public static function isEnabledHandler()
       {
       return (self::$handlerDisallow >= 0);
       }
    
       public static function onGetOptimalPrice()
       {
            /* проверяем, что обработчик уже запущен */
            if (!self::isEnabledHandler())
                    return;
            /* взводим флаг запуска */
            self::disableHandler();   
            self::$handlerParams = func_get_args();
            return true;
       }
     
       public static function onGetOptimalPriceResult(&$data)  
       {  
            /* далее логика, использующая данные из self::$handlerParams
            примерно так
            if (self::$handlerParams[0] == 17) //первый параметр из вызова CCatalogProduct::GetOptimalPrice
               $data['RESULT_PRICE']['BASE_PRICE'] = 100;
             */
            self::$handlerParams = array();  // очищаем параметры - в обязательном порядке 
            /* вновь разрешаем запускать обработчик */ 
            self::enableHandler();  
        } 
    }

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

    Данные до и после update

    Описание

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

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

    Решить задачу в D7 достаточно просто, в случае старого API — несколько сложнее, хотя подход одинаков. На D7 проще в силу того что набор имеющихся событий и место их вызова стандартизированы, равно как и данные, доступные в обработчиках. В старом ядре не всегда есть обработчики либо есть только один (обычно вызываемый после обновления).

    D7

    Реальный пример, взятый из API купонов правил корзины. В случае перепривязки купона (смены правила корзины, к которому он относится), необходимо обновить флаг существования купонов как у старого, так и у нового правила. Класс BitrixSaleInternalsDiscountCouponTable:

    public static function onUpdate(MainEntityEvent $event)
    {
       if (!self::isCheckedCouponsUse())
          return;
       $data = $event->getParameter('fields');
       if (isset($data['DISCOUNT_ID']))
       {
          $data['DISCOUNT_ID'] = (int)$data['DISCOUNT_ID'];
          $id = $event->getParameter('id');
          $couponIterator = self::getList(array(
             'select' => array('ID', 'DISCOUNT_ID'),
             'filter' => array('=ID' => $id)
          ));
          if ($coupon = $couponIterator->fetch())
          {
             $coupon['DISCOUNT_ID'] = (int)$coupon['DISCOUNT_ID'];
             if ($coupon['DISCOUNT_ID'] !== $data['DISCOUNT_ID'])
             {
                self::$discountCheckList[$data['DISCOUNT_ID']] = $data['DISCOUNT_ID'];
                self::$discountCheckList[$coupon['DISCOUNT_ID']] = $coupon['DISCOUNT_ID'];
             }
          }
          unset($coupon, $couponIterator);
       }
    }  
     
    public static function onAfterUpdate(MainEntityEvent $event)
    {
       self::updateUseCoupons();
    }

    Использованы события onUpdate и onAfterUpdate. Первое вызывается перед изменением записи в таблице, но уже после проверки данных и возможной отмены действия. Поскольку функционал реализован в самом классе, нет необходимости регистрировать обработчики отдельно. Для вмешательства в работу чужого класса регистрацию своих обработчиков придется сделать, используя следующий код:

          $eventManager = BitrixMainEventManager::getInstance();
          $eventManager->registerEventHandler('модуль', 'событие', 'Ваш_модуль', 'Ваш_класс', 'метод_класса');  
    
    /* событие - BitrixMainEntityDataManager::EVENT_ON_UPDATE или BitrixMainEntityDataManager::EVENT_ON_AFTER_UPDATE */
    

    Что же делают эти два обработчика? Рассмотрим обработчик onUpdate.

    1. if (!self::isCheckedCouponsUse()) 
          return; 

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

    2. $data = $event->getParameter('fields');
      if (isset($data['DISCOUNT_ID'] ) )
      {
         $data['DISCOUNT_ID'] = (int)$data['DISCOUNT_ID'];
         $id = $event->getParameter('id');
         $couponIterator = self::getList(array(
            'select' => array('ID', 'DISCOUNT_ID'),
            'filter' => array('=ID' => $id)
         ));
         if ($coupon = $couponIterator->fetch())
         {
            $coupon['DISCOUNT_ID'] = (int)$coupon['DISCOUNT_ID'];
            if ($coupon['DISCOUNT_ID'] !== $data['DISCOUNT_ID'] )
            {
               self::$discountCheckList[$data['DISCOUNT_ID']] = $data['DISCOUNT_ID'];
               self::$discountCheckList[$coupon['DISCOUNT_ID']] = $coupon['DISCOUNT_ID'];
            }
         }
         unset($coupon, $couponIterator);
      }
      

      Проверяется наличие интересующего нас поля (DISCOUNT_ID). Если оно есть, получается из базы старое значение, сравнивается, и, в случае несовпадения, — заносятся старое и новое значение в статическую переменную класса.

    3. Собственно, остался вызов onAfterUpdate, внутри которого проверяется, что переменная с данными не пустая и идет обновление флага существования купонов для старого и нового правил корзины.

    Примечание: Обязательные условия:

    • Оба обработчика должны относится к одному классу.
    • Оба обработчика должны быть статические.
    • Данные хранятся в статической переменной класса.

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

    Старое ядро

    При реализации на API старого ядра задача усложняется — может не оказаться необходимых событий. Единственный выход — либо просить вендора их добавить, либо искать обходные пути. В рамках описываемой задачи предположим, что они есть. Небольшой недостаток есть в том, что в старом ядре может выполниться событие Before и не выполниться After. Рассмотрим на примере CCatalogProduct. У метода Update есть два события — OnBeforeProductUpdate и OnProductUpdate. Создаем класс обработчиков:

    class MyChanger
    {
       protected static $oldWeight = array();
    
       public static function OnBeforeUpdate($id, &$fields)
       {
          $id = (int)$id;
          if ($id <= 0)
             return true;
          if (isset($fields['WEIGHT'] ) )
          {
             $productRes = CCatalogProduct::GetList(array(), array('ID' => $id), false, false, array('WEIGHT') );
             $product = $productRes->Fetch();
             if (!empty($product))
             {
                if ($product['WEIGHT'] != $fields['WEIGHT'] )
                   self::$oldWeight[$id] = $product['WEIGHT'];
             }
          }
          return true;
       }
    
       public static function OnUpdate($id, $fields)
       {
          if (isset($fields['WEIGHT'] ) && isset(self::$oldWeight[$id] ) )
          {
             /*
               * необходимые действия
               */
    
     unset(self::$oldWeight[$id] );
          }
       }
    }

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

    Метод MyChanger::OnBeforeUpdate вешается на событие модуля catalog OnBeforeProductUpdate. Если в массиве обновляемых данных есть поле WEIGHT — из базы получается старое значение и если оно отличается, то старое заносится в статическую переменную.

    Метод MyChanger::OnUpdate должен содержать требуемую логику (логирование, отправка писем и так далее). Он должен быть зарегистрирован на событие модуля catalog OnProductUpdate.

    Примечание: перечень параметров обработчика должен соответствовать параметрам, передаваемым в обработчик из метода.

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

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

    Вопрос: Можно ли вызывать функцию AddEventHandler несколько раз для одного и того же события?

    Ответ: Вызывать несколько раз функцию AddEventHandler с одинаковыми первыми двумя параметрами можно. Случаи, когда это не так (возможен лишь один обработчик на событие) очень редки. При повторном вызове желательно указывать четвёртый параметр, который отвечает за очерёдность вызова обработчиков. Если не указать этот параметр, то обработчики будут вызваны в порядке добавления.

    Как обработчику события узнать, какое событие он обрабатывает?

    Функция является обработчиком событий модулей (функция не знает каких модулей и каких событий). Но она должна, учитывая какое произошло событие, совершать различные действия.

    Вопрос: как обработчику события узнать, какое событие он обрабатывает?

    Решение зависит от способа инициализации.

    1. AddEventHandler — сделайте прослойку.
      function OnAdd()
      {
            RealHandler("add");
      }
      
      function OnUpdate()
      {
            RealHandler("update");
      }
    2. RegisterModuleDependences — при регистрации добавьте аргумент.
      $TO_METHOD_ARG = Array("argument"=>"OnUserDelete");
      RegisterModuleDependences("main", "OnUserDelete", "forum", "CForum", "OnUserDelete", $sort, $TO_PATH, $TO_METHOD_ARG);
      
      class CForum 
      { 
          function OnUserDelete($arguments, &$arFields) 
          { 
              //Код обработчика     
          } 
      }

    Вопрос: Как осуществить поиск не только по названию товаров но и по описанию.

    Решение: Нужно добавить свойство для поиска и в настройках свойства указываем что оно участвует в поиске. Далее:

    // регистрируем обработчик
    AddEventHandler("search", "BeforeIndex", "BeforeIndexHandler");
     // создаем обработчик события "BeforeIndex"
    function BeforeIndexHandler($arFields)
    {
       if(!CModule::IncludeModule("iblock")) // подключаем модуль
          return $arFields;
       if($arFields["MODULE_ID"] == "iblock")
       {
          $db_props = CIBlockElement::GetProperty(                        // Запросим свойства индексируемого элемента
                                        $arFields["PARAM2"],         // BLOCK_ID индексируемого свойства
                                        $arFields["ITEM_ID"],          // ID индексируемого свойства
                                        array("sort" => "asc"),       // Сортировка (можно упустить)
                                        Array("CODE"=>"CML2_ARTICLE")); // CODE свойства (в данном случае артикул)
          if($ar_props = $db_props->Fetch())
             $arFields["TITLE"] .= " ".$ar_props["VALUE"];   // Добавим свойство в конец заголовка индексируемого элемента
       }
       return $arFields; // вернём изменения
    } 

    «Ленивые» параметры в событиях

    Чтобы отправить событие выполните следующее:

    use BitrixMainEventManager;
    
    $event = new BitrixMainEvent('moduleName', 'onEventName', array(
        'data' => array(
            'name' => 'John',
            'sex' => 'male',
        ),
        'datetime' => new Datetime(),
    ));
    
    $event->send();

    Таким образом отправится событие от модуля moduleName с именем onEventName и данными, которые будут доступны в обработчиках этого события.

    В некоторых ситуациях вычисление данных для отправки требует привлечения дополнительных ресурсов. В такой ситуации сконфигурируйте объект иначе:

    use BitrixMainEventManager;
    
    $lazyEvent = new BitrixMainEvent('moduleName', 'onEventName', function() use ($userId){
        
        //какая-то работа, которая получает данные
        $groups = loadUserGroups($userId);
        
        return array(
            'data' => array(
                'name' => 'John',
                'sex' => 'male',
                'groups' => $groups,
            ),
            'datetime' => new Datetime(),
        );
    });
    
    
    $Lazyevent->send();

    В таком случае вычисление параметров события будет отложено до первого обращения к ним в обработчике. В итоге получается два преимущества:

    1. Если нет обработчиков, то не будут запускаться вычисления.
    2. Если в обработчиках не используются параметры события, то снова не будут запускаться вычисления.

    Примечание: Доступно с версии главного модуля 17.0.0.

    Настройка ЧПУ

    Примечание: Работа с ЧПУ штатными методами описана в курсе Администратор. Базовый. Перед тем как учиться работать с ЧПУ через API обязательно изучите этот раздел.

    Для хранения идентификаторов элементов/разделов информационных блоков удобнее всего использовать поле Символьный код. Например, в ссылке www.myserver.ru/catalog/mobile/nokia_3310/, mobile — это символьный код раздела Мобильные телефоны, а nokia_3310 — символьный код элемента, находящегося в разделе Мобильные телефоны. Символьный код должен быть уникальным и система сама проверяет уникальность.

    В обработчике 404 ошибки необходимо «разобрать» переменную $_SERVER["REQUEST_URI"] на нужные идентификаторы. Для этого в PHP есть ряд полезных функций:

    • preg_match
    • parse_url
    • pathinfo
    • strpos
    • parse_str

    Например, ссылки вида myserver.ru/users/<Логин пользователя> обрабатываются в файле 404.php так:

    <?
    if(preg_match("~^/users/([a-z_][a-z0-9_]{2,14})/?$­~i",$_SERVER["REQUEST_URI"],$match))
    {
    header("HTTP/1.1 200 OK");
    //делаем выборку по идентификатору
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.­php");
    $res = CUser::GetList($O, $B, Array("LOGIN_EQUAL_EXACT"=>$match[1],"ACTIVE"=>"Y"­));
    //$match[1] содержит логин
    if($arUser = $res->GetNext())
    {
    //выводим данные пользователя
    }
    else
    {
    //ошибка: нет такого пользователя
    }
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");
    }
    else
    {
    header("HTTP/1.1 404 Not Found");
    //ошибка
    }
    ?>
    

    Но жесткая проверка в preg_match не позволит сделать ссылки вида www.myserver.ru/users/user_login/?r1=banner&r2=com­puterra.ru, которые очень необходимы для анализа рекламных компаний. Поэтому, в начале файла 404.php пишем:

    <?$arURI = parse_url($_SERVER["REQUEST_URI"]);
    $_SERVER["REQUEST_URI"] = $arURI["path"];
    if(!empty($arURI["query"]))
    {
    parse_str($arURI["query"],$par);
    foreach($par as $key => $val)
    {
    global $$key;
    $$key = $val;
    }
    }
    ?>
    

    Комплексный компонент и SEF режим

    В комплексные компоненты встроена функция генерации ЧПУ. У этих компонентов всегда есть входной параметр SEF_MODE, который может принимать значения Y и N. Если параметр SEF_MODE равен N, то компонент работает с физическими ссылками и все параметры передает через стандартные параметры HTTP запроса. Например:

    /fld/cat.php?IBLOCK_ID=12&SECTION_ID=371

    Если параметр SEF_MODE равен Y, то компонент генерирует и обрабатывает ссылки на основании шаблонов. Например, он может понять и обработать ссылку:

    /catalog/section/371.php?IBLOCK_ID=12, даже если сам он лежит в файле /fld/cat.php.

    Если параметр SEF_MODE равен Y, то у компонента должен так же присутствовать параметр SEF_FOLDER, который должен содержать путь до папки, с которой работает компонент. Этот путь может как совпадать с физическим путем, так и не совпадать. Например, в компоненте bitrix:catalog, подключенном в файле /fld/cat.php, могут быть установлены параметры SEF_MODE = Y и SEF_FOLDER=/catalog/. Тогда компонент будет отвечать на запросы по пути /catalog/.... По умолчанию редактор должен устанавливать текущий физический путь к редактируемому файлу.

    У комплексного компонента, который может работать в режиме SEF, должен быть определен набор шаблонов путей по умолчанию. Например, в комплексном компоненте bitrix:catalog может быть определен следующий массив:

    $arDefaultUrlTemplatesSEF = array( 
          "list" => "index.php", 
          "section" => "section.php?IBLOCK_ID=#IBLOCK_ID#&SECTION_ID=#SECTION_ID#",
          "element" => "element.php?ELEMENT_ID=#ELEMENT_ID#" 
      );

    Эти шаблоны путей могут быть переопределены с помощью входного параметра комплексного компонента SEF_URL_TEMPLATES, содержащего новый массив всех шаблонов путей или их части.

    При сохранении в редакторе страницы с компонентом, работающим в SEF режиме, создается или обновляется запись в системе urlrewrite. Например, при сохранении файла /fld/cat.php, в котором лежит компонент bitrix:catalog, переключенный в SEF режиме с параметром SEF_FOLDER=/catalog/, в системе urlrewrite создается или обновляется запись типа:

    array( 
     "CONDITION" => "#^/catalog/#", 
     "ID" => "bitrix:catalog", 
     "PATH" => "/fld/cat.php" 
      ),
    • в CONDITION записывается значение параметра SEF_FOLDER, обрамленное символами «#^» и «#»;
    • в ID записывается название компонента;
    • в PATH записывается физический путь к файлу, который сохраняется.

    Если запись с таким PATH и ID уже есть, то она обновляется, если нет – добавляется.

    В run-time при запросе физически не существующей страницы механизм urlrewrite производит поиск соответствующей записи по CONDITION и передает управление на страницу PATH.

    Компонент на странице PATH на основании шаблонов путей выясняет запрашиваемую страницу и восстанавливает переменные, спрятаные в пути.

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

    То есть, набор шаблонов путей

    "section" => "section/#SECTION_ID#.php?IBLOCK_ID=#IBLOCK_ID#", 
    "element" => "element/#ELEMENT_ID#.php"

    является допустимым, а набор шаблонов путей

    "section" => "#SECTION_ID#.php?IBLOCK_ID=#IBLOCK_ID#", 
    "element" => "#ELEMENT_ID#.php"

    допустимым не является.

    Список ссылок по теме:

    • HTTP POST запросы

    Примеры

    Пример

    Новости вида /about/news/23.html (ссылка для печати /about/news/print_23.html) вместо /about/news/detail.php?ID=23 (/about/news/detail.php?ID=23&print=Y)

    • mod_rewrite
      RewriteEngine On
      RewriteBase /
      RewriteRule ^about/news/([0-9]+).html$ about/news/detail.php?ID=$1
      RewriteRule ^about/news/print_([0-9]+).html$ about/news/detail.php?ID=$1&print=Y
      
    • Обработчик 404 ошибки
      <?if(preg_match("~^/about/news/(print_)?([0-9]+).html$~",$_SERVER["REQUEST_URI"],$match))
      {
      header("HTTP/1.1 200 OK");
      $_GET["print"] = (strlen($match[1])>0 ? "Y": "");
      $_REQUEST["ID"] = $match[2];
      include($_SERVER["DOCUMENT_ROOT"]."/about/news/detail.php");
      }
      else
      {
      define("ERROR_404", "Y");
      header("HTTP/1.1 404 Not Found");
      require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
      $APPLICATION->SetTitle("404 - файл не найден");
      require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");
      }
      ?>

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

    Как убрать «PHPSESSID=…» из URL?

    Чтобы избавиться от идентификатора сессии в URL, раскомментируйте строку в /.htaccess:

    php_flag session.use_trans_sid off

    Если это не дает результата, необходимо изменить значение параметра session.use_trans_sid на Off непосредственно в php.ini на сервере.

    Удостоверьтесь, также, что значение параметра session.use_cookies установлено в On.

    Как убрать из URL страницы знак вопроса?

    Для этого необходимо выполнить следующие шаги:

    • создать в каталоге /news/ файл .htaccess со следующим содержимым:
      ErrorDocument 404 /news/404.php
    • создать в каталоге /news/ файл 404.php со следующим содержимым:
      <?
      $arrPath = pathinfo($_SERVER["REQUEST_URI"]);
      function initialize_params($url)
      {
      
      if (strpos($url,"?")>0)
      {
      $par = substr($url,strpos($url,"?")+1,strlen($url));
      $arr = explode("#",$par);
      $par = $arr[0];
      $arr1 = explode("&",$par);
      
      foreach ($arr1 as $pair)
      {
      $arr2 = explode("=",$pair);
      global $$arr2[0];
      $$arr2[0] = $arr2[1];
      }
      
      }
      
      }
      
      initialize_params($_SERVER["REQUEST_URI"]);
      require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
      $arr = explode("?",$arrPath["basename"]);
      $fname = $arr[0];
      
      if (strlen(trim($arrPath["extension"]))>0)
      {
      $arr = explode(".",$fname);
      $NEWS_ID = intval($arr[0]);
      
      if ($NEWS_ID>0)
      {
      $ID = $NEWS_ID;
      $APPLICATION->SetTitle("News Details");
      $sapi = php_sapi_name();
      if ($sapi=="cgi") header("Status: 200 OK"); else header("HTTP/1.1 200 OK");
      require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/iblock/iblock.php");
      CIblock::ShowPanel($IBLOCK_ID, $ID);
      include($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/include/news/news_detail.php"); // интерфейсный скрипт, который вызывается
                                                                                              //и в /news/detail.php
      }
      
      }
      require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog.php");
      ?>

    Модуль Поиск

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

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

    • должен быть интерактивным в конкретной сущности;
    • комбинировать поиск по тексту и фильтр по атрибутам;
    • быть быстрым (так называемым поиском «на кончиках пальцев»);
    • результатом поиска должно являться текущее представление сущности.

    Следовательно, возкникла задача: как эффективно ускорить, изменить и скрестить поиск с фильтрами? Для ее решения было предложено 3 варианта:

    1. Интегрировать текущий модуль поиска с фильтрами по сущностям — реализовать оказалось непросто и поиск не будет быстрым.
    2. Перейти на внешний поисковый индекс (Sphinx, Lucene) — результаты тестирования показали хорошую индексацию по атрибутам, тексту, но сложно скрестить результаты поиска с их представлениями в списке. Дополнительная сложность — это десятки и сотни тысяч индексов для облачных версий Битрикс24.
    3. Полнотекстовый индекс Mysql — оказался подходящим вариантом, о нем поговорим ниже.

    Что такое полнотекстовый индекс Mysql?

    • Индекс строится по одному или нескольким текстовым полям.
    • Mysql разбивает содержимое полей на «слова» и заносит их в отдельные таблицы для построения «обратного» индекса.
    • При построении запроса таблицы с индексами прозрачно «джоинятся» к основному запросу.
    • Поиск возможен на естественном языке или в режиме boolean.

    Как использовать индекс?

    Для этого существует оператор MATCH (col1,col2,...) AGAINST (expr [search_modifier]). У него есть модификаторы, которые позволяют определить нам на каком языке искать:

    • IN NATURAL LANGUAGE MODE — на естественном языке;
    • IN BOOLEAN MODE — в режиме boolean;
    • WITH QUERY EXPANSION — в этом режиме Mysql делает 2 запроса: сначала ищет по первому запросу, находит записи, затем во второй запрос подставляет строчки из первого. Таким образом, сильно расширяется область поиска.

    Запрос обычно выглядит следующим образом:

    SELECT * FROM articles WHERE MATCH (title,body) AGAINST ('database' IN NATURAL LANGUAGE MODE);
    

    Отметим особенности поиска Mysql:

    • При запросе на натуральном языке Mysql сам сортирует результат по релевантности.
    • Двойной поиск с параметром WITH QUERY EXPANSION.
    • Поиск по умолчанию регистронезависимый.
    • В качестве expr может использоваться только литерная строка.

    При поиск в режиме boolean можно использовать дополнительные параметры:

    • + Должно быть
    • — Не должно быть
    • (no operator) Или
    • @distance Расстояние
    • >~< Вес
    • ( ) Группировка
    • * Маска (проставляется только справа)

    Типичный запрос выглядит так:

    SELECT * FROM articles WHERE MATCH (title,body) AGAINST ('+MySQL -YourSQL' IN BOOLEAN MODE);
    

    Быстродействие поиска Mysql:

    • Полнотекстовый индекс существенно оптимизирован: кеширование на модификацию, partitioning (разбиение на несколько таблиц индекса).
    • Быстрее LIKE на один-несколько порядков.
    • Быстрее нашего модуля поиска в 2-10 раз за счет оптимизации.
    • Возможен реально интерактивный поиск.

    Требования к поиску:

    • Только Innodb (c 5.6) и MyISAM.
    • Не поддерживаются таблицы с «партициями».
    • Не поддерживаются иероглифы и некоторые кодировки (ucs2).

    Использование в продукте

    1. Создание индекса:

      В инсталляторе модуля новый файл install_ft.sql с содержимым:

      CREATE fulltext index IXF_B_USER_INDEX_1 on b_user_index (SEARCH_USER_CONTENT);
      

      В install.php код:

      $errors = $DB->RunSQLBatch($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/install/mysql/install_ft.sql");			
      if ($errors === false)			
      {				
      	$entity = BitrixMainUserTable::getEntity();
      	$entity->enableFullTextIndex("SEARCH_USER_CONTENT");			
      }
      
    2. Индексирование:
      • Можно создать либо текстовую колонку в таблице, либо связанную отдельную таблицу (предпочтительнее для больших существующих таблиц).
      • В индексированную колонку можно записывать объединенные данные с разных колонок (искать «везде»).
      • На одном хите практически невозможно ни заполнить эту колонку, ни построить индекс по имеющимся данным.
      • Рекомендуем пошаговый индексатор на агентах.
    3. Индексатор:
      • Базовый класс BitrixMainUpdateStepper запускает агенты и предоставляет графический интерфейс.
      • Необходимо реализовать метод execute(), который выполняет реальную работу в пошаговом режиме.
      • Метод getHtml() предоставляет интерфейс с аяксовым «хитователем» — желтый прогресс-индикатор.
      • Метод bind() добавляет агента (в обновлении).
    4. Использование:
      • Добавлены новые операторы в построитель запросов:
        "*" => "FT", // partial full text match
        "*=" => "FTI", // identical full text match
        "*%" => "FTL", // partial full text match based on LIKE
        
      • Необходимо учитывать, что индекса может не быть:
        $operation = (LogIndexTable::getEntity()->fullTextIndexEnabled("CONTENT")? '*' : '*%‘);
        
      • В новый фильтр ORM добавлена поддержка match: методы whereMatch(), whereNotMatch(), а также хелпер matchAgainstWildcard().

    Пример обращения к сущности:

    $res = BitrixMainUserIndexTable::getList(array(
        "select" => array("SEARCH_ADMIN_CONTENT"),
        "filter" => array(
            "*SEARCH_ADMIN_CONTENT" => BitrixMainSearchContent::prepareStringToken("vad dumbrav"),
        )
    ));
    
    var_dump(BitrixMainEntityQuery::getLastQuery()) 
    

    Запрос:

    string(216) "SELECT 
    	`main_user_index`.`SEARCH_ADMIN_CONTENT` AS `SEARCH_ADMIN_CONTENT`
    FROM `b_user_index` `main_user_index` 
    
    WHERE MATCH (`main_user_index`.`SEARCH_ADMIN_CONTENT`) AGAINST ('(+inq* +qhzoeni*)' IN BOOLEAN MODE)"; 
    

    Более медленный запрос с LIKE:

    string(277) "SELECT 
    	`main_user_index`.`SEARCH_ADMIN_CONTENT` AS `SEARCH_ADMIN_CONTENT`
    FROM `b_user_index` `main_user_index` 
    
    WHERE ((UPPER(`main_user_index`.`SEARCH_ADMIN_CONTENT`) like '%INQ%' ESCAPE '!' AND UPPER(`main_user_index`.`SEARCH_ADMIN_CONTENT`) like '%QHZOENI%' ESCAPE '!'))"
    

    Особенности по использованию полнотекстового поиска:

    • Минимальная длина слова в индексе (по умолчанию 3). Параметр соединения ft_min_token_size.
    • Словарь стоп-слов. Можно или отключить, или преобразовывать данные методом BitrixMainSearchContent::prepareStringToken().
    • Поиск по части слова справа (звездочка слева). Можно декомпозировать не длинные слова: 123456789, 23456789, 3456789…

    Пользовательские поля

    Пользовательские поля

    Пользовательское поле — инструмент, позволяющий добавлять к объектам системы поля, не предусмотренные штатным функционалом.

    Необходимо отличать Пользовательские поля в модулях системы и свойства используемые в рамках инфоблоков, хотя в формах системы (форма создания/редактирования пользователя, форме создания/редактирования раздела инфоблока и другие) используется термин пользовательские свойства.

    Пользовательские поля это сущность:

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

    Пользовательские поля могут создаваться в неограниченном количестве для каждого объекта. При выборе того или иного типа пользовательского поля становятся доступными дополнительные поля настройки для соответствующего типа. Детально об этом можно узнать в документации.

    Применение пользовательских полей в системе к тем или иным модулям задаётся с помощью объектов, которые необходимо указать при создании поля. Не все модули имеют объекты для пользовательских полей по умолчанию. Разработчик может создавать собственные объекты, но надо понимать, что в методах GetList поддерживаются только системные объекты:

    Штатные объекты пользовательских полей
    Модуль Объект Описание Продукт
    Главный модуль USER для пользователя БУС, КП
    Блоги BLOG_BLOG для блога БУС, КП
    BLOG_POST для сообщения в блоге БУС, КП
    BLOG_COMMENT для комментария сообщения БУС, КП
    Задачи TASKS_TASK для задач КП
    TASKS_SCRUM_ITEM для прикрепления файлов диска КП
    TASKS_TASK_TEMPLATE_CHECKLIST для чеклиста в шаблонах КП
    TASKS_TASK_CHECKLIST для чеклиста в задачах КП
    TASKS_TASK_TEMPLATE для шаблонов КП
    Информационные блоки IBLOCK_N_SECTION для секций инфоблока с ID = N БУС, КП
    IBLOCK_N Для инфоблока с ID = N БУС, КП
    Календарь CALENDAR_EVENT для событий календаря КП
    Обучение LEARN_ATTEMPT для попыток теста БУС, КП
    Социальная сеть SONET_GROUP для групп соцсети БУС, КП
    SONET_COMMENT для комментариев БУС, КП
    SONET_LOG для логов БУС, КП
    Библиотека документов WEBDAV для библиотек документов КП
    Форум FORUM_MESSAGE для сообщений форума БУС, КП
    Highload-блоки HLBLOCK_N для highload-блока с ID=N БУС, КП
    Торговый каталог PRODUCT для товаров БУС, КП
    Корзина RECYCLEBIN_DISK для элементов корзины КП
    CRM CRM_MAIL_TEMPLATE для шаблонов писем КП
    CRM_TIMELINE для Таймлайна КП
    CRM_LEAD для Лидов КП
    CRM_DEAL для Сделок КП
    CRM_COMPANY для Компаний КП
    CRM_CONTACT для Контактов КП
    CRM_ORDER для Заказов КП
    CRM_INVOICE для Счетов КП
    CRM_(смарт-процесс) для Смарт-процессов КП
    CRM_ACTIVITY для действий бизнес-процессов КП
    CRM_QUOTE для Коммерческих предложений КП
    CRM_LEAD_SPD для Лидов с привязкой к элементам корзины КП
    CRM_DEAL_SPD для Сделок с привязкой к элементам корзины КП
    CRM_COMPANY_SPD для Компаний с привязкой к элементам корзины КП
    CRM_CONTACT_SPD для Контактов с привязкой к элементам корзины КП
    CRM_ORDER_SPD для Заказов с привязкой к элементам корзины КП
    CRM_INVOICE_SPD для счетов с привязкой к элементам корзины КП
    CRM_(смарт-процесс)_SPD для Смарт-процессов с привязкой к элементам корзины КП
    CRM_ACTIVITY_SPD для дейтсвий БП с привязкой к элементам корзины КП
    CRM_QUOTE_SPD для Коммерческих предложений с привязкой к элементам корзины КП
    Роботизация бизнеса (RPA) RPA_(id процесса) для робота. КП
    RPA_COMMENT для комментария к роботу КП

    Примечание: Модули, использующие информационные блоки, могут работать с объектами пользовательских полей модуля Информационные блоки.

    Создание полей

    Создание пользовательских полей из Административной части выполняется на странице Настройки > Настройки продукта > Пользовательские поля либо, что предпочтительнее, с использованием ссылки Добавить пользовательское свойство в тех формах системы, в которых предусмотрено штатное добавление пользовательских свойств:

    • форма добавления/редактирования пользователя;
    • форма добавления/редактирования раздела информационного блока;
    • форма добавления/редактирования блога;
    • форма добавления/редактирования сущности CRM;
    • форма добавления/редактирования RPA процесса.

    Использовать страницу Пользовательские поля можно в случае, если разработчик точно знает, какой идентификатор типа объектов ему нужен.

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

    Установка флажка в поле Не разрешать редактирование пользователем исключит возможность редактирования свойства не только пользователем, но и администратором через административный интерфейс. Значение таких свойств нужно устанавливать используя API. Это нужно для служебных полей, которые не должны использовать пользователи.

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

    • Число;
    • Да/Нет;
    • Видео;
    • Деньги;
    • Шаблон;
    • Список;
    • Строка;
    • Дата;
    • Дата со временем;
    • Привязка к элементам highload-блоков (Для коробочной версии Битрикс24);
    • Привязка к разделам инф. блоков;
    • Привязка к элементам инф. блоков;
    • Привязка к элементам CRM (Для коробочной версии Битрикс24);
    • Привязка к справочникам CRM (Для коробочной версии Битрикс24);
    • Файл;
    • Целое число.
    • Документ из библиотеки документов (Для коробочной версии Битрикс24);
    • Привязка к сотруднику (Для коробочной версии Битрикс24);
    • Документ истории из библиотеки документов
    • Ссылка
    • Адрес
    • Бронирование ресурсов
    • Версия файла (Диск) (Для коробочной версии Битрикс24)
    • Письмо
    • Содержимое ссылки
    • Файл (Диск) (Для коробочной версии Битрикс24)
    • Опрос

    Как правило, этих типов вполне хватает для работы. Если есть необходимость создания собственных типов данных, то это можно сделать самостоятельно. Пример добавления типов данных «Связь с элементом» и «Связь с элементом в виде списка» (блог).

    Для работы с пользовательскими полями можно использовать События.

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

    Событие Вызывается Метод
    OnUserTypeBuildList при построении списка пользовательских полей CUserTypeManager::GetUserType
    OnUserTypeRightsCheck при проверке прав доступа на пользовательские поля GetRights.

    Примеры работы

    Рассмотрим несколько примеров работы с пользовательскими полями.

    Фильтрация и сортировка

    Фильтрация

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

    $sec_Filter= array(  
       "IBLOCK_ID" => $IBLOCK_ID, 
       "DEPTH_LEVEL" => "2", 
       "!UF_ARC_PAGES" => ""
    );

    Примечание: Фильтрация по пользовательским полям работает только при наличии фильтра по IBLOCK_ID.

    Будут отобраны все разделы, у которых установлено значение свойства UF_ARC_PAGES.

    Фильтрация по значению пользовательского свойства:

    $arSFilter ['=UF_USERS_PROPERTY'] =$users_property_value;

    Сортировка

    Сортировать по пользовательским полям разделов:

        $arSort = array(
           "UF_RATING"=>"asc",
           "sort"=>"asc"
          
       );

      Получение значений

    Получить значение пользовательского поля можно с помощью метода GetList соответствующего класса.

    Значение пользовательского поля для пользователя можно получить таким образом:

    $rsUser = CUser::GetByID($user);
    $arUser = $rsUser->Fetch();
    $нужное значение = $arUser['код пользовательского поля']; 

    Чтобы получить значение пользовательского поля определенного пользователя, тип поля – строка, необходимо воспользоваться методом GetList класса CUser. При этом в качестве четвертого аргумента данному методу необходимо передать массив с ключом SELECT, значениями которого являются список кодов пользовательских свойств, которые необходимо получить.

    global $USER;
    $arFilter = array("ID" => $USER->GetID());
    $arParams["SELECT"] = array("UF_USER_CARD_CODE");
    $arRes = CUser::GetList($by,$desc,$arFilter,$arParams);
        if ($res = $arRes->Fetch()) {
            echo $res["UF_USER_CARD_CODE"];
        }

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

    global $USER;
    $arFilter = array("ID" => $USER->GetID());
    $arParams["SELECT"] = array("UF_LIST_TASK");
    $arRes = CUser::GetList($by,$desc,$arFilter,$arParams);
        if ($res = $arRes->Fetch()) {
            foreach ($res["UF_LIST_TASK "] as $id) {
                    $rsRes= CUserFieldEnum::GetList(array(), array(
                        "ID" => $id,
                    ));
                    if($arGender = $rsRes->GetNext())
                        echo $arGender["VALUE"];
                }   
    }

    Если необходимо получить список всех значений пользовательского поля объекта USER типа список, то следует воспользоваться следующим кодом:

    global $USER_FIELD_MANAGER;
    $arFields = $USER_FIELD_MANAGER->GetUserFields("USER");
    $obEnum = new CUserFieldEnum;
    $rsEnum = $obEnum->GetList(array(), array("USER_FIELD_ID" => $arFields["UF_LIST_TASK "]["ID"]));
    while($arEnum = $rsEnum->GetNext()){
       echo $arEnum["VALUE"];
    }

    Для выбора значения пользовательского поля у раздела информационного блока можно воспользоваться методом CIBlockSection:GetList:

    	
    $aSection   = CIBlockSection::GetList( array(), array(
        'IBLOCK_ID'         => 3,
        'CODE'          => 'test_section',
    ), false, array( 'UF_DEV2DAY_FIELD' ) )->Fetch();
    

    Примечание: Передача идентификатора инфоблока (IBLOCK_ID) обязательна, иначе выборка пользовательских свойств не будет осуществлена.

    Получение значения пользовательского поля типа файл конкретного раздела инфоблока:

    $iblockID = 1;
        $sectionID = 10; // ID секции
        $propertyCode = 'UF_MY_FILE'; // Код свойства
    
        $rsResult = CIBlockSection::GetList(array("SORT" => "ASC"), array("ID" => $sectionID, "IBLOCK_ID" => $iblockID),
                                            false, $arSelect = array("ID", "IBLOCK_ID", $propertyCode));
        if ($arResult = $rsResult -> GetNext())
        {
            print_r($arResult);
        }

    Так как пользовательские поля можно использовать не только с разделами информационного блока, но и с любыми другими сущностями, то для выбора значений по идентификатору сущности используется класс CUserTypeManager. Экземпляр данного класса уже находится в глобальной переменной $USER_FIELD_MANAGER.

    	
    global $USER_FIELD_MANAGER;
     
    $aSection   = CIBlockSection::GetList( array(), array(
        'IBLOCK_CODE'   => 'shop_news',
        'CODE'          => 'test_section',
    ) )->Fetch();
     
    if( !$aSection ) {
        throw new Exception( 'Секция не найдена' );
    }
     
    $aUserField = $USER_FIELD_MANAGER->GetUserFields(
    'IBLOCK_3_SECTION',
    $aSection['ID']
    ); // array

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

    Примечание: Чтобы получить все значения пользовательских полей в параметре arSelect достаточно указать Array("UF_*").

      Использование пользовательских свойств

    Использование пользовательских свойств на примере дополнительных полей в подписке

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

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

    • Размещение компонента Форма подписки (bitrix:subscribe.form) в шаблоне сайта.
    • Настройка компонента на страницу редактирования подписки пользователя.
    • Создание, при необходимости, рубрик подписки.
    • Создать раздел управления подписками.
    • Размещение компонента Страница рассылок (bitrix:subscribe.index) и настройка ее на страницу редактирования подписки пользователя
    • Создание страницы редактирования подписки пользователя и размещение на ней компонента Страница редактирования подписки (bitrix:subscribe.edit).
    • Настройка модуля Подписка

    Решение. Зададим идентификатор сущности к которой будут привязываться значения дополнительных свойств: MY_SUBSCRIPTION. В качестве уникального идентификатора объектов этой сущности будут выступать b_subscription.ID.

    На странице Пользовательские поля (Настройки > Настройки продукта > Пользовательские поля) откроем форму создания нового поля.

    Заполните поля:

    • Тип данных — Список
    • Объект — MY_SUBSCRIPTION
    • Код поля — UF_GENDER

    Остальные поля не заполняем, нажимаем кнопку Применить.

    На вкладке Список задаем возможные значения: Женский и Мужской. Применяем внесенные изменения.

    Кастомизиция компонента subscribe.edit

    После копирования компонента в свое пространство имён заменяем вызов на странице /personal/subscribe/subscr_edit.php на путь к копированному компоненту.

    Для вывода значения пользовательских свойств подписки в файле component.php после

    $arResult["ALLOW_REGISTER"] = $bAllowRegister?"Y":"N"; 

    добавляем чтение значений из базы данных

    $arResult["USER_PROPERTIES"] = $GLOBALS["USER_FIELD_MANAGER"]->GetUserFields(
    	"MY_SUBSCRIPTION",
    	$arResult["ID"],
    	LANGUAGE_ID
    ); 

    В файле setting.php шаблона выводим примерно следующее:

    <table> 
    <?foreach ($arResult["USER_PROPERTIES"] as $FIELD_NAME => $arUserField):?> 
    	<tr> 
    	<td><?echo $arUserField["EDIT_FORM_LABEL"]?>:</td> 
    	<td><?$APPLICATION->IncludeComponent( 
    		"bitrix:system.field.edit", 
    		$arUserField["USER_TYPE"]["USER_TYPE_ID"], 
    		array(
    			"bVarsFromForm" => false,
    			"arUserField" => $arUserField
    		),
    		null,
    		array("HIDE_ICONS"=>"Y"));?></td> 
    	</tr> 
    <?endforeach;?> 
    </table> 
    

    Для сохранения значений в базе данных в файле component.php после строк

    if($ID>0) 
    { 
    	... 
    	$res = $obSubscription->Update($ID, $arFields); 
    	... 
    } 
    else 
    { 
    	... 
    	$ID = $obSubscription->Add($arFields); 
    	... 
    } 

    добавляем код установки значений свойств

    if($res && $ID > 0) 
    { 
    	global $USER_FIELD_MANAGER; 
    	$arUserFields = $USER_FIELD_MANAGER->GetUserFields("MY_SUBSCRIPTION"); 
    	$arFields = array(); 
    	foreach($arUserFields as $FIELD_ID => $arField) 
    		$arFields[$FIELD_ID] = $_REQUEST[$FIELD_ID]; 
    	$USER_FIELD_MANAGER->Update("MY_SUBSCRIPTION", $ID, $arFields); 
    } 

    Для полноты действий у данного поля в административной части указываем:

    • Обязательное — Да
    • Подпись — Пол

    Создайте новую подписку (или отредактируйте уже существующую) и укажите пол подписчика.

    Теперь надо сделать так, чтобы #GENDER_HELLO# будет заменяться на Уважаемая/Уважаемый в зависимости от пола. Создаем обработчик события BeforePostingSendMail:

    <? 
    // файл /bitrix/php_interface/init.php 
    // регистрируем обработчик 
    AddEventHandler("subscribe", "BeforePostingSendMail", Array("MyClass", "BeforePostingSendMailHandler")); 
    class MyClass 
    { 
    	// создаем обработчик события "BeforePostingSendMail" 
    	function BeforePostingSendMailHandler($arFields) 
    	{ 
    		$rs = CSubscription::GetByEmail($arFields["EMAIL"]); 
    		if($ar = $rs->Fetch()) 
    		{ 
    			global $USER_FIELD_MANAGER; 
    			$arUserFields = $USER_FIELD_MANAGER->GetUserFields("MY_SUBSCRIPTION", $ar["ID"]); 
    
    			if($arUserFields["UF_GENDER"]["VALUE"] == 1) 
    				$arFields["BODY"] = str_replace("#GENDER_HELLO#", "Уважаемая", $arFields["BODY"]); 
    			elseif($arUserFields["UF_GENDER"]["VALUE"] == 2) 
    				$arFields["BODY"] = str_replace("#GENDER_HELLO#", "Уважаемый", $arFields["BODY"]); 
    			else 
    				$arFields["BODY"] = str_replace("#GENDER_HELLO#", "", $arFields["BODY"]); 
    		}
    		else 
    		{ 
    			$arFields["BODY"] = str_replace("#GENDER_HELLO#", "", $arFields["BODY"]); 
    		} 
    		return $arFields; 
    	} 
    } 
    ?>
    • Расширение пользовательских типов для пользовательских полей главного модуля. (блог)
    • Как прикрутить пользовательские свойства к своему модулю (блог)
    • Добавление типов данных «Связь с элементом» и «Связь с элементом в виде списка» (блог)

    Добавление, редактирование, удаление пользовательских свойств и их значений

    За работу с пользовательскими полями в D7 отвечает класс UserField, в старом ядре: CUserTypeEntity.

    Пример добавления пользовательского свойства типа Строка

    /**
     * Добавление пользовательского свойства
     */
    $oUserTypeEntity    = new CUserTypeEntity();
     
    $aUserFields    = array(
    /*
    *  Идентификатор сущности, к которой будет привязано свойство.
    * Для секция формат следующий - IBLOCK_{IBLOCK_ID}_SECTION
    */
        'ENTITY_ID'         => 'IBLOCK_3_SECTION',
    /* Код поля. Всегда должно начинаться с UF_ */
        'FIELD_NAME'        => 'UF_DEV2DAY_FIELD',
        /* Указываем, что тип нового пользовательского свойства строка */
        'USER_TYPE_ID'      => 'string',
    /*
    * XML_ID пользовательского свойства.
    * Используется при выгрузке в качестве названия поля
    */
    'XML_ID'            => 'XML_ID_DEV2DAY_FIELD',
    /* Сортировка */
    'SORT'              => 500,
    /* Является поле множественным или нет */
    'MULTIPLE'          => 'N',
    /* Обязательное или нет свойство */
        'MANDATORY'         => 'N',
    /*
    * Показывать в фильтре списка. Возможные значения:
    * не показывать = N, точное совпадение = I,
    * поиск по маске = E, поиск по подстроке = S
    */
        'SHOW_FILTER'       => 'N',
    /*
    * Не показывать в списке. Если передать какое-либо значение,
    * то будет считаться, что флаг выставлен.
    */
        'SHOW_IN_LIST'      => '',
    /*
    * Пустая строка разрешает редактирование.
    * Если передать какое-либо значение, то будет считаться,
    * что флаг выставлен.
    
    */
        'EDIT_IN_LIST'      => '',
        /* Значения поля участвуют в поиске */
        'IS_SEARCHABLE'     => 'N',
        /*
    * Дополнительные настройки поля (зависят от типа).
    * В нашем случае для типа string
    */
        'SETTINGS'          => array(
            /* Значение по умолчанию */
            'DEFAULT_VALUE' => '',
            /* Размер поля ввода для отображения */
            'SIZE'          => '20',
    /* Количество строчек поля ввода */
            'ROWS'          => '1',
            /* Минимальная длина строки (0 - не проверять) */
            'MIN_LENGTH'    => '0',
    /* Максимальная длина строки (0 - не проверять) */
            'MAX_LENGTH'    => '0',
            /* Регулярное выражение для проверки */
            'REGEXP'        => '',
    ),
    /* Подпись в форме редактирования */
        'EDIT_FORM_LABEL'   => array(
            'ru'    => 'Пользовательское свойство',
            'en'    => 'User field',
        ),
    /* Заголовок в списке */
        'LIST_COLUMN_LABEL' => array(
            'ru'    => 'Пользовательское свойство',
            'en'    => 'User field',
        ),
    /* Подпись фильтра в списке */
        'LIST_FILTER_LABEL' => array(
            'ru'    => 'Пользовательское свойство',
    'en'    => 'User field',
    ),
    /* Сообщение об ошибке (не обязательное) */
        'ERROR_MESSAGE'     => array(
            'ru'    => 'Ошибка при заполнении пользовательского свойства',
            'en'    => 'An error in completing the user field',
    ),
    /* Помощь */
        'HELP_MESSAGE'      => array(
            'ru'    => '',
    'en'    => '',
        ),
    );
     
    $iUserFieldId   = $oUserTypeEntity->Add( $aUserFields ); // int
    
    

    При удачном добавлении свойства в переменную $iUserFieldId будет возвращен идентификатор нового пользовательского свойства.

    Для создания пользовательских полей других типов замените значение USER_TYPE_ID:

    • enumeration — Список
    • double — Число
    • integer — Целое число
    • boolean — Да/Нет
    • string — Строка
    • file — Файл
    • video — Видео
    • datetime — Дата/Время
    • iblock_section — Привязка к разделам инф. блоков
    • iblock_element — Привязка к элементам инф. блоков
    • string_formatted — Шаблон
    • crm — Привязка к элементам CRM
    • crm_status — Привязка к справочникам CRM

    Пример добавления значений в пользовательские поля типа Список.

    $obEnum = new CUserFieldEnum();
    $obEnum->SetEnumValues($newID, $arAddEnum);
    
    //мы передаем массив, который состоит из таких элементов (наличие n в ключе обязательно)
    $arAddEnum['n'.$i] = array(
          'XML_ID' => $key,//xml_id
          'VALUE' => $value,//значение
          'DEF' => 'N',//по умолчанию
          'SORT' => $i*10//сортировка
       );

    Обновление пользовательского свойства

    При обновлении пользовательского свойства накладываются ограничения на изменение его типа (USER_TYPE_ID), объекта привязки (ENTITY_ID), кода поля (FIELD_NAME). Это связано с возможными ошибками связей значений и сущностей. Если необходимо изменить одно из этих полей, то нужно сначала создать новое пользовательское свойство, привязать все значения к нему, а затем удалить старое свойство.

    Пример обновления пользовательского свойства:

    $oUserTypeEntity->Update( $iUserFieldId, array(
        'MANDATORY' => 'Y',
    ) ); // boolean;

    В примере установлено требование обязательности заполнения поля.

    Удаление пользовательского поля

    Необходимо передать идентификатор пользовательского поля:

    	
    $oUserTypeEntity->Delete( $iUserFieldId );   // CDBResult

    Добавление и обновление значений пользовательских полей

    Добавление и обновление реализовано также через класс CUserTypeManager и метод Update.

    global $USER_FIELD_MANAGER;
     
    $aSection   = CIBlockSection::GetList( array(), array(
        'IBLOCK_CODE'   => 'shop_news',
        'CODE'          => 'test_section',
    ) )->Fetch();
     
    if( !$aSection ) {
        throw new Exception( 'Секция не найдена' );
    }
     
    $USER_FIELD_MANAGER->Update( 'IBLOCK_3_SECTION', $aSection['ID'], array(
        'UF_DEV2DAY_FIELD'  => 'updated value'
    ) ); // boolean

    В случае успешного обновления метод вернет true.

    Добавить пользовательское свойство к разделу инфоблока

    Если необходимо добавить пользовательское свойство к разделу инфоблока через механизм Битрикс-API, нужно использовать следующий код с вариациями:

    $arFields = Array(
    "ENTITY_ID" => "IBLOCK_2_SECTION",
    "FIELD_NAME" => "UF_TITLE",
    "USER_TYPE_ID" => "string",
    "EDIT_FORM_LABEL" => Array("ru"=>"заголовок", "en"=>"title")
    );
    $obUserField  = new CUserTypeEntity;
    $obUserField->Add($arFields);

    Поля к нештатным объектам и новые объекты

    Создание пользовательского поля к нештатным объектам

    Иногда возникает необходимость создавать пользовательские поля к объектам, у которых нет поддержки пользовательских полей по умолчанию. В таком случае, можно самостоятельно создать [dw]пользовательское свойство для этого объекта[/dw][di]Помните при этом, что в методах GetList поддерживаются только системные объекты.[/di].

    Рассмотрим это на примере комментариев блога. Например, у каждого комментария необходимо создавать свойство Рейтинг.
    Создаем в административной части пользовательское свойство нужного типа (Настройки > Настройки продукта > Пользовательские поля). Заполняем все поля, в поле Объект указываем любое придуманное имя объекта, главное, чтобы оно было уникально. В нашем случае, напишем BLOG_RATING.
    Для считывания и записи значений пользовательских свойств можно использовать следующие функции:

    function SetUserField ($entity_id, $value_id, $uf_id, $uf_value) //запись значения
    {
    return $GLOBALS["USER_FIELD_MANAGER"]->Update ($entity_id, $value_id,
    Array ($uf_id => $uf_value));
    }
     
    function GetUserField ($entity_id, $value_id, $uf_id) //считывание значения
    {
    $arUF = $GLOBALS["USER_FIELD_MANAGER"]->GetUserFields ($entity_id, $value_id);
    return $arUF[$uf_id]["VALUE"];
    }
     
    // $entity_id - имя объекта (у нас "BLOG_RATING")
    // $value_id - идентификатор элемента (вероятно, ID элемента, свойство которого мы сохраняем или получаем. в нашем случае, это ID комментария)
    // $uf_id - имя пользовательского свойства (в нашем случае UF_RATING)
    // $uf_value - значение, которое сохраняем

    Пример использования:

    SetUserField ("BLOG_RATING", $CommentID, "UF_RATING", $Rating);
    echo "Рейтинг комментария: ".GetUserField ("BLOG_RATING", $CommentID, "UF_RATING");

    Создание пользовательских полей вручную не так удобно, как использование функций GetList для объектов с поддержкой пользовательских свойств по умолчанию. Однако, он позволяет максимально быстро и просто использовать в самописных компонентах и модулях пользовательские свойства для произвольных объектов.

    Создание собственного объекта

    Можно создать любой объект и работать с ним как вам удобно. Пример:

    $GLOBALS["USER_FIELD_MANAGER"]->Update("GRADEBOOK_RESULT", $ID, Array("UF_TEACHERS"=>$arValue));
    $arUserFields = $GLOBALS["USER_FIELD_MANAGER"]->GetUserFields("GRADEBOOK_RESULT", $ID);

    Тестирование проектов

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

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

    В процессе тестирования проверяется много важных мелочей, которые повторяются из проекта в проект. Программисты часто говорят, что помнят их — на самом деле опыт показывает, что не помнят, пропускают важные моменты и занимаются «наступанием на грабли». Не зря опытные руководители проектов закрывают этапы/релизы только при наличии оформленных ответственными сотрудниками чеклистов-отчетов: начиная от верстальщиков, заканчивая системными администраторами.

    Чеклисты обычно составляются опытными сотрудниками. По мере накопления интеллектуального багажа компании в базах знаний, развиваются и совершенствуются и чеклисты. Суть проста — сэкономить время и деньги, не позволив сотрудникам непроизвольно (а иногда и произвольно) наступать на грабли 2 и более раз.

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

    • Поиск. Если на сайте используется форма поиска, то её следует протестировать в первую очередь. Обязательно надо проверить, чтобы ссылки в результатах не были битые. (Битые ссылки присутствуют в анонсах, в подробных описаниях, просто в статическом контенте, в настройках инфоблоков.) Если на сайте используется несколько инфоблоков с динамической информацией, то нужно сделать поисковые запросы, чтобы найти по отдельности элементы различных инфоблоков и проверить, корректно ли работают ссылки в результатах поиска.
    • F5. Во всех формах, в которых пользователь может оставлять какие-либо данные (например, комментарии), нужно проверить, не отправляются ли данные снова при нажатии кнопки F5 в браузере.
    • Пользователи и права доступа. Следует протестировать сайт в трёх состояниях: неавторизованным, авторизованным и авторизованным под администратором. Часто бывает, что создавая инфоблок из-под администратора, ему забывают поставить нужные права доступа, и он остается доступным только для администраторов.
    • Карта сайта Не всегда для этой страницы подойдёт стандартный компонент Карта сайта. Если основная информация на сайте – каталог товаров (сделанный, естественно, с помощью модуля инфоблоков), то в карте сайта логично вывести разделы каталога, а не только разделы сайта.
    • Дефолтные шаблоны. Необходимо проверить, не используются ли где-нибудь на сайте дефолтные шаблоны. Может получиться не очень хорошо, если, например, пользователь пытается восстановить пароль, ему на почту уходит письмо со ссылкой на сайт, он нажимает на ссылку и видит не привычный ему дизайн сайта, а пример дефолтного «корпоративного» сайта «1С-Битрикс: Управление сайтом».
    • Стили в визуальном редакторе. Хорошо, когда контент-менеджер, пользуясь визуальным редактором, видит в нём текст, максимально похожий на текст, который отобразится на сайте. То есть для абзацев, заголовков и т.д. свой кегль шрифта, цвет и т.д.
    • Динамическая информация во включаемых областях Если во включаемой области содержится какая-либо динамическая информация, то она может закешироваться, что скорее всего приведет к нежелательному результату. Лучше вообще не использовать динамическую информацию во включаемой области.
    • Резервная копия, обновления. Необходимо обновить Битрикс до актуальной версии, сделать резервную копию сайта и сохранить её на локальный компьютер.
    • Кеш. Следует проверить, включено ли автокеширование. Если оно не включено, то нужно его включить и повторить всё тестирование с самого начала
    • Права доступа. Проверка на корректность прав доступа различных групп пользователей, генерация отчета по правам групп в папках сайта.
    • Ядро системы. Проверка работы ядра и соответствие его оригиналу с генерацией отчета о папках компонентах и модулях не соответствующих оригиналу и генерации отчета о несоответствии версий. Иногда шаловливые ручки позволяют себе править непосредственно компоненты Bitrix Framework и, чтобы не порушить функционал сайта, очень полезно перед тем как обновлять проект узнать что наделано не так и что наделано дополнительно.
    • Неиспользуемые файлы. Часто при разработке или изменениях делают резервные копии и хорошо если делают приставку old а иногда просто весь этот мусор остается, плюс неиспользуемые картинки и т.п.
    • Сторонние подключения. Часто к сайту подключают различные партнерские программы: баннеры, скрипты и т.п., вещи без которых невозможна монетизация. Но при этом может пострадать безопасность. Необходимо проверять подобный функционал на предмет безопасности и наличия уязвимостей (доступ на редактирование, доступ к ядру, возможность внедрения кода, сохранения через подключаемый модуль файлов)

    Из опыта веб-разработчиков.

    Роман Петров: Один из простых способов протестировать на ошибки разработки ваш сайт на «1С-Битрикс: Управление сайтом» — это перед запуском проекта, включить тест производительности на нужное время и на том же сервере запустить 3-4 одновременных команды wget на зеркалирование сайта. Первое обращение создаст кэш страницы, а остальные — обратятся уже к закэшированным файлам. Это позволит очень легко выявить все проблемные странички (мелкие ошибки «по забывчивости», ошибки верстки (пропущенные файлы и др.)) и исправить ошибки.

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

    Монитор качества

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

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

    Монитор качества — инструмент для проверки качества выполненного проекта перед сдачей его заказчику.

    Монитор качества это:

    • Структурированная методика управления качеством внедрения;
    • Система тестов для веб-разработчиков, набор рекомендаций для клиентов;
    • Состоит из 26 обязательных тестов и 40 необязательных;
    • Включает 14 автоматических проверок.

    Монитор качества дает дополнительные возможности разработчикам и клиентам:

      Разработчикам:

    • Систематизация процедуры тестирования;
    • Повышение качества создания интернет-проектов за счет систематизации производства;
    • Формализация отношений с клиентом как на этапе сдачи, так и на этапе поддержки.
      Клиентам:

    • Снижение рисков: чем раньше найдена проблема, тем дешевле ее устранить;
    • Систематизация приемки проекта и запуска его в эксплуатацию: шаги расписаны, можно сосредоточиться на деталях;
    • Формализация и упрощение взаимодействия с разработчиком на этапе поддержки и развития проекта;
    • Снижение затрат на получение качественного результата;
    • Высокая производительность и безопасность веб-решения.

    Тесты представлены в виде дерева, организованного согласно этапам типичного внедрения. Однако можно выполнять тесты в любом удобном порядке. Тесты состоят из обязательных и необязательных тестов. Некоторые тесты могут быть автоматизированными. Автоматизированы сложные и рутинные проверки.

    Обязательный тест — критичный тест для качества веб-решения. Может быть пропущен при условии наличия комментария разработчика. В общем списке помечаются черным цветом.

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

    Для сдачи проекта по чеклисту необходимо добиться успешного прохождения обязательных тестов. Необязательные тесты – призваны существенно улучшить качество решения и снизить риски.

    Автоматизированный тест — тест, данные по которому собирает система. Конечное решение в любом случае за разработчиком. Автоматизированный тест можно запустить повторно.

    Сдача проекта

    Сдача проекта

    Сдача проекта производится на странице Монитор качества (Настройки > Инструменты > Монитор качества). При первом открытии отобразится страница с вводной информацией. Запуск теста произойдёт после нажатия на кнопку , после чего откроется дерево тестов с предложением запуска автотестирования. Автотестирование можно отложить и запустить его позже.

    Тесты

    Автоматизированный тест

    После запуска автоматизированого теста система соберет данные и предложит отобразить тесты, которые по ее мнению пройдены, а какие — нет:

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

    Ручной тест

    Необходимо вручную открыть непройденные или ручные тесты и вручную перевести их в тот или иной статус и дать, при необходимости описание.

    Прохождение тестов

    Тест открывается кликом по его названию. Форма автоматического теста (в форме ручного теста отсутствует кнопка Запустить автотест):

    Если тест не пройден, то по ссылке Подробный отчет можно просмотреть причины непрохождения:

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

    После исправления ошибок можно:

    • запустить повторно автоматический тест (для автоматизированных тестов);
    • вручную сменить статус теста.

    Примечание: ручной перевод обязательного теста в статус Пропущен требует обязательного добавления комментария.

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

    Архив тестов

    Отчеты можно просмотреть в любое время, получив подробную информацию по каждому тесту (в том числе и системные сообщения автотестов).

    Примечание: Отчёт можно послать в компанию «1С-Битрикс» для участия в программе монитора качества. Для этого используйте ссылку Направить в 1С-Битрикс и заполните открывшуюся форму.

    Способы использования

    Монитор качества можно использовать в нескольких вариантах.

    Базовое тестирование по чеклисту

    Партнер/Разработчик организует тестирование выполненной интеграции по чеклисту и выступает в роли тестировщика. Затем предъявляет клиенту успешно сданный отчет, доступный в административном интерфейсе в разделе Настройки > Инструменты > Контроль качества – в котором все обязательные тесты успешно пройдены.

    Углубленное тестирование по чеклисту

    Разработчик и клиент договариваются о необходимости углубленного тестирования по чеклисту качества высоконагруженного проекта. Партнер выступает в роли тестировщика и добивается успешного прохождения всех (большинства) тестов чеклиста + своих тестов. Клиент обращает внимание на число доступных тестов и число пройденных успешно, просматривая отчет по тестированию в архиве отчетов.

    Внутренняя разработка

    Клиент силами собственной команды разработки осуществляет интеграцию решения. В роли тестировщика выступает команда тестирования клиента. Одно структурное подразделение клиента сдает проект другому подразделению — работу координирует менеджер проекта.

    Итерационное развитие с минимальными рисками

    Партнер оказывает клиенту услугу по доработке функционала действующего веб-проекта, первоначальная интеграция которого была выполнена с использованием Монитора качества. Партнер сдает работу, формируя отчет по тестированию. Клиент проверяет отчет по тестированию и следит, чтобы все доработки веб-проекта регистрировались в архиве отчетов.

    Модификация тестов

    Система допускает модификацию тестов под нужды разработчика используя штатный механизм Событий Bitrix Framework.

    Разработчики, при необходимости, могут сами добавить свои тесты и разделы в Монитор качества.

    Так же инструмент можно адаптировать под нужды конкретной задачи, создав собственные разделы и тесты. Например:

    • Тесты по SEO-оптимизации;
    • Тест на CodeStyle;
    • Тест на корректность работы биллинга под нагрузкой;
    • и другие.

    Как дополнить штатные тесты

    Сначала нам необходимо описать свои тесты и их разделы. Для этого вешаем обработчик на событие Главного модуля onCheckListGet. Событие вызывается в конструкторе CCheckList с аргументом $arCheckList следующего вида:

    array(2) {
      ["CATEGORIES"]=>
      array(10) {
        ["QDESIGN"]=>
        array(0) {
        }
        ["DESIGN"]=>
        array(1) {
          ["PARENT"]=>
          string(7) "QDESIGN"
        }
        ["MODEL"]=>
        array(1) {
          ["PARENT"]=>
          string(7) "QDESIGN"
        }
        ["STANDART"]=>
        array(1) {
          ["PARENT"]=>
          string(7) "QDESIGN"
        }
      }
      ["POINTS"]=>
      array(65) {
        ["QD0010"]=>
        array(2) {
          ["PARENT"]=>
          string(6) "DESIGN"
          ["REQUIRE"]=>
          string(1) "Y"
        }
        ["QD0020"]=>
        array(5) {
          ["REQUIRE"]=>
          string(1) "Y"
          ["PARENT"]=>
          string(6) "DESIGN"
          ["AUTO"]=>
          string(1) "Y"
          ["CLASS_NAME"]=>
          string(10) "CAutoCheck"
          ["METHOD_NAME"]=>
          string(14) "CheckTemplates"
        }
    )

    Нетрудно заметить, что CATEGORIES содержит список разделов чеклиста, которые могут быть вложенными, а POINTS — сами тесты.

    Ключ массива CATEGORIES — ID раздела, значение — массив параметров:

    • NAME — наименование раздела;
    • LINKS
    • PARENT — родительский раздел.

    Пример описания раздела:

    $checkList['CATEGORIES']['ITC_QC'] = array(
        'NAME' => 'Корпоративный тест качества ITConstruct',
        'LINKS' => ''
    );

    Ключом элементов POINTS является символьный идентификатор теста, а значение представляет собой массив следующих параметров:

    • NAME — наименование теста;
    • DESC — краткое описание теста (вкладка Рекомендации, блок Описание);
    • HOWTO — длинный текст о том, что будет проверяться (вкладка Рекомендации, блок Как тестировать);
    • LINKS — аналогично разделам;
    • PARENT — ID раздела, обязательное;
    • REQUIRE — флаг обязательности теста (Y/N);
    • AUTO — «Y», если является автотестом;
    • CLASS_NAME — имя класса теста (для автотеста);
    • METHOD_NAME — имя метода теста (для автотеста);
    • FILE_PATH — подключение файла теста, если он вынесен в отдельный скрипт (для автотеста). Путь — относительно DOCUMENT_ROOT;
    • PARAMS — массив дополнительных параметров, передаваемых первым аргументом при вызове метода автотеста.

    Примечание: Такие ключи как NAME, DESC и LINKS можно не определять в массиве описания теста, они являются языкозависимыми и жесткое определение их прямо в обработчике лишает возможности локализации.

    Достаточно подключить свой языковой файл примерно с таким содержанием:

    $MESS["CL_ITC_QC_FAVICON_NAME"] = 'Наличие favicon'; 
    $MESS["CL_ITC_QC_FAVICON_DESC"] = 'Проверка наличия favicon - иконки сайта'; 
    $MESS["CL_ITC_QC_FAVICON_LINKS"] ='блаблабла';

    И эти языковые фразы будут подтягиваться в поля NAME, DESC и LINKS теста с кодом ITC_QC_FAVICON.
    К HOWTO это тоже будет относиться, но пока его можно определить только в массиве описания пункта.

    Пример:

    $checkList['POINTS']['ITC_QC_FAVICON'] = array(
        'PARENT' => 'ITC_QC',
        'REQUIRE' => 'Y',
        'AUTO' => 'Y',
        'CLASS_NAME' => __CLASS__,
        'METHOD_NAME' => 'checkFavicon',
        'NAME' => 'Наличие favicon',
        'DESC' => 'Проверка наличия favicon - иконки сайта, отображаемой в заголовке вкладки и поисковых системах',
        'HOWTO' => 'Производится проверка главной страницы сайта на наличие соответствующего мета-тэга. 
                    Если тэг объявлен - проверяется наличие иконки по указанному урлу. 
                    Если не указан - наличие favicon.ico в корне сайта',
        'LINKS' => 'links'
    );

    Обработчик события может возвращать как изменённый $arCheckList, так и новый массив с разделами и тестами — если категория/тест с неким ID уже существует, то он не заменится, т.е. подкорректировать системные тесты не получится.

    Теперь необходимо объявить метод для автотеста. Для альфа-версии логика теста будет тривиальна:

    $check = file_exists($_SERVER['DOCUMENT_ROOT'] . '/favicon.ico')

    По результатам проверки мы должны вернуть массив. Автотест может быть одно- или многошаговым. Если тест многошаговый и текущая итерация не является конечной, необходимо вернуть массив следующего вида:

    $arResult = array(
        'IN_PROGRESS' => 'Y',
        'PERCENT' => '42',
    );

    PERCENT служит лишь для визуализации прогресса на странице и никуда не сохраняется для последующего использования. Промежуточные данные для идентификации прогресса шага надо сохранять самим — в сессию, временный файл, базу (в зависимости от объёма данных и прочих условий).

    Если же тест закончен, сообщаем статус массивом, содержащим следующие ключи:

    • STATUS— результат теста. true, если успешно, нечто иное, если тест провалился. В коде проверяется так:

      if ($result['STATUS'] == "true")

    • MESSAGE — разъяснения результата:
      • PREVIEW — краткий текст результата;
      • DETAIL — расширенное объяснение, открываемое во всплывающем окне.

    Готовый метод теста фавиконки имеет вид:

    static public function checkFavicon($arParams)
    {
        $arResult = array('STATUS' => 'F');
        $check = file_exists($_SERVER['DOCUMENT_ROOT'] . '/favicon.ico');
    
        if ($check === true) {
            $arResult = array(
                'STATUS' => true,
                'MESSAGE' => array(
                    'PREVIEW' => 'Favicon найдена - ' . '/favicon.ico',
                ),
            );
        } else {
            $arResult = array(
                'STATUS' => false,
                'MESSAGE' => array(
                    'PREVIEW' => 'Favicon не найдена',
                    'DETAIL' => 'Тест очень старался, но так и не смог найти фавыконку. Ну и чёрт с ней',
                ),
            );
        }
    
        return $arResult;
    }

    Результат

    Тест в мониторе качества:

    Справочная информация о тесте:

    Код

    Код обработчика и тесты целиком:

    AddEventHandler('main', 'OnCheckListGet', array('CItcCheckListTests', 'onCheckListGet'));
    
    class CItcCheckListTests
    {
        static public function onCheckListGet($arCheckList)
        {
            $checkList = array('CATEGORIES' => array(), 'POINTS' => array());
    
            $checkList['CATEGORIES']['ITC_QC'] = array(
                'NAME' => 'Корпоративный тест качества ITConstruct',
                'LINKS' => ''
            );
    
            $checkList['POINTS']['ITC_QC_FAVICON'] = array(
                'PARENT' => 'ITC_QC',
                'REQUIRE' => 'Y',
                'AUTO' => 'Y',
                'CLASS_NAME' => __CLASS__,
                'METHOD_NAME' => 'checkFavicon',
                'NAME' => 'Наличие favicon',
                'DESC' => 'Проверка наличия favicon - иконки сайта, отображаемой в заголовке вкладки и поисковых системах',
                'HOWTO' => 'Производится проверка главной страницы сайта на наличие соответствующего мета-тэга. 
                            Если тэг объявлен - проверяется наличие иконки по указанному урлу. 
                            Если не указан - наличие favicon.ico в корне сайта',
                'LINKS' => 'links'
            );
    
            $checkList['POINTS']['ITC_QC_DENY_DEV'] = array(
                'PARENT' => 'ITC_QC',
                'REQUIRE' => 'N',
                'AUTO' => 'N',
                'NAME' => 'Закрытие доступа извне к dev-серверу',
                'DESC' => 'Согласовать с менеджером закрытие доступа ко внутреннему серверу разработок из внешнего мира',
                'HOWTO' => 'Попинговать с телефона после апдейта днса',
            );
    
            return $checkList;
        }
    
        static public function checkFavicon($arParams)
        {
            $arResult = array('STATUS' => 'F');
            $check = file_exists($_SERVER['DOCUMENT_ROOT'] . '/favicon.ico');
    
            if ($check === true) {
                $arResult = array(
                    'STATUS' => true,
                    'MESSAGE' => array(
                        'PREVIEW' => 'Favicon найдена - ' . '/favicon.ico',
                    ),
                );
            } else {
                $arResult = array(
                    'STATUS' => false,
                    'MESSAGE' => array(
                        'PREVIEW' => 'Favicon не найдена',
                        'DETAIL' => 'Тест очень старался, но так и не смог найти фавыконку. Ну и чёрт с ней',
                    ),
                );
            }
    
            return $arResult;
        }
    }

    Несколько советов

    Цитатник веб-разработчиков.

    Максим Месилов: Если на текущем проекте сбоит штатный функционал, то следует попробовать смоделировать аналогичную ситуацию в демо-лаборатории от 1С-Битрикс. Эта ссылка должна быть всегда под рукой.

    2 способа отладки веб-приложений

    var_dump – способ

    Самый простой вариант использование оператора var_dump(): получение состава переменной, даже будь это объект или массив. Если обернуть вывод этого оператора в <pre>, то будет удобочитаемо.

    FirePHP – способ

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

    Загрузите с этого сайта последнюю версию класса для работы с расширением FirePHP и подлючите этот класс к своему движку:

    1. Скопируйте файл fb.php в папку /bitrix/php_interface/
    2. Добавьте в файл /bitrix/php_inteface/init.php строку:
      require_once(‘FirePHPCore/fb.php’);

    Теперь можно использовать логгирование в консоль FireBug. В простейшем варианте это делается так: fb($var), если нужно поставить метку, то fb($var, ‘Label’);

    Видео

    Смотреть

    Senior, выше некуда

    Senior — высший уровень квалификации. Программист этого уровня должен уметь:

    • Читать исходный код.
    • Уметь работать с Базой данных.
    • Разбираться в вопросах производительности и безопасности кода.
    • Знать как можно кастомизировать административный раздел Bitrix Framework.
    • Уметь создавать новые компоненты и модули.

    Производительность

    Производительность сайта — комплексное явление. На скорость открытия страниц влияют:

    1. ресурсы сервера;
    2. настройка серверного ПО;
    3. программная платформа (БУС);
    4. прикладная разработка на платформе.

    Провал производительности может быть на одном или более этапов. Прикладная разработка имеет очень большое значение в этом списке, хотя и не единственное. «Кривые руки» могут свести на нет преимущество любой, самой совершенной платформы и «подвесить» любое, самое мощное «железо».

    Цитатник веб-разработчиков.

    Степан Овчинников: Как всегда: универсальность и функциональность против простоты и производительности.

    Особенности веб-программирования

    Одно из отличий веб-программирования от Windows программирования (однопользовательского) в том, что в Windows вам принадлежат все ресурсы компьютера, и почти неограниченное время. В вебе вам принадлежит лишь совсем чуть-чуть памяти и немного времени (обычно не более 30-90 секунд). Поэтому сложные и долгие расчеты – не для классических сайтов.

    Основная задача веб-сайта (в 99% случаев) – это быстро вернуть посетителю запрошенную страничку и освободить ресурсы для обслуживания других посетителей. Именно для решения этой задачи существуют такие инструменты, как NGINX, lighthttpd, кэширование, PHP-акселераторы и другие. Для большинства сайтов с небольшой посещаемостью таких инструментов, имеющихся в наличии на практически каждом хостинге или почти в любой CMS, вполне достаточно.

    Однако для более посещаемых проектов возникает необходимость дополнительной оптимизации.

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

    Сложным ресурсоемким проектом можно назвать проект, под 30 000 посещений, с количеством элементов в базе под 500 000 и больше, более 40 типов инфоблоков, самих инфоблоков больше 200. В ключевых инфоблоках по 150-200 свойств.

    Что позволяет работать таким проектам:

    • Инфоблоки выборки из которых можно делать на стандартном API;
    • Кэширование.

    Нюансы, на которые надо обращать внимание:

    • При большом количестве свойств возникают проблемы, могут происходить и сбои: проблемы с изменением свойства элемента из административной части, может не выполняться фильтр по значению.
    • Немного неудобно работать в административной части с таким большим кол-вом данных.

    Самое главное это — проектирование инфоблоков. Также нужно быть внимательными с обработчиками по работе с инфоблоками, и с агентами. И при проектировании необходимо знать и учитывать особенности API. Например, выбирать элементы по IBLOCK_CODE или IBLOCK_TYPE хуже, чем по IBLOCK_ID. Это конкретный пример, таких вариаций очень много.

    Сложные и объемные проекты лучше реализовывать на собственных серверах. При этом рекомендуется использовать 2 сервера: отдельно для базы и для статики и кэша.

    Опыт разработчиков проектов на Bitrix Framework показывает, что около 1 миллиона хитов на проекте – это реальный показатель, если уметь конфигурировать MySQL.

    Есть и своеобразная «вилка»: база данных против надежности жесткого диска. Не кешировать данные — хуже для базы и лучше для диска. То есть если используется очень мощный сервер под БД, и он хорошо сконфигурирован, то это может быть реальной альтернативой файловому кешу.

    Примечание: Для снижения нагрузки к базе данных и удобства работы с системой в Bitrix Framework используется хранение разнообразных данных в файловой системе. Подробнее про файлы и их влияние на производительность можно посмотреть в уроке Структура файлов.

    Ссылки по теме:

    • Оптимизация веб-проектов (Группа по вопросам производительности веб-проектов)
    • Производительность сайта в картинках (Блог)
    • График времени генерации страниц сайта (Блог)
    • Оптимизация времени генерации страницы (Блог)
    • Количество запросов и время выполнения функции (Сообщество разработчиков)
    • MySQL, InnoDB, Монитор производительности (Оптимизация работы MySQL)
    • Семинар «Битрикс Хайлоад» — как это было, презентации, видео (habrahabr.ru)

    Кеширование при проектировании сайта

    Кеширование

    Цитатник веб-разработчиков.

    Максим Месилов: В 1С-Битрикс есть производительная система кеширования. Она используется как в стандартных компонентах системы, так может быть применена и при самостоятельной разработке. Основная её задача — снизить время отклика сайта и повысить его устойчивость при нагрузках.

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

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

    Примечание: Возможность использования кеширования в проекте лучше заранее предусмотреть в ТЗ т.к. его внедрение после запуска проекта обходится значительно дороже и сложнее.

    Для реализации кеширования в системе созданы два класса:

    • CPageCache — для кеширования HTML
    • CPHPCache — для кеширования HTML и PHP переменных

    Результаты кеширования сохраняются в виде файлов в каталоге /bitrix/cache/. Если время кеширования не истекло, то вместо ресурсоемкого кода будет подключен предварительно созданный файл кеша.

    Правильное использование кеширования позволяет увеличить общую производительность сайта на порядок. Однако необходимо учитывать, что неразумное использование кеширования может привести к серьезному увеличению размера каталога /bitrix/cache/.

    Пример кеширования HTML

    <?
    // создаем объект
    $obCache = new CPageCache; 
    
    // время кеширования - 30 минут
    $life_time = 30*60; 
    
    // формируем идентификатор кеша в зависимости от всех параметров 
    // которые могут повлиять на результирующий HTML
    $cache_id = $ELEMENT_ID.$IBLOCK_TYPE.$USER->GetUserGroupString(); 
    
    // инициализируем буферизирование вывода
    if($obCache->StartDataCache($life_time, $cache_id, "/")):
    
        // выбираем из базы параметры элемента инфоблока
        if($arIBlockElement = GetIBlockElement($ELEMENT_ID, $IBLOCK_TYPE)):
            echo "<pre>"; print_r($arIBlockElement); echo "</pre>";
        endif;
    
        // записываем предварительно буферизированный вывод в файл кеша
        $obCache->EndDataCache(); 
    
    endif;
    ?>

    В данном примере, метод CPageCache::StartDataCache проверит наличие неистекшего и валидного файла кеша. Если такой файл существует, то он будет подключен и выведен на экран, в противном случае будет включена буферизация. Результаты буферизации будут записаны в файл кеша методом CPageCache::EndDataCache.

    В первом параметре метода CPageCache::StartDataCache задается интервал времени в секундах, в течение которого файл кеша будет считаться валидным и не истекшим (интервал времени отсчитывается с момента создания файла кеша).

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

    Например:

    • Если на результат выполнения кешируемого кода может повлиять авторизация текущего пользователя, то к идентификатору необходимо добавить результат выполнения функции CUser::GetUserGroupString, возвращающей строку c ID групп текущего пользователя.
    • Если в кешируемом коде вы используете постраничную навигацию, то в данный идентификатор обязательно нужно включить результат работы функции CDBResult::NavStringForCache.
    • При использовании сортировки необходимо также помнить, что в идентификатор необходимо включить значения переменных хранящих в себе поле, по которому идет сортировка и порядок сортировки (например, $by.$sort).

    Пример кеширования HTML и PHP переменных

    <?
    // создаем объект
    $obCache = new CPHPCache; 
    
    // время кеширования - 30 минут
    $life_time = 30*60; 
    
    // формируем идентификатор кеша в зависимости от всех параметров 
    // которые могут повлиять на результирующий HTML
    $cache_id = $ELEMENT_ID.$SECTION_ID.$USER->GetUserGroupString(); 
    
    // если кеш есть и он ещё не истек то
    if($obCache->InitCache($life_time, $cache_id, "/")):
        // получаем закешированные переменные
        $vars = $obCache->GetVars();
        $SECTION_TITLE = $vars["SECTION_TITLE"];
    else :
        // иначе обращаемся к базе
        $arSection = GetIBlockSection($SECTION_ID);
        $SECTION_TITLE = $arSection["NAME"];
    endif;
    
    // добавляем пункт меню в навигационную цепочку
    $APPLICATION->AddChainItem($SECTION_TITLE, $SECTION_URL."SECTION_ID=".$SECTION_ID);
    
    // начинаем буферизирование вывода
    if($obCache->StartDataCache()):
    
        // выбираем из базы параметры элемента инфоблока
        if($arIBlockElement = GetIBlockElement($ELEMENT_ID, $IBLOCK_TYPE)):
            echo "<pre>"; print_r($arIBlockElement); echo "</pre>";
        endif;
    
        // записываем предварительно буферизированный вывод в файл кеша
        // вместе с дополнительной переменной
        $obCache->EndDataCache(array(
            "SECTION_TITLE" => $SECTION_TITLE
            )); 
    endif;
    ?>

    Отличие данного примера от предыдущего заключается в том, что помимо HTML также кешируется PHP переменная $SECTION_TITLE. Структура и тип кешируемых PHP переменных могут быть произвольными. Метод CPHPCache::InitCache выполняет проверку на наличие неистекшего и валидного файла кеша. Если данный файл найден, то происходит его подключение, при этом все закешированные переменные будут доступны после использования метода CPHPCache::GetVars. Метод CPHPCache::EndDataCache, помимо буферизированного HTML кода, записывает в файл кеша также PHP переменные.

    Для отключения кеширования на одной странице, необходимо авторизоваться с административными правами и вызвать эту страницу с параметром &clear_cache=Y. Если, авторизовавшись с административными правами, вызвать любую страницу с параметром &clear_cache_session=Y, то кеш будет отключен для всех страниц, которые будут просмотрены в рамках сессии. Файлы кеша можно удалить в административной части на закладке Очистка файлов кеша страницы Настройки > Настройки продукта > Автокеширование.

    Смотрите также:

    • Класс CPageCache
    • Класс CPHPCache

    Альтернативные способы хранения кеша

    Мы убедились, что использовать технологию кеширования для веб-сайта нужно, значит необходимо предусмотреть кеширование ещё на стадии написания ТЗ.

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

    Однако, у нас, из-за специфики веб-сайта, скопился большой объем самих закешированных данных, использование которых (десятки тысяч файлов) вызывает «некоторые» неудобства — возросла нагрузка на дисковую подсистему. А также, к сожалению, при разработке вкрались ошибки и наши закешированные данные чистятся не полностью, поэтому постепенно их объем на диске становится все больше и больше…

    Решения есть:

    memcached

    При использовании memcached временные данные будут храниться в оперативной памяти. Можно выделить для хранения кеша недорогой сервер с несколькими гигабайтами памяти.

    При этом, устаревшие данные будут автоматически вытесняться и наш кеш перестанет «расползаться» по системе, пожирая все больше и больше места. Допустим, мы выделили в memcached 4GB места для кеширования и можем быть уверенными, что больше 4GB кеш не вырастет, а наименее часто используемые данные будут вытесняться (алгоритм LRU). Очень удобно и эффективно.

    Подключение memcached осуществляется в файле dbconn.php.

    Для эффективной работы системы настройки по умолчанию нужно изменить. Примеры:

    // Мемкеш с объемом памяти 2ГБ 8 потоками и работой через tcp
    PORT="11211"
    USER="memcached"
    MAXCONN="10240"
    CACHESIZE="2048"
    OPTIONS="-t 8"
    
    // настройки в dbconn.php
    define("BX_CACHE_TYPE", "memcache");
    define("BX_CACHE_SID", $_SERVER["DOCUMENT_ROOT"]."#01");
    define("BX_MEMCACHE_HOST", "127.0.0.1");
    define("BX_MEMCACHE_PORT", "11211");
    
    // Мемкеш с объемом памяти 2ГБ 8 потоками и работой через сокет
    PORT="11211"
    USER="bitrix"
    MAXCONN="10240"
    CACHESIZE="2048"
    OPTIONS="-t 8 -s /tmp/memcached.sock"
    
    // настройки в dbconn.php
    define("BX_CACHE_TYPE", "memcache");
    define("BX_CACHE_SID", $_SERVER["DOCUMENT_ROOT"]."#01");
    define("BX_MEMCACHE_HOST", "unix:///tmp/memcached.sock");
    define("BX_MEMCACHE_PORT", "0");
    

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

    Ссылки по теме:

    • memcached.org

    APC

    Подключение APC осуществляется в файле dbconn.php:

    // настройки в dbconn.php
    define("BX_CACHE_TYPE", "apc");
    define("BX_CACHE_SID", $_SERVER["DOCUMENT_ROOT"]."#01");
    

    Подробнее про установку и настройку APC смотрите в документации www.php.net/apc.

    Параметр cacheenginenone

    Очень часто встречающаяся проблема при работе с кешем — в закладке Хранение кеша страницы Настройки > Производительность > Панель производительности появляется параметр cacheenginenone, а кеширование при этом не производится. По сути данный параметр — это указание системы, что выбранный вами способ хранения кеша не работает. Можно сказать, что подходящего способа кеширования для системы нет, поэтому она и не будет выполнять его, как будто оно не включено вовсе.

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

    Смотрите также:

    • Инструкция по настройке кеширования (Документация для разработчиков)
    • Memcached (в составе веб-кластера) (Курс Администратор. Модули)

    Рекомендации по работе с кешированием

    • Оптимизировать проект так, чтобы без использования кеширования сложность и количество запросов было минимально. Например, для облегчения запросов, возможно, потребуется создание специфических для проекта индексов. Также, в некоторых случаях, будет эффективнее разбить один сложный запрос на несколько более простых и легких. Это снизит общую нагрузку к базе и позволит более эффективно использовать кеширование.

      Цитатник веб-разработчиков.

      Юналиев Рамиль: Если разработчик делает 1000 запросов и говорит «и так сойдет, оно же кешируется», то это конечно неправильно, любым инструментом нужно уметь пользоваться.

      Цитатник веб-разработчиков.

      Сербул Александр: Иногда во время разработки активно используются технологии кеширования, однако при возрастании нагрузки база данных подвергается перегрузкам. Почему? Для предупреждения данного риска необходимо сначала обеспечить наиболее оптимальную работу с базой данных с выключенным кешированием, прежде чем его включать (рекомендую менеджерам интернет-проектов взять это на заметку и включить в чеклист контроля качества проекта).
      Типичный кейс данной «ловушки» такой — при включенном кешировании кастомизированное меню использует 0 SQL-запросов, при выключенном — 5000!

    • Подобрать оптимальное время кеширования с направлением в большую сторону. Малое время заставляет обновляться кеш чаше, для больших объемов кешируемых данных это будет создавать дополнительную (лишнюю) нагрузку на сервер, особенно если данные не меняются.

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

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

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

      Пример создания кеша для новостей, когда срок активности новости скоро истекает, и кеш достаточно будет создать на весь день всего один раз:

      // Не правильный ИД, так как в данном случае на каждый хит будет создан новый кеш.
      // (При условии что нам надо выбрать все элементы которые на данный день активны.)
      
      $arFilter = array(
          'IBLOCK_ID' => 19,
          'ACTIVE' => 'Y',
          '>=PROPERTY_end_date' => date("Y-m-d H:i:s")
      );
      
      $CACHE_TIME = 86400;
      $CACHE_ID = "some_key_iblock".implode('_', $arFilter);
      $obCache = new CPHPCache;
      
      if($obCache->InitCache($CACHE_TIME, $CACHE_ID, "/"))
      {
          $arVars = $obCache->GetVars();
      }
      
      // Правильный ИД, так как в данном случае кеш будет создан на весь день.
      // (При условии что нам надо выбрать все элементы которые на данный день активны.)
      $arFilter = array(
          'IBLOCK_ID' => 19,
          'ACTIVE' => 'Y',
          '>=PROPERTY_end_date' => date("Y-m-d 00:00:00")
      );
      
      $CACHE_TIME = 86400;
      $CACHE_ID = "some_key_iblock".implode('_', $arFilter);
      $obCache = new CPHPCache;
      
      if($obCache->InitCache($CACHE_TIME, $CACHE_ID, "/"))
      {
          $arVars = $obCache->GetVars();
      }
      

    Ссылки по теме:

    • Кеширование компонентов
    • Технологии кластеризации 1С-Битрикс — эффективное кэширование в memcached (Кэширование в составе кластера)
    • Кеш не панацея (MySQL), Сколько запросов к БД оптимально, когда кешировать не стоит, и прочие моменты (Советы веб-разработчиков)
    • Битрикс и большой кэш по ID (www.yunaliev.ru)
    • Кэш [bitrix api] (www.yunaliev.ru)
    • Тегированный кеш (main 9.1.0) (Блог)
    • Класс CPHPCache (Документация для разработчиков)
    • Кэш не панацея (обсуждение темы в группе Академия 1С-Битрикс выпускников)

    Проблемы при кешировании меню

    Большой размер папки с кешем меню

    Ситуация. Выявлен большой размер папки bitrix/managed_cache/MYSQL/menu: 1.9 Gb. На сайте 4 типа сквозных меню, страниц на сайте много. В чём проблема и что делать?

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

    Само по себе такое количество файлов не страшно, если место на диске достаточно. Проблема в том, что акселератор (в нашем случае APC) складывает эти файлы в кеш и кеш переполняется.

    Решение: Исключить файловый кэш из акселератора, убедившись, что в template.php и result_modifier.php нет запросов и тяжелых вычислений. В файлах menu_ext запросы должны кешироваться.

    apc.filters="-/bitrix/cache,-/bitrix/managed_cache/,-/upload/"

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

    "CACHE_SELECTED_ITEMS" => "N",

    Это приведет к тому, что при создании файла кеша меню в ключе не будет участвовать url. А расчет выбранного уровня будет происходить после получения данных из кеша.

    Примеры. Кешируем правильно

    Рассмотрим примеры, как можно улучшить производительность проекта.

      Кешируем правильно

    • Комплексный пример: класс кеширования пользователя;
    • Время кеширования (cache key and ttl);
    • Как в API правильно подставлять ключи;
    • Отключаем сброс ключей;
    • Итоги рекомендаций по кешированию.

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

    Но работа кеша может быть организована не совсем корректно, что приводит к проблемам производительности.

    Комплексный пример: класс кеширования пользователя

    Как неправильно:

    // Класс кеширования пользователя с некорретным кешированием
    class User
    {
    	private $userData;
    	function __construct($userId = false)
    	{
    		if (!$userId)
    		{
    			global $USER;
    			$userId = $USER->GetID();
    		}
    
    		$cntStartCacheId = __CLASS__.'::'.__FUNCTION__.'|'.SITE_ID.'|'.$userId;
    		$cache = new CXxxCache($cntStartCacheId.'sid0',3600,'user_data');
    		$this->userData = $cache->Init();
    
    		if (null == $this->userData)
    		{
    			$this->putUserData(array("ID"=>$userId));
    			$this->putUserData(CUser::GetByID($userId)->Fetch());
    			$this->putUserData(array("DEPARTMENT" => $this->getDepartment()));
    
    			$cache->registerTag('USER_NAME_'.$userId);
    			$cache->set($this->userData);
    		}
    	}
    }
    

    Что не так в примере выше:

    1. Общая папка для кеша пользователей
    Такой пример кеша практически не работает на проекте. На портале, где много пользователей, мы постоянно будем [dw]терять кеш[/dw][di]Большое количество хитов попадает в том момент, когда кеш уже скинут. Происходит это из-за того, что кеш пользователей уникальный на каждого человека и его хотелось бы скидывать отдельно. В примере указана общая папка для кеша, а не отдельная для пользователя. Следовательно, когда мы хотим скинуть кеш для пользователя, мы скидываем его его для всех. [/di], что негативно отразиться на его работе.
    2. Время кеширования
    Кеширование сущности в примере на 1 час — неэффективно. Сохранение на малое время может привести к [dw]массовой генерации кеша[/dw][di]Простой пример: сотрудники ушли на обед и вернулись через час, а кеш уже у всех сброшен.[/di]. Такая проблема актуальна для больших компаний и проектов.
    3. Заполнение пользователя методом GetByID
    В кеш попадает большой объем данных о пользователе, а именно много ненужной информации (пароль, подтверждение пароля, настройки синхронизации и т.д.).
    4. Использование Fetch
    При использовании Fetch данные не экранируются и никак не проверяются, что приводит к ошибкам безопасности.

    Как правильно:

    GetID();
    		}
    
    		$cntStartCacheId = __CLASS__.'::'.__FUNCTION__.'|'.SITE_ID.'|'.$userId;
    		$cache = new CXxxCache(
    			$cntStartCacheId.'sid0',
    			// увеличили время кеширования
    			604800,
    			// путь для ключей кеша сделали зависимым от $userID
    			'user_data/'.substr(md5($userId),2,2).'/'.$userId 
    		);
    
    		$this->userData = $cache->Init();
    
    		if (null == $this->userData)
    		{
    			$this->putUserData(array("ID"=>$userId));
    
    			// Выбираем только нужные поля
    			$this->putUserData(CUser::GetList(...)->GetNext(true, false));
    			$this->putUserData(array("DEPARTMENT" => $this->getDepartment()));
    
    			$cache->registerTag('USER_NAME_'.$userId);
    			$cache->set($this->userData);
    		}
    	}
    }
    
    • Увеличили время кеширования до недели;
    • Изменили папку хранения кеша (разложили персонализированный кеш по подпапкам);
    • Изменили сбор информации по пользователю с GetByID на [dw]GetList[/dw][di]Достаточно часто в проектах многие используют вызов CIBlockElement::GetById. Простой, удобный метод, когда надо вытащить какое-то поле для элемента инфоблока. Но этот метод тянет все поля и все свойства элемента. В случае инфоблока с большим количеством свойств и большого числа посетителей на сайте этот простой запрос приводит к снижению производительности.

      Подробнее…[/di];

    • Заменили Fetch на GetNext(true, false).

    Время кеширования (cache key and ttl)

    Пример на компоненте, который отображает дни рождения. Установлено большое время кеширования и добавлен дополнительный параметр, который сам компонент никак не отрабатывает и его не обрабатывает шаблон. Но так как в параметры подставлен date, то в 0 часов получим новый ключ у кеша данного компонента.

    IncludeComponent(
    	"bitrix:intranet.structure.birthday.nearest",
    	"widget",
    	Array(
    		"CACHE_TYPE" => "A",
    		"CACHE_TIME" => "86450",
    		"DATE_FORMAT" => "j F",
    		"DETAIL_URL" => "#SITE_DIR#company/personal/user/#USER_ID#/",
    		"DEPARTMENT" => "0",
    		.....
    		"CACHE_DATE" => date('dmy')
    	)
    );
    

    Пример: как в API правильно подставлять ключи

    В таком случае часто проставляют лишние параметры, что приводит к увеличению кеша. (например date без параметров приводит к обновлению кеша каждую секунду).

    <?php
    
    // Пример добавление в ключ кеша метки времени для корректного переключения кеша. Метка может быть и не из времени.
    
    $cache = BitrixMainDataCache::createInstance();
    if ($cache->initCache(86450, '/some_key/'.date('myd').'/', '/some_dir/'))
    {
    	$var = $cache->getVars();
    }
    else
    {
    	// Получение данных
    	$cache->startDataCache();
    	$cache->endDataCache($var);
    }
    

    Отключаем сброс ключей

    Процесс импорта обычно приводит к сбросу кеша. Если мы выгружаем большой объем данных — это занимает продолжительное время и дает большую нагрузку на боевом проекте. В ряде случаев этого можно избежать. В частности при работе с инфоблоками:

    1. Отключите кеширование элементов (сбрасывание тегированного кеша) перед импортом;
    2. Включите его по окончании процесса импорта элементов;
    3. Сбросьте те теги инфоблока, которые сбрасывались в данном случае.

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

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

    <?php
    // Отключение сброса тегированного кеша инфоблоков и пересчета фасетного индекса, во время импорта.
    
    BitrixIblockPropertyIndexManager::enableDeferredIndexing();
    BitrixCatalogProductSku::enableDeferredCalculation();
    
    CAllIBlock::disableTagCache($iblockID);
    
    // Импорт элементов 
    
    CAllIBlock::enableTagCache($iblockID);
    CAllIBlock::clearIblockTagCache($iblockID);
    
    BitrixCatalogProductSku::disableDeferredCalculation();
    BitrixCatalogProductSku::calculate();
    
    BitrixIblockPropertyIndexManager::disableDeferredIndexing();
    BitrixIblockPropertyIndexManager::runDeferredIndexing($iblockID);
    

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

    Итоги рекомендаций по кешированию

    Хорошо Плохо
    • Уникальные ключи, в отдельных папках;
    • Только нужные поля в кеше;
    • Максимальное время хранения ключей;
    • Конечные списки в одном ключе;
    • Разумное количество тегов в ключе;
    • Сброс ключей по пути.
    • Закрывать кешем долгие вычисления или страницы;
    • Часто сбрасывать ключи, особенно в большом количестве.

    Внимание! Не забывайте, что управляемый кеш инфоблоков очищается только при вызове CIBlockElement::Update. При изменении, например, свойств с помощью CIBlockElement::SetPropertyValueCode очистки не произойдет. Делаем вручную после изменения:

    if(defined('BX_COMP_MANAGED_CACHE'))
       $GLOBALS['CACHE_MANAGER']->ClearByTag('iblock_id_'.$arParams['IBLOCK_ID']);

    Оптимизация выборки дополнительных данных

    Использование метода GetList

    Мелкие запросы, на первый взгляд, не сильно влияют (в относительной оценке) на работу сайта, если при этом на том же сайте есть какой-нибудь явно тяжелый кусок кода, связанный, например, с фильтрацией товаров, который по определению кешированию не поддается.

    Достаточно часто в проектах многие используют вызов CIBlockElement::GetById. Простой, удобный метод, когда надо вытащить какое-то поле для элемента инфоблока. Но этот метод тянет все поля и все свойства элемента. В случае инфоблока с большим количеством свойств и большого числа посетителей на сайте этот простой запрос приводит к снижению производительности. А если таких запросов несколько десятков в различных result_modifier у разных компонентов? Оказывается, что в совокупности, несмотря на кеширование, эти вещи создают пиковые нагрузки в момент обновления кеша.

    Если уж надо получить название элемента по ID, то лучше воспользоваться GetList с указанием конкретного вытаскиваемого поля элемента.

    Примечание: GetByID внутри вызывает GetList. Разница только в том, что GetByID в любом случае тянет все поля и свойства, а в GetList этот перечень можно контролировать.

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

    Также GetList позволяет выбрать сразу несколько записей, тогда как GetByID только одну.

    Единственное преимущество GetById перед GetList с точки зрения разработчика состоит в том, что GetById можно просто вызвать и все. Прямое использование GetList требует определенной работы с кодом (создание массива параметров).

    Примеры повышения производительности

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

    1. Рассмотрим пример, когда необходимо для каких-либо элементов одного инфоблока (авторы книг) получить дополнительные свойства из другого инфоблока (информация по авторам), например путем изменения шаблона или result modifier.

      В большинстве случаев это делается в цикле, например в таком:

       // неправильный вариант выборки, на каждый элемент делается дополнительный запрос
      foreach($arResult['ITEMS'] as $ikey => $ival)
      {
          $avtorID = intval($ival['PROPERTIES']['AVTOR']['VALUE']);
          if($avtorID > 0)
          {
              $rs = CIBlockElement::GetByID($avtorID);
              while($ar = $rs->GetNext())
              {
                  $arResulr['ITEMS'][$ikey]['AVTOR_INFO'][] = $ar;
              }
          }
      }
      

      но это не правильно. Такой цикл создает множество запросов, что снижает производительность.

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

      // правильный вариант, информация по авторам выбирается одним запросом и только нужная
      $avtorID = array();
      
      foreach($arResult['ITEMS'] as $ikey => $ival)
      {
          $aID = intval($ival['PROPERTIES']['AVTOR']['VALUE']);
          if($aID > 0)
          {
              $avtorID[] = $aID;
          }
      }
      
      $avtorID = array_unique($avtorID);
      
      
      $rs = CIBlockElement::GetList(
          array('ID' => 'ASC'),
          array(
              'IBLOCK_ID' => XX,
              'ID' => $avtorID,
              'ACTIVE' => 'Y'
          ),
          false,
          false,
          array('ID', 'NAME', 'PREVIEW_PICTURE')
      
      );
      while($ar = $rs->GetNext())
      {
          $arResulr['AVTOR_INFO'][$ar['ID']] = $ar;
      }
      
    2. Если мы можем изменить основной АПИ-вызов, например, используем свой компонент, то данные по авторам из примеров выше можно получить сразу:
      // правильный вариант,
      // информация по книгам и связанная с ними информация по авторам выбирается одним запросом
      $rs = CIBlockElement::GetList(
          array('ID' => 'ASC'),
          array(
              'IBLOCK_ID' => YY,
              'ACTIVE' => 'Y'
          ),
          false,
          false,
          array(
              'ID',
              'NAME',
              'PREVIEW_PICTURE',
              'PREVIEW_TEXT',
              'PROPERTY_AVTOR.NAME',
              'PROPERTY_AVTOR.PREVIEW_PICTURE',
          )
      
      );
      while($ar = $rs->GetNext())
      {
          $arResulr['ITEMS'][] = $ar;
      }
      
    3. Использование более оптимального кода, который сокращает количество запросов.

      Пример: Необходимо проверить для списка определенных пользователей, состоят ли они в заданной группе.

      Типичный код:

      foreach ($arSomeUser as $arUser)
      {
          $arGroups = CUser::GetUserGroup($arUser['ID']);
          if(in_array(5, $arGroups))
          {
              // Делаем что-то
          }
      }
      

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

      Если в группе содержится мало пользователей, то правильнее будет использовать следующий подход:

      $rsUser = $arUser->GetList(($by="ID"), ($order="desc"), array('ACTIVE' => 'Y', 'GROUPS_ID' => 5 ....))
      while($arUser = $rsUser->GetNext())
      {
              // Делаем что то
      }
      

    Выборка и хранение в кеше только нужных данных

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

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

    Важные примеры повышения производительности

    1. Часть АПИ-методов может генерировать излишнюю информацию. Лучше сразу ограничить запросы только нужными данными.

      Примеры:

      $rs = CIBlockResult::GetNext(true, false);
      // установка параметра false позволяет ограничить выборку только преобразованными значениями полей.
      $res = CIBlock::GetList(array(), array('TYPE'=>'catalog', 'SITE_ID'=>SITE_ID, 'ACTIVE'=>'Y'), false);
    2. В некоторых случаях метод Fetch работает быстрее, чем GetNext, но он не делает данные безопасными.
    3. Кеширование только нужных данных.

      В самом компоненте нужно ограничить результат работы компонента $arResult только нужными данными.

      Например, компонент должен выводить всего идентификатор элемента и его название, тогда только их и нужно занести в кеш:

      $this->SetResultCacheKeys(array("ID", "NAME"));

      Если мы делаем какие-то операции в result_modifier.php, чтоб потом передать их результат в component_epilog.php, то также понадобится установить ключи в SetResultCacheKeys. Тогда результат наших операций будет также закеширован.

      foreach ($sAr as $key => $arElement)
      {
      $this->__component->arResult["IDS"][] = $arElement["ID"];
      }
      $this->SetResultCacheKeys(array("IDS"))

      Примечание: Если в SetResultCacheKeys не установлены ключи, то тогда, по умолчанию, будет производиться кеширование всех результатов $arResult.

      Ссылки по теме:

      • Файл component_epilog.php

    4. Оптимизация размера кеша.

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

    5. Ограничение объема запрашиваемой информации.

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

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

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

      Пример:

      Параметр nPageSize=20 массива arNavStartParams позволяет отобразить 20 элементов, но он делает 2 запроса к базе:

      1. "COUNT" - получение кол-ва элементов
      2. "SELECT" - непосредственно выборка данных
      

      в то время как параметр nTopCount=4 того же массива создает сразу 1-н запрос вида:

      SELECT * FROM tbl LIMIT 4;  
      

      Использование nTopCount гораздо эффективнее т.к. он не делает дополнительный запрос на получение записей выборки.

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

    Оптимизация запросов к БД

      Пример оптимизации запроса

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

    Нельзя:

    foreach($arResult["ORDERS"] as $key => $val)
    {
       foreach($val["BASKET_ITEMS"] as $vvval)
       {
          $rsEls = CIBlockElement::GetByID();
       }
    }

    Следует:

    $arIDs = array();
    foreach($arResult["ORDERS"] as $key => $val)
      {
        foreach($val["BASKET_ITEMS"] as $vvval)
          {
            $arIDs[] = $vvval["PRODUCT_ID"];
          }
      }
    if(!empty($arIDs))
    {
        $rsEls = CIBlockElement::GetList(array(), array("ID" => $arIDs));
        ...
    }
    
    foreach($arResult["ORDERS"] as $key => $val)
    {
       foreach($val["BASKET_ITEMS"] as $vvval)
       {
          //наполняем данные, налаживая соответствие ID-ков
       }
    }

    Фактически, вы сводите порой десятки, если не сотни, запросов к одному.

      Специальные методы

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

    Хороший пример — модуль интернет-магазина и работа с заказом: можно изменить флаг оплаты заказа путем CSaleOrder::Update, а можно путем CSaleOrder::PayOrder. Так вот, следует применять именно PayOrder, потому что в нем произойдет вызов соответствующих обработчиков.

    Даже если вам надо изменить множество полей (того же заказа) и флаг оплаты, то произведите изменение через PayOrder, а затем уже апдейт остальных полей.

    Как сделать сайт быстрым

    Разработчик в большинстве случаев под ускорением загрузки сайта понимает работу по back-end. Однако посетителю всё равно за сколько миллисекунд открывается страница у разработчика, ему важно как быстро открывается страница у него самого. А тут уже важен целый комплекс показателей. Квалифицированный разработчик должен рассматривать сайт не как кусок программного кода, за который он отвечает, а как конечный комплексный продукт.

    Back-End

    Без отладки программного кода, конечно же, не обойтись. Здесь разработчику в помощь приходит инструмент Монитор производительности, который позволяет профилировать все страницы сайта на предмет кода, SQL запросов. Кроме того, этот модуль даёт рекомендации по тюнингу серверного ПО.

    Далее необходимо заняться кешированием компонентов.

    Следующий шаг — это внедрение технологии Композитный сайт.

    Расположение сайтов на удалённых территориально серверах может серьёзно повлиять на время получения информации пользователем. Особенно это актуально для проектов, ориентирующихся на локальные рынки. Перенос хостинга в таком случае может дать существенный прирост, в ряде случаев отмечалось ускорение до 1 секунды.

    Front-End

    После выполнения работ по back-end’у необходимо переходить к рассмотрению остальных параметров, влияющих на скорость загрузки. В Bitrix Framework есть инструмент — Скорость сайта. Его задача — облегчить разработчику работу по оптимизации загрузки страницы.

    Примечание: Для работы Скорости сайта нужна статистика. После внесения каких-то изменений необходимо выдерживать некоторый период для получения статистически верных данных. Продолжительность периода зависит от посещаемости сайта.

    У этого инструмента есть диаграмма по хитам, где можно посмотреть каждый из этапов загрузки Navigation Timing.

    На этом графике зелёным цветом показана работа back-end’а, а фиолетовым — работа front-end’а. То есть получается, что большую часть времени в загрузке страницы браузер тратит не на back-end, а на front-end.

    Улучшить работу front-end можно уменьшив количество загружаемых ресурсов и их объём.

    Меры по уменьшению числа загружаемых ресурсов:

    • Включив объединение css и js скриптов. В браузерах есть ограничение на количество одновременных соединений с сервером. Как правило это 6, но может доходить до 13, как в IE. То есть, если на странице 70 файлов, то браузер скачает их в несколько приёмов, а не одномоментно. Если объединить эти скрипты, то скорость загрузки увеличится.
    • Включив CDN. Однако к подключению CDN нужно подходить внимательно. Если ваш основной посетитель локализован на определённой территории, то CDN может оказаться замедляющим фактором, если ближайший сервер расположен далеко от пользователей.
    • Используя кеширование ресурсов в браузере (Expires/Cache-Control: max-age, Last-Modified/E-tag). Нужно отдавать заголовки для картинок, css и js-скрипта. Если нет заголовков последней модификации или заголовка Expires, то на каждый хит браузер будет скачивать все ресурсы повторно.

      Если у вас есть дата последней модификации, то это уже хорошо. В этом случае на вторых и последующих хитах браузер будет делать conditional get запрос. То есть спрашивать: а не изменился ли этот ресурс вот с этой даты? И загружать только изменённые ресурсы. Правда и в этом случае приходится делать один дополнительный запрос.

      Чтобы избежать этого запроса достаточно настроить в Apache или NGINX выдачу заголовка Expires. Это, к сожалению, часто забывают сделать.

    • Все картинки для шаблона должны быть в спрайтах. А для мелких картинок лучше вообще использовать Base64.

    Меры по уменьшению объёма загружаемых данных:

    • GZIP-сжатие. Рекомендуется использовать серверное сжатие. Если оно не доступно, то можно использовать модуль Компрессия. Однако с этим модулем надо быть осторожным, его сжатие поддерживается не всеми хостерами.
    • Минификация CSS и Javascript. Это — вырезание пробелов, переводов строк, уменьшение локальных переменных и оптимизация кода (для js).
    • Сжатие картинок специальными утилитами.

    Зачем нужно сжимать данные?

    Дополнительные меры

    • Вынос ресурсов на разные домены, который позволяет обойти ограничение на 6 подключений.
    • Порядок подключения ресурсов на странице: CSS вверху, Javascript – внизу. Оба они блокируют рендеринг страницы до завершения собственной загрузки. Но если css мы не можем убрать в низ страницы: без него она просто не отобразится, то Javascript — вполне убрать вниз или же грузить асинхронно.

      Примечание: Это же относится к Яндекс.Метрике, GoogleAnalitics и все подобные внешние скрипты и инструменты. Несмотря на то, что они все внешние и должны попозже загружаться. Достаточно часто получается что несмотря на то, что эти скрипты грузятся асинхронно, эти файлы могут загрузится раньше рендеринга страницы и, соответственно, как только этот ресурс загрузился, он начинает выполняться. Совет: грузите все внешние инструменты (метрики, счётчики, кнопки Вконтакте и прочие) в BX.ready, то есть на факт того, что страница загрузилась.

    Сторонние инструменты

    Для оптимизации front-end’a в качестве чек-листа можно использовать сторонние инструменты:

    • Google PageSpeed Insights
    • Audits в Chrome Developers Tools
    • YSlow

    Если долго сохраняется элемент в административном разделе

      Проблема и её признаки

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

    Признаки:

    • долгое (60 секунд и выше) сохранение элемента/товара через форму редактирования в административном разделе (при импорте и обновлении из консоли проблема не наблюдается)
    • анализ на стороне хостера выявляет долгий запрос вида:
      UPDATE b_iblock_element SET 
               TIMESTAMP_X = TIMESTAMP_X,
               SHOW_COUNTER_START = ifnull(SHOW_COUNTER_START, now()),
               SHOW_COUNTER =  ifnull(SHOW_COUNTER, 0) + 1
      WHERE ID=ИД_сохраняемого_элемента
      
    • в некоторых случаях изменения не сохраняются вообще

    Наиболее вероятная причинаобработчик событий, посылающий http(s)-запрос к публичной детальной странице элемента, на которой в настройках компонента включено обновление счетчика просмотров.

    Далее рассмотрим выявление причины подробнее на примере.

      Выявление причины

    1. Выявляем тормозящий запрос.

      В ходе первичного анализа установлено, что тормозящий запрос — это вызов метода ClBlockElement::CounterInc. Однако данный метод вызывается только для увеличения счетчика просмотров в публичных компонентах и не должен тормозить процесс сохранение элементов.

    2. Проверяем, как работает сохранение элемента.

      Открываем код страницы редактирования и видим, что сохранение элемента работает через транзакции. Делается это, чтобы откатить все изменения, если на каком-то этапе сохранения произошла ошибка.

    3. Проверяем вызовы методов.

      Последовательно проверяем все вызовы [dw]методов[/dw][di]
      CIBlockElement::Update, обновление полей цен и товара, работу с документооборотом.
      [/di], находящиеся внутри блока транзакции — есть ли на проекте обработчики событий, что именно вызывается в этих методах. Если результат отрицательный — разбираем методы детально и смотрим уже их (например, для метода CIBlockElement::Update таким является вызов CIBlockElement::UpdateSearch, а в нем — CSearch::Index).

      Важно! Учтите, в проекте могут быть и другие обработчики с вызовом api.

      В результате в одном из обработчиков находим http(s)-запрос к публичной детальной странице элемента. А на этой странице [ds]в компоненте[/ds][di]
      Например, в компонентах Элемент каталога детально или Каталог включена опция Использовать счетчик просмотров.[/di] включено обновление счетчика просмотров. Отключаем его — все тормоза исчезают.

      Примечание: При открытии транзакции строка (а иногда и вся таблица) блокируется от записи. Это одна из причин, почему использование транзакций запрещено в методах api. Все попытки изменения записи, кроме текущей, ставятся в очередь ожидания на уровне базы. Пока транзакция не закрыта или не отменена — очередь ожидает.

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

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

      План сохранения данных без/с обработчиком

    Ожидаемый план сохранения данных:

    1. Открываем транзакцию
    2. Обновляем строку в b_iblock_element
    3. Запись блокируется от внешнего изменения (другой хит) на уровне БД
    4. Закрываем транзакцию
    5. Запись разблокируется
    6. Выполняется очередь изменений с других хитов (если успела накопиться)

    План сохранения данных с ранее выявленным обработчиком

    1. Открываем транзакцию
    2. Обновляем строку в b_iblock_element
    3. Запись блокируется от внешнего изменения (другой хит) на уровне БД
    4. Вызывается обработчик
    5. Идет хит на публичную страницу
    6. Пытаемся обновить счетчик просмотров (та же самая строка в b_iblock_element)
    7. …долгое ожидание…

    8. Прерывание по таймауту. Обработчик завершен
    9. Если время исполнения скрипта не вышло — закрываем транзакцию. Вышло — просто «падаем»
    10. Запись разблокируется
    11. И тут, наконец, выполнится увеличение счетчика просмотров

    Важно! Причины реализации подобных обработчиков могут быть разные. Тем не менее, в идеале на проекте их быть не должно. Это основная и главная рекомендация.

      Что делать, если обработчик нельзя убрать

    Повторимся, что лучше не использовать в своем проекте обработчики событий, посылающие http(s)-запросы к публичной детальной странице элемента и вступающие в конфликт с выполнением транзакций.

    Если по каким-либо причинам от такого обработчика избавиться нельзя, то можно воспользоваться следующими вариантами решения проблемы:

    1. Заменить прямое обращение к публичной странице на добавление в очередь обработки (своя таблица) и агента/скрипт на кроне, который пойдет по очереди и выполнит требуемое (обращение к странице и какие-то действия с результатом). Наиболее предпочтительный способ, однако его реализация требует высокого уровня знаний и квалификации разработчика.
    2. Передавать в строке обращения некий параметр (например,disableCounter=Y). На самой странице параметр вызова компонента USE_ELEMENT_COUNTER (на примере bitrix:catalog или catalog.element) меняем таким образом:
      • было:
        "USE_ELEMENT_COUNTER" => "Y"
        
      • стало:
        "USE_ELEMENT_COUNTER" => (isset($_REQUEST['disableCounter']) && $_REQUEST['disableCounter'] === 'Y') ? 'N' : 'Y'
        

      Этот способ проще первого, однако теряется возможность настройки компонента через визуальный редактор. Это можно поправить переносом правки в шаблон (в случае комплексного компонента).

    Как снизить нагрузку с помощью API

    Снижаем нагрузку с помощью API

    Фильтры по like

    Часто встречаемая проблема на проектах это фильтры по like.

    Пример в старом API:

    <?php
    
    // Различные вызовы АПИ и запросы которые они генерируют
    
    BitrixMainLoader::includeModule('iblock');
    $rs = CIBlockElement::GetList(
    	[], 
    	['CODE' => 'xxx'], // правильно вариант данного фильтра ['=CODE' => 'xxx'], 
    	false, 
    	false, 
    	['ID', 'CODE', 'NAME']
    );
    
    /*
    SELECT BE.ID as ID,BE.CODE as CODE,BE.NAME as NAME
    FROM
    	b_iblock B
    	INNER JOIN b_lang L ON B.LID=L.LID
    	INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID
    WHERE
    	1=1 AND ( ((((BE.CODE LIKE 'xxx')))))
    		AND (((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL))
    	)
    */
    
    

    Пример в ORM:

    <?php
    
    BitrixIblockElementTable::getList([
    	'select' => ['ID', 'NAME', 'CODE'],
    	'filter' => ['CODE' => 'xxx'] // правильный вариант фильтра 'filter' => ['=CODE' => 'xxx']
    ]);
    
    /*
    SELECT
    	`iblock_element`.`ID` AS `ID`,
    	`iblock_element`.`NAME` AS `NAME`,
    	`iblock_element`.`CODE` AS `CODE`
    FROM `b_iblock_element` `iblock_element`
    WHERE UPPER(`iblock_element`.`CODE`) like upper('xxx')
    */
    

    Для ORM есть решение, это [dw]новый фильтр[/dw][di]В обновлении main 17.5.2 в ORM появился новый фильтр.

    Подробнее…[/di].

    <?php
    
    // С новым фильтром не получится допустить ошибку
    BitrixIblockElementTable::query()
    	->setSelect(['ID', 'NAME', 'CODE'])
    	->where('CODE','xxx')->exec();
    
    /*
    SELECT
    	`iblock_element`.`ID` AS `ID`,
    	`iblock_element`.`NAME` AS `NAME`,
    	`iblock_element`.`CODE` AS `CODE`
    FROM `b_iblock_element` `iblock_element`
    WHERE `iblock_element`.`CODE` = 'xxx'
    */
    

    В старых фильтрах необходимо контролировать like.

    Подготовка собственных индексов

    В штатном продукте не предусмотрены все индексы, т.к. все проекты разные. Для улучшения производительности не забывайте предусмотреть создание своих индексов. В этом поможет инструмент [dw]Монитор производительности[/dw][di]Заочно нельзя сказать какие индексы необходимо создавать, надо всегда рассматривать конкретную ситуацию. Индексы нужны для конкретных выборок на конкретных проектах. В зависимости от архитектуры и логики проекта медленные запросы получаются у каждого свои, и для них нужны свои индексы, часто составные.
    Страницы Анализ индексов и Список индексов — инструмент анализа и рекомендаций по созданию индексов.

    Подробнее в курсе Администратор. Базовый[/di].

    Убираем count

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

    Проблема очень критична для Rest. Чтобы не использовать Count в случае если вам не нужно [dw]количество элементов[/dw][di]Например вам нужно просто 10 последних записей.[/di] или вы делаете импорт всех записей по фильтру, передавайте параметр start= -1. Подробнее почитать об этом и посмотреть пример можно в документации по REST.

    Предотвращаем срыв конвейера

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

    Рекомендации по предотвращению срыва конвейера

    • Не должно быть файлов after_connect*.php в ядре продукта. Эти файлы содержат настройки подключения к базе данных для случаев, когда мы устанавливаем проект на какой-то хостинг;
    • Не использовать SET (в начале хита полностью срывает конвейер);
    • Минимизировать изменения в начале страницы (для отлавливания таких мест в коде поможет модуль Монитор производительности);
    • В ряде случаев изменения можно экранировать и они не сломают конвейер.

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

    <?php
    // Выполнение всех запросов на master сервере, без срыва конвейера
    
    BitrixMainApplication::getInstance()->getConnectionPool()->useMasterOnly(true);
    
    // Какие либо обновления и изменения
    
    BitrixMainApplication::getInstance()->getConnectionPool()->useMasterOnly(false);
    

    Например, таким образом сейчас обернута работа с тегированным кешем.

    Вебинар Мастер-класс по производительности

    Рассмотренную в уроке тему подробнее смотрите в вебинаре:

    Мастер-класс по производительности. Highload проекты, как их сделать и поддерживать? Техноволна 6 от 08 апреля 2020.

    Примеры оптимизации JS кода

    Разработчики не очень часто задумываются над количеством хитов, когда делают инструмент которым сами не пользуются и не арендуют мощности под это. Они оставляют этот момент на откуп клиентам: «Купят по мощнее сервер и все залетает». Однако профессионализм разработчика состоит в том, чтобы уметь видеть все последствия своих трудов и уметь выбирать решения оптимальные не только для него, как программиста, но и для клиента.

    Где искать ошибку в ajax обработчике при POST запросах

    Пример уменьшения количества хитов рассмотрим на основе десктоп мессенджера Битрикс24.

    Специфика этого инструмента такова, что он может выдавать просто запредельное количество хитов, ни один другой инструмент Bitrix Framework ни делает даже половины хитов мессенджера.

    На Битрикс24 количество пользователей росло чуть ли не по экспоненте, количество хитов тоже, и наступил критичный момент когда игнорировать такие цифры стало нельзя:

    Анализ лога по хитам выявил следующую проблему: в логе было только название обработчика, так как все данные отправлялись POST запросом на сервер.
    Логировать POST запросы вещь не очень приятная, поэтому нужно было искать другое решение что бы понять легальные ли это запросы или все таки паразитные.

    Решение очень простое: это GET метки. Достаточно выполнять запрос вместе с GET меткой: im.ajax.php?GET_HISTORY, im.ajax.php?UPDATE_STATE и так далее.

    По этим меткам получилось группировать запросы к серверу и выявить узкие места.

    Были определены самые популярные метки, оптимизировано время исполнения, а часть из них полностью переделано таким образом чтобы не дергать сервер лишний раз (были созданы агенты, которые отправляли данные с помощью модуля Push & Pull).

    После всех этих операций получились вот такие показатели:

    Сессии и cookie

    Про механизм и возможности сессии отлично написано в официальной документации PHP.

    Внимание! Описанные в главе способы работы с сессиями актуальны с версии главного модуля 20.5.0.

    Переменная $_SESSION

    Работать напрямую с $_SESSION — допустимо, но не желательно. Все изменения данных в глобальной переменной будут сохранены, но настоятельно советуем использовать новое API вместо этой переменной.

    Вместо прямого использования переменной лучше использовать объект, возвращаемый методом BitrixMainApplication::getSession():

    $session = BitrixMainApplication::getInstance()->getSession();
    if (!$session->has('foo'))
    {
        $session->set('foo', 'bar');            
    }
    
    echo $session['foo']; //bar

    Данный объект реализует интерфейс ArrayAccess, а также BitrixMainSessionSessionInterface.

    Сессионный кеш (Local Session)

    Иногда возникает задача кешировать данные, которые связаны с текущим пользователем. Конечно, один из вариантов, это использовать сессию, но это не всегда подходит, так как:

    1. сессия не создана для кеширования,
    2. большое количество данных сказывается на скорость работы с сессией,
    3. возникают блокировки хитов.

    Один из альтернативных вариантов, это создать кеш, который привязан к session_id(). По сути, это простая имитация сессии. С версии main 20.5.400 есть новая возможность.

    Пример:

    $localStorage = BitrixMainApplication::getInstance()->getLocalSession('someCategory');
    if (!isset($localStorage['productIds']))
    {
        $localStorage->set('productIds', [1, 2, 100]);
        $localStorage->set('price', 42);
    }
    
    var_dump($localStorage->get('productIds'));
    

    Принцип работы

    Принцип работы достаточно прост: при вызове BitrixMainApplication::getLocalSession($name) всегда возвращается экземпляр BitrixMainDataLocalStorageSessionLocalStorage. Это элемент кеша, который автоматически опирается на session_id().

    При этом, если это первое обращение и данных нет, то будет создан пустой контейнер, если же в кеше были данные по $name, то контейнер будет наполнен данными.

    Все SessionLocalStorage сохраняются в конце хита ядром автоматически.

    Внимание! SessionLocalStorage работает на кеше, который описан в настройках .settings.php.

    Примечание: Если кеш файловый, то SessionLocalStorage будет использовать для хранения $_SESSION, так как иначе возникает проблема контроля и удаления устаревших файлов, что может повлиять на работу файловой системы.

    Документация по теме:

    • Документация D7: SessionLocalStorage

    Сессия разделенный режим (hot&cold)

    Введение

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

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

    Общий принцип

    Один из вариантов решения как избежать блокировки — это написать SessionHandlerInterface и не делать там просто блокировку. Но этот вариант не подходит, так как есть большое количество существующих использований в коде Bitrix Framework и партнеров.

    Выбран другой путь:

    1. Данные, которые используются на каждом хите выделены в KernelSession-сессию (hot данные). Это данные для аутентификации, авторизации и другие связанные с ядром.
    2. Всё остальное, что хранится в сессии — это cold-данные.
    3. KernelSession-сессия неблокирующая и работает по принципу «кто последний, того и тапки».
    4. Всё остальное — это обычная блокирующая сессия.
    5. Блокирующая сессия стартует только при первом обращении к cold-данным.

    Разделение хранения cold&hot

    Хранилище для KernelSession-сессии — это шифрованные cookies.

    Хранилище для cold-сессии — это обычная сессия, принцип работы, как и раньше, поэтому данные могут храниться в Redis, Memcache, БД.

    Настройка хранения

    Чтобы включить разделенный режим сессии, нужно в bitrix/.settings.php изменить session[mode] на separated. И добавить 'kernel' => 'encrypted_cookies', и 'lifetime' => 14400,.

    Примеры можно посмотреть здесь.

    Настройка хранения данных сессии

    Ядро поддерживает четыре варианта для хранения (файлы, redis, database, memcache) данных сессии. Способ хранения описывается в bitrix/.settings.php в секции ‘session’:

    Файлы

    // bitrix/.settings.php
    return [
    //...        
        'session' => [
            'value' => [
                'mode' => 'default',
                'handlers' => [
                    'general' => [
                        'type' => 'file',       
                    ]           
                ],
    
            ]                   
        ] 
    ];

    Настройка для разделённой сессии:

    // bitrix/.settings.php
    return [
    //...        
        'session' => [
            'value' => [
                'lifetime' => 14400, // +
                'mode' => 'separated',  // +
                'handlers' => [
                    'kernel' => 'encrypted_cookies',  // +           
                    'general' => [
                        'type' => 'file',       
                    ],
                ],
    
            ]                   
        ] 
    ];

    Redis

    // bitrix/.settings.php
    return [
    'session' => [
        'value' => [
            'mode' => 'default',
            'handlers' => [
                'general' => [
                    'type' => 'redis',
                    'port' => '6379',
                    'host' => '127.0.0.1',
                ],
            ],
        ],
    ]

    Кластерное хранения данных сессии

    Отличие от обычной конфигурации заключается лишь в servers дополнительных опциях: serializer, persistent, failover, timeout, read_timeout. Про них можно прочитать в официальной документации.

    Redis в режиме cluster может быть настроен двумя способами:

    1. Мультимастер кластер: N мастеров (и могут быть слейвы у каждого).
    2. Обычный кластер: 1 мастер и N слейвов

    Redis cluster в режиме мультимастер, указываются параметры всех мастеров:

    // bitrix/.settings.php
    return [
    //...        
        'session' => [
            'value' => [
                'mode' => 'default',
                'handlers' => [
                    'general' => [
                        'type' => 'redis',   
                        'servers' => [
                            [
                                'port' => 6379,
                                'host' => '127.0.0.1',
                            ],
                            [
                                'port' => 6379,
                                'host' => '127.0.0.2',
                            ],
                            [
                                'port' => 6379,
                                'host' => '127.0.0.3',
                            ],
                            'serializer' => Redis::SERIALIZER_IGBINARY,
                            'persistent' => false,
                            'failover' => RedisCluster::FAILOVER_DISTRIBUTE,
                            'timeout' => null,
                            'read_timeout' => null,
                        ],
                    ],           
                ],
            ]                   
        ] 
    ];

    Redis cluster в режиме 1 мастер + N слейвов. Указываются только параметры мастера блок с опциями опускается:

    return [
        'session' => [
            'value' => [
                'mode' => 'default',
                'handlers' => [
                    'general' => [
                        'type' => 'redis',
                        'servers' => [
                            [
                                'port' => '30015',
                                'host' => '127.0.0.1'
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ];

    Настройка для разделённой сессии:

    // bitrix/.settings.php
    return [
    //...        
        'session' => [
            'value' => [
                'lifetime' => 14400, // +
                'mode' => 'separated', // +
                'handlers' => [
                    'kernel' => 'encrypted_cookies',  // +  
                    'general' => [
                        'type' => 'redis',   
        		    'port' => '6379',
        		    'host' => '127.0.0.1',
                    ],           
                ],
    
            ]                   
        ] 
    ];

    Memcache

    // bitrix/.settings.php
    return [
    //...        
        'session' => [
            'value' => [
                'mode' => 'default',
                'handlers' => [
                    'general' => [
                        'type' => 'memcache',   
        		    'port' => '11211',
        		    'host' => '127.0.0.1',
                    ],           
                ],
    
            ]                   
        ] 
    ];

    Кластерное хранения данных сессии

    Если необходимо создать кластер из memcache серверов, то достаточно добавить настройку servers.

    // bitrix/.settings.php
    return [
    //...        
        'session' => [
            'value' => [
                'mode' => 'default',
                'handlers' => [
                    'general' => [
                        'type' => 'memcache',   
                        'servers' => [
                            [
                                'port' => 11211,
                                'host' => '127.0.0.1',
                                'weight' => 1, //про настройку weight читайте внимательно в документации по memcahe
                            ],
                            [
                                'port' => 11211,
                                'host' => '127.0.0.2',
                            ],
                        ],
                    ],           
                ],
            
            ]                   
        ] 
    ];

    Настройка для разделённой сессии:

    // bitrix/.settings.php
    return [
    //...        
        'session' => [
            'value' => [
                'lifetime' => 14400, // +
                'mode' => 'separated', // +
                'handlers' => [
                    'kernel' => 'encrypted_cookies',  // +  
                    'general' => [
                        'type' => 'memcache',   
        		    'port' => '11211',
        		    'host' => '127.0.0.1',
                    ],           
                ],
    
            ]                   
        ] 
    ];

    Mysql

    Данные хранятся в таблице b_user_session

    // bitrix/.settings.php
    return [
    //...        
        'session' => [
            'value' => [
                'mode' => 'default',
                'handlers' => [
                    'general' => [
                        'type' => 'database',       
                    ]           
                ],         
            ]                   
        ] 
    ];

    Настройка для разделённой сессии:

    // bitrix/.settings.php
    return [
    //...        
        'session' => [
            'value' => [
                'lifetime' => 14400, // +
                'mode' => 'separated', // +
                'handlers' => [
                    'kernel' => 'encrypted_cookies',  // +  
                    'general' => [
                        'type' => 'database',       
                    ]           
                ],         
            ]                   
        ] 
    ];

    Работа с сессиями

    Продукт допускает работу с сессиями следующим образом:

    Хранение сессий в memcached

    Для включения хранения сессий в memcached в старом ядре необходимо в файле /bitrix/php_interface/dbconn.php установить следующие константы:

    define('BX_SECURITY_SESSION_MEMCACHE_HOST', 'localhost');
    define('BX_SECURITY_SESSION_MEMCACHE_PORT', 11211);

    Либо, в случае использования unix-socket:

    define('BX_SECURITY_SESSION_MEMCACHE_HOST', 'unix:///path/to/memcached.sock');
    define('BX_SECURITY_SESSION_MEMCACHE_PORT', 0);

    После этого, включить в файле [ds]bitrix/.settings.php[/ds][di]Ядро поддерживает четыре варианта для хранения (файлы, redis, database, memcache) данных сессии. Способ хранения описывается в bitrix/.settings.php в секции ‘session’.

    Подробнее …[/di] хранение сессий в базе данных средствами ядра.

    Данный способ хранения сессий дает следующие преимущества:

    • нет необходимости следить за количеством старых сессий на нагруженном проекте;
    • возможность разделять сессии между серверами в кластере;
    • возможность использовать не ожидающую получения блокировки сессию;
    • возможность использовать виртуальные сессии.

    Хранение сессий в БД имеет те же преимущества что и хранение сессий в memcached, но оно значительно более медленное. Поэтому рекомендуем для этой цели использовать memcached взамен БД.

    Неблокирующие сессии

    Одной из проблем больших проектов с множественными Ajax запросами, является частые блокировки хитов одного пользователя на ожидание получения блокировки сессии. Особенно это актуально для «Битрикс24 в коробке», где во многих местах прикрепленные к сущностям файлы отдаются пользователю, после проверки прав на PHP. Поэтому на страницах возможно построение «лесенки», из-за ожидания получения блокировки сессии. Включить не блокирующую сессию можно установкой константы, до подключения ядра продукта:

    define('BX_SECURITY_SESSION_READONLY', true);

    После этого сессия читается из memcached или БД не ожидая получения блокировки. Внутри продукта данная функциональность используется например при отдаче файлов.

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

    Виртуальные сессии

    Неблокирующих сессий достаточно для большинства случаев. Но в некоторых ситуациях, когда сессия нам не нужна совсем, использование неблокирующей сессии — избыточно, так как сессия будет создаваться в случае ее отсутствия. В результате при большом количестве не связанных между собой хитов, будет создано большое количество «мусорных» сессий. Примером таких хитов, являются хиты REST’а.

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

    define('BX_SECURITY_SESSION_VIRTUAL', true);

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

    Примечание: Если у вас «Битрикс24 в коробке», проект с большим количеством ajax запросов или много файлов отдается с проверкой прав (например в блогах, соцсети, форуме), то лучше использовать хранение сессий в memcached средствами ядра.

    Шифрованные cookies

      Шифрованные куки

    Шифрованные [ds]куки[/ds][di]
    Cookie — это текстовая строка информации, которую веб-сервер передает в браузер посетителя сайта и которая

    сохраняется в файле на устройстве посетителя сайта

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

    Подробнее…[/di] (BitrixMainWebCryptoCookie) позволяют отправлять данные пользователю, не раскрывая содержимое и не позволяя изменять данные внутри. Доступно с версии main 20.5.400.

    Конфигурация

    Чтобы ядро могло шифровать данные необходимо указать в настройках /bitrix/.settings.php crypto_key. По умолчанию, в новых дистрибутивах он генерируется автоматически.

    Если он отсутствует, то добавьте его в ручную в файл настроек ядра:

    <?php
    return [
        //...
        'crypto' => [
            'value' => [
                'crypto_key' => 'mysupersecretphrase',
                //советуем устанавливать 32-х символьную строку из a-z0-9,
            ],
            'readonly' => true,
        ]
    
        //...
    ];
    

      Примеры использования

    Установка Cookie

    Чтобы установить шифрованную cookie, достаточно создать объект, как в сниппете ниже добавить в нужный Response:

    $cookie = new BitrixMainWebCryptoCookie('someName', 'secret value');
    BitrixMainContext::getCurrent()->getResponse()->addCookie($cookie);

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

    В итоге в http-ответе будет содержаться cookie someName cо значением -crpt-someName_0. И cookie someName_0 уже с шифрованным значением вида DRMg6jrwXO1aUxTvdyBYyT-3_bCqomI9MMN_enurA5abplMm2OiSlNdu_1zgjbkKT_3D3uT8366.

    Чтение Cookie

    Чтобы получить расшифрованное значение cookie, достаточно использовать стандартное API ядра по работе с cookies:

    $httpRequest = BitrixMainContext::getCurrent()->getRequest();
    
    echo $httpRequest->getCookie('someName');
    //secret value

    Ядро автоматически определяет, что cookie шифрованная или нет, распаковывает значение и дешифрует. Если значение не удаётся расшифровать, то будет получено пустое значение.

    Использование постранички для массивов данных

    Пример реализации выборки из нескольких инфоблоков с постраничной навигацией и сортировкой.

    Задача

    1. Выбрать список элементов из нескольких инфоблоков в таблицу;
    2. Иметь возможность сортировки элементов;
    3. Должна работать постраничная навигация

    Решение

    Этот пример можно использовать при небольшом количестве выбираемых элементов (максимум до 100), естественно с кэшированием.

    Соберём все данные в ассоциативный массив, например вот такой:

    [ITEM] => Array
            (
                [0] => Array
                    (
                        [CITY_NAME] => value
                        [CITY_DETAIL_URL] => value
                        [OBJECT_NAME] => value
                        [OBJECT_ID] => 2487
                        [DATE_CREATE] => 02.07.2006
                        [STATUS] => Y
                        [PAID_STATUS] => Y
                        [DETAIL_OBJECT_URL] => value
                    )
    
                [1] => Array
                    (
                        [CITY_NAME] => value
                        [CITY_DETAIL_URL] => value
                        [OBJECT_NAME] => value
                        [OBJECT_ID] => 2489
                        [DATE_CREATE] => 02.07.2006
                        [STATUS] => Y
                        [PAID_STATUS] => N
                        [DETAIL_OBJECT_URL] => value
                    )

    Теперь нужно отсортировать массив $arResult['ITEM'], для этого пишем класс:

    class CCabinet_SortObject {
    
    	function __cmp_ValueOf($a, $b, $name, $order) {
    		if(is_set($a[$name]) && is_set($b[$name])) {
    			if($order == 'ASC')
    				return ($a[$name]<$b[$name])?true:false;
     			elseif($order == 'DESC')
     				return ($b[$name]>$a[$name])?false:true;
    		}
    	}
    
    	function cmp_STATUS_ASC($a, $b) {
    		return CCabinet_SortObject::__cmp_ValueOf($a, $b, "STATUS", "ASC");
    	}
    
    	function cmp_STATUS_DESC($a, $b) {
    		return CCabinet_SortObject::__cmp_ValueOf($a, $b, "STATUS", "DESC");
    	}
    
    	function cmp_NAME_ASC($a, $b) {
    		return CCabinet_SortObject::__cmp_ValueOf($a, $b, "OBJECT_NAME", "ASC");
    	}
    
    	function cmp_NAME_DESC($a, $b) {
    		return CCabinet_SortObject::__cmp_ValueOf($a, $b, "OBJECT_NAME", "DESC");
    	}
    
    	function cmp_CITY_ASC($a, $b) {
    		return CCabinet_SortObject::__cmp_ValueOf($a, $b, "CITY_NAME", "ASC");
    	}
    
    	function cmp_CITY_DESC($a, $b) {
    		return CCabinet_SortObject::__cmp_ValueOf($a, $b, "CITY_NAME", "DESC");
    	}
    
    	function cmp_DATE_DESC($a, $b) {
    		if ($a["DATE_CREATE"] == $b["DATE_CREATE"]) {
            	return 0;
    	    }
    	    return ($a["DATE_CREATE"] > $b["DATE_CREATE"]) ? -1 : 1;
    	}
    
    	function cmp_DATE_ASC($a, $b) {
    		if ($a["DATE_CREATE"] == $b["DATE_CREATE"]) {
            	return 0;
    	    }
    	    return ($a["DATE_CREATE"] < $b["DATE_CREATE"]) ? -1 : 1;
    	}
    
    }

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

    usort($arResult['ITEM'], array("CCabinet_SortObject", "cmp_".$arParams['SORT_BY']."_".$arParams['SORT_ORDER']));

    После чего нам нужно разбить массив постранично используя API:

    $rs_ObjectList = new CDBResult;
    $rs_ObjectList->InitFromArray($arResult['ITEM']);
    $rs_ObjectList->NavStart(10, false);
    $arResult["NAV_STRING"] = $rs_ObjectList->GetPageNavString("", 'komka.cabinet');
    $arResult["PAGE_START"] = $rs_ObjectList->SelectedRowsCount() - ($rs_ObjectList->NavPageNomer - 1) * $rs_ObjectList->NavPageSize;
    while($ar_Field = $rs_ObjectList->Fetch())
    {
    $arResult['_ITEM'][] = $ar_Field;
    }

    Работа с БД

    Общая архитектура классов API для работы с базами данных:

    • Пул соединений BitrixMainDataConnectionPool управляет соединениями, параметры которых настроены в файле настроек .settings.php. В пуле есть соединение по умолчанию и может быть набор дополнительных именованных соединений. Например, соединений к другой базе данных.
    • Конкретные классы соединений наследуют абстрактный класс BitrixMainDBConnection. Для разных типов баз данных реализованы разные конкретные классы.
    • Конкретные классы формирования SQL запросов, наследующие BitrixMainDBSqlHelper. Они помогают сформировать запрос не опускаясь до синтаксиса конкретной базы данных.
    • Конкретные классы для работы с результатом выполнения запроса, наследующие BitrixMainDBResult.

    Эти классы позволяют работать с базами данных на низком уровне, но это необходимо в небольшом числе случаев. Предпочтительно работать через ORM, которая позволяет программировать только на уровне бизнес-логики.

  • Получение соединения, именованные соединения
  • Разные формы вызова выполнения запроса
  • Получение результатов запроса
  • Работа со временем
  • Работа с несколькими базами данных
  • Поддержка нового типа БД
  • Получение соединения, именованные соединения

    Получить соединение можно через приложения, которое, кроме всего прочего, является точкой входа. С этой точки входа можно получить экземпляры «звездных» объектов для данного приложения, которые нужны всем (или почти всем) страницам или компонентам данного приложения.

    $connection = BitrixMainApplication::getConnection();
    $sqlHelper = $connection->getSqlHelper();
    
    $sql = "SELECT ID FROM b_user WHERE LOGIN = '".$sqlHelper->forSql($login, 50)."'";
    
    $recordset = $connection->query($sql);
    while ($record = $recordset->fetch())
    {
       ***

    Разные формы вызова выполнения запроса

    Соответственно, через приложение выполняется запрос в разной форме: просто запрос, запрос с указанием лимита на записи, скалярный запрос либо запрос «вообще».

    $result1 = $connection->query($sql);
    $result2 = $connection->query($sql, $limit);
    $result3 = $connection->query($sql, $offset, $limit);
    
    $cnt = $connection->queryScalar("SELECT COUNT(ID) FROM table");
    
    $connection->queryExecute("INSERT INTO table (NAME, SORT) VALUES ('Название', 100)")

    Получение результатов запроса

    $connection = BitrixMainApplication::getConnection();
    $sqlHelper = $connection->getSqlHelper();
    
    $sql = "SELECT ID FROM b_user WHERE LOGIN = '".$sqlHelper->forSql($login, 50)."' ";
    
    $recordset = $connection->query($sql);
    while ($record = $recordset->fetch())
    {
       ***

    Типизированные данные возвращаются сразу в виде типа, а не в виде строк или чисел.

    Модификация результата:

    $connection = BitrixMainApplication::getConnection();
    $recordset = $connection->query("select * from b_iblock_element", 10);
    $recordset->addFetchDataModifier(
       function ($data)
       {
           $data["NAME"] .= "!";
           return $data;
       }
    );
    
    while ($record = $recordset->fetch(BitrixMainTextConverter::getHtmlConverter()))
       {
          $data[] = $record;
       }

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

    Работа со временем

    При проектировании своей БД возникает вопрос, какой тип данных использовать: datetime или timestamp? При работе с БД приведение времени к GMT является минимально необходимым. Чтобы меньше зависеть от настроек сервера, лучше использовать datetime. В этом случае текущее значение времени придется получать на PHP.

    Работа с несколькими базами данных

    В рамках работы ORM можно работать с несколькими базами данных. Для этого в файле /bitrix/.settings.php в секции connections создается несколько записей для работы с базой:

    'default' =>
        array(
            'className' => '\Bitrix\Main\DB\MysqlConnection',
            'host' => 'localhost', 
            'database' => 'site', 
            'login' => 'user', 
            'password' => 'passwd',
            'options' => 2,
            ),
    'old_db' =>
        array(
            'className' => '\Bitrix\Main\DB\MysqlConnection',
             'host' => 'localhost',
             'database' => 'old_db',
             'login' => 'user',
             'password' => 'passwd',
             'options' => 2,
     ), 

    Таким образом подключены 2 базы: default (по умолчанию Bitrix Framework использует это подключение) и old_db (указаны параметры подключения ко 2-й базе данных). Обращаться к old_db нужно так:

    $connection = BitrixMainApplication::getConnection('old_db');
    $sqlHelper = $connection->getSqlHelper();
    
    $sql = "SELECT ID FROM b_user WHERE LOGIN = '".$sqlHelper->forSql($login, 50)."' ";
    
    $recordset = $connection->query($sql);
    while ($record = $recordset->fetch())
    {
       *** 

    В параметре include_after_connected можно указать путь к файлу, который будет подключен и выполнен после первого соединения с дополнительной БД:

    'old_db' => array(
      'className' => '\Bitrix\Main\DB\MysqlConnection',
      'host' => 'localhost',
      'database' => 'old_db',
      'login' => 'user',
      'password' => 'passwd',
      'options' => 2,
      'include_after_connected' => $_SERVER["DOCUMENT_ROOT"].'/local/php_interface/after_connect_d7_old_db.php',
    ),
    

    Сам файл after_connect_d7_old_db.php

    <?
    $connectionOld = BitrixMainApplication::getConnection("old_db");
    $connectionOld->queryExecute("SET NAMES 'utf8'");
    $connectionOld->queryExecute("SET collation_connection = 'utf8_unicode_ci'");
    ?>

    Для привязки сущности ORM к конкретному соединению переопределите в своем *Table классе метод BitrixMainORMDataDataManager::getConnectionName().

    Поддержка нового типа БД

    В редких случаях возникает необходимость подключения баз данных к которым невозможно использовать встроенный драйвер для PHP (например, у заказчика чрезвычайно устаревшая (или наоборот, слишком свежая) версия СУБД. Чтобы добавить в Bitrix Framework поддержку нового типа БД, необходимо:

    • Создать класс подключения (наследник BitrixMainDBConnection). В нем определить все базовые операции с БД: подключение, отключение, выполнение произвольного запроса, работу с транзакциями.
    • Создать класс SQL-хелпер (наследник BitrixMainDBSqlHelper) и возвращать его экземпляр в методе createSqlHelper. Класс предназначен для самой низкоуровневой работы с БД: добавляет экранирование, работает с датами, предоставляет доступ к базовым SQL-функциям и так далее.
    • Создать класс для результата выборки (наследник BitrixMainDBResult). В нем требуется определить методы-обертки над традиционными функциями работы с результатом выборки.

    Примечание: реальный пример: использование ORM для подключения из PHP к MSSQL в Linux от компании «Интерволга».

    Пример работы с БД

    Практический пример работы с Базой данных через API D7 на основе создания собственного компонента. Создадим компонент:

    <?php
    
    class d7SQL extends CBitrixComponent
    {
        var $connection;
        var $sqlHelper;
        var $sql;
    
        function __construct($component = null)
        {
            parent::__construct($component);
            $this->connection = BitrixMainApplication::getConnection();
            $this->sqlHelper = $this->connection->getSqlHelper();
    
            //Строка запроса. Выбираем все логины, активных пользователей
            $this->sql = 'SELECT LOGIN FROM b_user WHERE ACTIVE = ''.$this->sqlHelper->forSql('Y', 1).'' ';
        }
    
        /*
         * Возвращаем все значения
         */
        function var1()
        {
            $recordset = $this->connection->query($this->sql);
            while ($record = $recordset->fetch())
            {
                $arResult[]=$record;
            }
    
            return $arResult;
        }
    
        /*
         * Возвращаем первые два значения
         */
        function var2()
        {
            $recordset = $this->connection->query($this->sql,2);
            while ($record = $recordset->fetch())
            {
                $arResult[]=$record;
            }
    
            return $arResult;
        }
    
        /*
         * Возвращаем два значения, отступая два элемента от начала
         */
        function var3()
        {
            $recordset = $this->connection->query($this->sql,2,2);
            while ($record = $recordset->fetch())
            {
                $arResult[]=$record;
            }
    
            return $arResult;
        }
    
        /*
         * Возвращаем сразу первый элемент из запроса
         */
        function var4()
        {
            $arResult = $this->connection->queryScalar($this->sql);
    
            return $arResult;
        }
    
        /*
         * Выполняем запрос, не возвращая результат, т. е. INSERT, UPDATE, DELETE
         */
        function var5()
        {
            $this->connection->queryExecute('UPDATE b_user SET ACTIVE = 'N' WHERE LOGIN='test' ');//Заменить на UPDATE
        }
    
        /*
         * Модифицируем результат
         */
        function var6()
        {
            $recordset = $this->connection->query($this->sql);
            $recordset->addFetchDataModifier(
                function ($data)
                {
                    $data["LOGIN"] .= ": Логин пользователя";
                    return $data;
                }
            );
            while ($record = $recordset->fetch())
            {
                $arResult[]=$record;
            }
    
            return $arResult;
        }
    
        public function executeComponent()
        {
            //$this->arResult = $this->var1();
    
            //$this->arResult = $this->var2();
    
            //$this->arResult = $this->var3();
    
            //$this->arResult = $this->var4();
    
            //$this->var5();
    
            $this->arResult = $this->var6();
    
            $this->includeComponentTemplate();
        }
    };

    В коде объявлены три переменные:

    1. connection — хранит подключение к базе данных;
    2. sqlHelper— хранит объект конкретного класса формирования sql запросов;
    3. sql — sql запрос.

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

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

    Через приложение выполняется запрос и получаются все значения, соответствующие значению.

    Функция var1: в ней осуществляется запрос и с помощью fetch получаются результаты. Типизированные данные возвращаются сразу в виде типа, а не в виде строк или чисел.

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

    Возвращаем первые два значения

    Функция var3. Выполняется тот же самый запрос, но указываются два дополнительных параметра. Такая запись означает, то, что возвратятся два элемента. Это последний параметр. И эти элементы возвращаются нам начиная со второй позиции. Это второй параметр. То есть отступаем два элемента и отдаем два, начиная с третьего элемента.

    Возвращаем два значения, отступая два элемента от начала

    Функция var4 — скалярный запрос, то есть когда возвращается первый, единственный результат выборки.

    Возвращаем сразу первый элемент из запроса

    Функция var5 — выполнение запроса, без получения результата. Это нужно в случае INSERT, UPDATE, DELETE.

    Выполняем запрос, не возвращая результат

    Функция var6 — модификация результата. Смотрим . С помощью метода addFetchDataModifier объявляется функцию, которая на вход принимает массив результата для одного элемента и после модификации его возвращает. В нашем случае не сложный пример: просто к полю логин после двоеточия добавляется текст Логин пользователя.

    В метод fetch можно передать конвертер. Выглядит это так:

    <?
    $record = $recordset->fetch(BitrixMainTextConverter::getHtmlConverter())

    Допустимо использовать методы BitrixMainTextConverter::getHtmlConverter и BitrixMainTextConverter::getXmlConverter. Соответственно, они подготавливают к выводу в html и в xml. Происходит преобразование специальных символов в html сущности.

    Миграция на MySQL

    Внимание! С 1 января 2017 года поддержка продуктов «1С-Битрикс» на Oracle Database и MS SQL Server стала ограниченной, заказчики не могут скачать обновления продукта платформы и воспользоваться возможностями новых релизов.

    Задачу миграции Базы данных сайта с Oracle или MSSQL на MySQL можно с помощью мигратора. «Переезд» с его помощью возможен с Oracle или MSSQL только в одну сторону, на MySQL.

    Мигратор работает на PHP не ниже версии 5.3.

    Порядок действий при миграции:

    1. Установите драйвера PHP для БД: mysql, oracle, mssql.
    2. Установите на БД MySQL редакцию 1С-Битрикс аналогичную используемой вами. Обновите её до той же версии, что и исходная установка.
    3. В файле config.php укажите данные доступа к исходной и конечной базам данных.
    4. Запустите конверсию командой "./converter.php convert oracle|mssql mysql".
    5. Внимательно отслеживайте диагностику, думайте дважды перед подтверждением операций. Детали процесса можно изучить в лог-файле.
    6. Если администратор не уверен в том с какими ключами нужно запускать конвертацию, то используйте команду "./converter.php" без параметров.
    7. Вручную скопируйте файлы из старой установки на новую.

    Смена кодировки сайта

    Рассмотрим общий порядок конвертации сайта с кодировки cp1251 в UTF-8.

    Внимание! Прежде чем приступить к конвертации сайта обязательно сделайте резервную копию сайта и базы данных.

    Настоятельно рекомендуем

    предварительно потренироваться выполнять конвертацию на отдельной копии сайта. Конвертация сайта сложная операция и каждый случай индивидуален. При её выполнении высока вероятность потерять важные данные, если что-то пойдет не так!

      Общий порядок действий

    Редактировать файлы и вносить правки на сервере можно подключаясь по SSH.

    Общий порядок действий:

    1. В региональных настройках Настройки > Настройки продукта > Языковые параметры > Региональные настройки сменитe кодировку на UTF-8 для всех языков;
    2. mbstring.func_overload до версии 20.100.0 модуля main

    3. Установите в файле настроек php.ini значение default_charset = "utf8";

      Расположение файла настроек php.ini можно посмотреть заранее в административном разделе на странице [dw]Настройки PHP[/dw][di]Страница Настройки PHP (Настройки > Инструменты > Диагностика > Настройки PHP) служит для отображения информации о текущих настройках PHP.

      Подробнее в курсе Администратор. Базовый[/di] (Loaded Configuration File) или с помощью PHP функции phpinfo().

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

    4. Добавьте в /bitrix/php_interface/dbconn.php
      define("BX_UTF", true);
      

      В этом же файле удалите строки, относящиеся к кодировке cp1251:

      setlocale(LC_ALL, 'ru_RU.CP1251');
      setlocale(LC_NUMERIC, 'C');
      mb_internal_encoding("Windows-1251");
      
    5. Установите значение ‘value’ => true для utf_mode в файле /bitrix/.settings.php:
      utf_mode =>
          array(
              'value' => true,
              'readonly' => true,
          ),
      
    6. Перекодируйте всю базу данных в UTF-8. Вероятнее всего придётся обращаться за помощью к администратору сервера.
    7. Установите в файле /bitrix/php_interface/after_connect.php
      $DB->Query("SET NAMES 'utf8'");
      $DB->Query('SET collation_connection = "utf8_unicode_ci"');
      

      и в файле /bitrix/php_interface/after_connect_d7.php

      $this->queryExecute("SET NAMES 'utf8'");
      $this->queryExecute('SET collation_connection = "utf8_unicode_ci"');
      //До версии main 22.0 вместо $this использовалась переменная $connection.
    8. Установите в /.htaccess:
      php_value default_charset utf-8
      
    9. Перекодируйте все файлы сайта в UTF-8.
    10. Сбросьте весь кеш;
    11. Выйдите и зайдите заново на сайт чтобы обновить данные сессии.

      База данных

    Для конвертации базы (БД) потребуется сменить кодировку самой базы, всех её таблиц и всех текстовых полей таблиц. НЕ выполняйте конвертацию БД из административной части. Используйте для этого другие доступные средства.

    В простом случае (без сериализованных данных) перекодировать базу данных и все таблицы можно следующим образом:

    • Изменить кодировку самой базы данных сайта:
      ALTER DATABASE имя_базы_данных charset=utf8;
      
    • Изменить кодировку соединения с базой данных:
      SET NAMES 'utf8'
      
      ALTER DATABASE database_name CHARACTER SET utf8 COLLATE utf8_unicode_ci;
      
    • Выполним запрос, который позволит найти все таблицы базы данных и сформировать запрос на смену кодировки для каждой:
      SELECT CONCAT('ALTER   TABLE `', t.`TABLE_SCHEMA`, '`.`', t.`TABLE_NAME`, '` CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;') as sqlcode
      FROM `information_schema`.`TABLES` t
      WHERE 1
      AND t.`TABLE_SCHEMA` = 'имя_базы_данных'
      ORDER BY 1
      ;
      
    • В качестве ответа получим список запросов вида:
      ALTER TABLE `имя_базы_данных`.`имя_таблицы` CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
      
    • Выполните все запросы. База данных и таблицы перекодированы.

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

      Файлы

    В простом варианте, когда все файлы сайта в кодировке cp1251, перекодировать их в UTF-8 можно выполнив такую команду в корневой папке сайта (для UNIX систем):

    // переходим в корневую папку сайта. Например:
    cd /var/www/html/ 
    
    // выполняем команду для перекодирования файлов
    find . -name '*.php' -type f -exec iconv -fcp1251 -tutf8 -o /tmp/tmp_file {} ; -exec mv /tmp/tmp_file {} ;

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

    Использование внешних программ или конвертация файлов вручную

    Часто при использовании внешних программ для конвертации в файлы добавляется специальная последовательность символов, так называемый BOM. Эти символы должны находиться только вначале файла, а поскольку итоговая страница является составной из нескольких php файлов, то спецсимволы появляются в теле страницы. Если делаете конвертацию файлов вручную — не сохраняйте с BOM!

      Советы и ссылки

    Основные шаги по конвертации сайта выполнены. Если после конвертации возникают ошибки при открытии сайта, включите режим отладки 'debug' => true в файле /bitrix/.settings.php. Это позволит видеть где и какие возникают ошибки.

    Обязательно выполните [dw]проверку системы[/dw][di]Форма Проверка системы (Настройки > Инструменты > Проверка системы) предназначена для всесторонней проверки соответствия параметров системы, на которой осуществляется функционирование проекта, минимальным и рекомендуемым техническим требованиям продукта.

    Подробнее в курсе Администратор. Базовый.[/di]. По результатам проверки будет видно, что ещё нужно исправить. Пользуйтесь подсказками под знаками вопроса справа.

    Если возникли ошибки с таблицами базы данных (последняя строка проверки), можно посмотреть [dw]логи в журнале[/dw][di][/di]. В конце файла логов указаны будут указаны запросы, с помощью которых можно исправить эти ошибки. Перед началом исправления рекомендуется сделать копию базы данных.

    Список ссылок по теме:

    • Конвертация сайта из cp1251 в UTF-8 (блог разработчиков);
    • Выбор кодировки сайта;
    • Настройка параметров ядра;
    • Проверка системы в курсе Администратор. Базовый.

    Кастомизация Административной части

    Административная панель управления — HTML-код, который может быть выведен авторизованному пользователю при наличии у него достаточных прав на операции, представленные на панели управления. HTML-код представляет из себя область с кнопками, в самом верху страницы, каждая из которых предназначена для той или иной операции.

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

    Добавить кнопку в панель можно с помощью функции CMain::AddPanelButton. Эту же функцию можно использовать в скрипте /bitrix/php_interface/include/add_top_panel.php, который автоматически будет подключен при выводе панели.

    Панель управления имеет два основных режима:

    • Сайт — режим для работы над содержимым сайта.
    • Администрирование — административный раздел для полнофункционального управления всем проектом.

    Подробную информацию о работе с элементами управления в интерфейсе Эрмитаж смотрите в курсе Контент-менеджер. Исторически в Bitrix Framework существовало еще два вида административной панели. Оба они похожи между собой (но сильно отличаются от Эрмитажа), вы их можете встретить в давно не обновлявшихся проектах. И работа с ними описана в курсе Интерфейс до версии 9.5.

    • Эрмитаж с точки зрения разработчика

    Интерфейс «Эрмитаж» с точки зрения разработчика

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

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

    • управлять параметрами текущего раздела;
    • перейти к редактированию текущей страницы и включаемых областей;
    • добавить и изменить меню текущего раздела;
    • настроить параметры компонентов;
    • быстро перейти в административный раздел сайта;
    • и многое другое.

    Примечание: С подробным описанием интерфейса панели вы можете ознакомиться в курсе Контент-менеджер.

    Код для подключения административной панели задается в служебной области шаблона дизайна сайта сразу после тега <body>.

    <?
    $APPLICATION->ShowPanel();
    ?>
    

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

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

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

    Примечание: в концепции Эрмитажа основные действия выносятся в toolbar, остальные остаются в контекстном меню.

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

    • Нотификации для собственных модулей
    • Добавляем нотификатор в Битрикс 12 (блог)
    • Удаляем Стикеры и Рейтинги из меню Админки (блог)

    Добавление кнопок на панель управления

    При создании собственных проектов может возникнуть потребность в создании новых кнопок на Панели управления. Добавление кнопок на панель управления можно осуществить следующим образом:

    <?$APPLICATION->AddPanelButton(
    	Array(
    		"ID" => "ID кнопки", //определяет уникальность кнопки
    		"TEXT" => "Название кнопки",
    		"TYPE" => "BIG", //BIG - большая кнопка, иначе маленькая
    		"MAIN_SORT" => 100, //индекс сортировки для групп кнопок
    		"SORT" => 10, //сортировка внутри группы
    		"HREF" => "URL для перехода", //или javascript:MyJSFunction())
    		"ICON" => "icon-class", //название CSS-класса с иконкой кнопки
    		"SRC" => "путь к иконке кнопки",
    		"ALT" => "Текст всплывающей подсказки", //старый вариант
    		"HINT" => array( //тултип кнопки
    			"TITLE" => "Заголовок тултипа",
    			"TEXT" => "Текст тултипа" //HTML допускается
    		),
    		"HINT_MENU" => array( //тултип кнопки контекстного меню
    			"TITLE" => "Заголовок тултипа",
    			"TEXT" => "Текст тултипа" //HTML допускается
    		),
    		"MENU" => Array(
    			Array( //массив пунктов контекстного меню
    				"TEXT" => "название пункта",
    				"TITLE" => "всплывающая подсказака над пунктом",
    				"SORT" => 10, //индекс сортировки пункта
    				"ICON" => "", //иконка пункта
    				"ACTION" => "Javascript-код",
    				"SEPARATOR" => true, //определяет пункт-разделитель
    				"DEFAULT" => true, //пункт по умолчанию?
    				"MENU" => Array() //массив подменю
    				)
    			)
    		),
    	$bReplace = false //заменить существующую кнопку?
    );	
    ?>
    

    Есть несколько вариантов добавления. В зависимости от того, что нужно, кнопку можно добавлять:

    • в компоненте
    • на странице
    • в шаблоне сайта
    • на событии OnBeforeProlog

    Результат добавления в шаблон сайта:

    Добавление контекстного меню

    Для добавления пунктов контекстного меню к любой кнопке панели используйте следующий код:

    $APPLICATION -> AddPanelButtonMenu($btnId, $arMenuItem)
    

    где:

    • $btnId – идентификатор кнопки;
    • $arMenuItem – массив пунктов.

    Примечание: пересортировку пунктов согласно индексу сортировки можно выполнить с помощью следующей конструкции:

    "RESORT_MENU" => true

    Toolbar компонента

    Для вывода toolbar’a компонента используйте в component.php следующий код:

    $this->AddIncludeAreaIcons(
    	Array( //массив кнопок toolbar'a
    		Array(
    			"ID" => "Идентификатор кнопки",
    			"TITLE" => "Название кнопки toolbar'a",
    			"URL" => "ссылка для перехода", //или javascript:MyJSFunction ()
    			"ICON" => "menu-delete", //CSS-класс с иконкой
    			"MENU" => Array(
    				//массив пунктов контекстного меню
    			),
    			"HINT" => array( //тултип кнопки
    				"TITLE" => "Заголовок тултипа",
    				"TEXT" => "Текст тултипа" //HTML допускается
    			),
    			"HINT_MENU" => array ( //тултип кнопки контекстного меню
    				"TITLE" => "Заголовок тултипа",
    				"TEXT" => "Текст тултипа" //HTML допускается
    			),
    			"IN_PARAMS_MENU" => true, //показать в контекстном меню
    			"IN_MENU" => true //показать в подменю компонента
    		)
    	)
    );
    //Режим редактирования включён?
    if ($APPLICATION->GetShowIncludeAreas())
    {
    	$this->AddIncludeAreaIcons(Array(
    		//массивы кнопок toolbar'a
    	));
    }

    Страница со списком элементов

    С версии 21.600.0 Главного модуля доступны новые методы объекта грида. Цель их внедрения — упростить и уменьшить объем кода, требующийся для создания своей админской страницы со списком элементов.

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

    Для работы со списками элементов в main есть два класса:

    В коде страниц, использующих эти классы, имеется множество обращений к $_REQUEST для получения текущего режима страницы, текущего группового действия, измененных данных и т.п. Например, таких:

    $bExcel = isset($_REQUEST["mode"]) && ($_REQUEST["mode"] == "excel"); // режим выгрузки в Excel
    
    !isset($_REQUEST["mode"]) || $_REQUEST["mode"]=='list' || $_REQUEST["mode"]=='frame' 
    // все, кроме Excel и окна настроек
    
    switch($_REQUEST['action']) // получение идентификатора групповой операции
    

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

    Режим работы грида

    Грид может работать в нескольких режимах:

    • Простой вывод списка элементов (открытие страницы).
    • Обработка быстрого редактирования элементов.
    • Обновление грида без перезагрузки страницы (например, сортировка).
    • Выгрузка в формат для Excel.
    • Показ формы настроек грида.

    Ранее получение текущего режима сводилось к анализу ключа mode в $_REQUEST. Вместо этого предлагается использовать методы объекта грида:

    • Метод
      public function getCurrentMode(): string // получение режима работы
      

      вернет одну из констант класса:

      • public const MODE_PAGE = 'normal'; — обычный вывод страницы;
      • public const MODE_LIST = 'list'; — обновление содержимого грида без полной перезагрузки;
      • public const MODE_ACTION = 'frame'; — обработка результатов быстрого редактирования;
      • public const MODE_EXPORT = 'excel'; — выгрузка в псевдо-Excel;
      • public const MODE_CONFIG = 'settings'; — показ формы настроек.

    Режим определяется в момент инициализации объекта грида.

    Старый код:

    $sTableID = "tbl_user";
    $lAdmin = new CAdminUiList($sTableID);
    $excelMode = ($_REQUEST["mode"] == "excel");
    

    Новый:

    $sTableID = "tbl_user";
    $lAdmin = new CAdminUiList($sTableID);
    $excelMode = $lAdmin->getCurrentMode() === CAdminList::MODE_EXPORT;
    

    Чтобы каждый раз не писать большой объем кода для сравнения с константой, добавлены вспомогательные методы:

    Метод Описание
    public function isPageMode(): bool Вернет true, если страница с гридом просто открывается в браузере.
    public function isExportMode(): bool Режим выгрузки (Excel).
    public function isAjaxMode(): bool Работа в режиме ajax (как обновление грида, так и обработка быстрого редактирования).
    public function isConfigMode(): bool Показ окна настроек.
    public function isActionMode(): bool Результаты inline-редактирования.
    public function isListMode(): bool Обновления блока грида без обновления страницы.

    Таким образом, предыдущий пример превращается в такой код:

    $sTableID = "tbl_user";
    $lAdmin = new CAdminUiList($sTableID);
    $excelMode = $lAdmin->isExportMode();
    

    Обработка inline-редактирования элементов

    Типичный код обработки на странице

    if ($lAdmin->EditAction())
    {
       if (is_array($_REQUEST['FIELDS']))
       {
          foreach($_REQUEST['FIELDS'] as $ID=>$arFields)
          {
             $ID = (int)$ID;
             if ($ID <= 0)
                continue;
             if (!$lAdmin->IsUpdated($ID))
                continue;
             // дальше код, сохраняющий данные одной строки
          }
       }
    }
    

    можно заменить на такой:

    if ($lAdmin->EditAction())
    {
       foreach ($lAdmin->getEditFields() as $ID=>$arFields)
       {
          //код, сохраняющий данные одной строки
       }
    }
    
    • Метод

      public function getEditFields(): array
      

      возвращает массив записей, которые были изменены (с предварительной проверкой, что ключи непустые).

    • Дополнительно (если в гриде используется вывод и редактирование файлов) есть метод:

      public function convertFilesToEditFields(): void
      

      для копирования присланных файлов в общий массив полей. Т.е. предыдущий код слегка расширяем:

      if ($lAdmin->EditAction())
      {
         $lAdmin->convertFilesToEditFields();
         foreach ($lAdmin->getEditFields() as $ID=>$arFields)
         {
             //код, сохраняющий данные одной строки
         }
      }
      

    Работа с групповыми операциями

    Типичный код обработки групповых операций:

    if ($arID = $lAdmin->GroupAction()
    {
       if ($_REQUEST['action_target']=='selected')
       {
           // в $arID выбираем идентификаторы всех строки
       }
       foreach ($arID as $ID)
       {
          switch ($_REQUEST['action']) // получение ID действия
          {
              ...
          }
       }
    }
    

    можно упростить так:

    if ($arID = $lAdmin->GroupAction()
    {
       $actionId = $lAdmin->getAction();
       if ($actionId === null)
       {
           continue;
       }
       if ($lAdmin-> isGroupActionToAll())
       {
            // в $arID выбираем все идентификаторы строк
       }
       foreach ($arID as $ID)
       {
           switch ($actionId) // действия
           {
               ...
           }
       }
    }
    
    Метод Описание
    public function getAction(): ?string Вернет идентификатор группового действия или null, если ничего не передано.
    public function isGroupActionToAll(): bool Вернет true, если в гриде для действий был выбран пункт «Для всех».

    Контекстное меню элементов списка

    • Установить HTML-атрибут id для блочного тега:
      <div id="<?=$this->GetEditAreaID("идентификатор_области")?>">
      	<!-- контент блока -->
      </div>
    • В component_epilog.php определить кнопки контекстного меню с помощью метода:
          $APPLICATION->SetEditArea($areaId, $arIcons);

      где:

      • $areaId – идентификатор области с контекстным меню;
      • $arIcons – массив иконок контекстного меню.
    • Метод добавляет кнопку, которая открывает указанный URL в popup-окне:
      $this->AddEditAction(
      	"Идентификатор_области",
      	"URL страницы, которая откроется в popup-окне",
      	"Название кнопки в toolbar",
      	Array(
      		"WINDOW" => array("wight"=>780, "height"=>500),
      		"ICON" => "bx-context-toolbar-edit-icon",
      		"SRC" => "/bitrix/images/myicon.gif"
      	)
      );
    • Метод добавляет кнопку удаления элемента:
      $this->AddDeleteAction(
      	"Идентификатор_области",
      	"URL страницы, удаляющая указанный элемент",
      	"Название кнопки",
      	Array(
      		"CONFIRM" => "Вы действительно хотите удалить этот элемент?",
      
      	)
      );

    Примечание: Для названия кнопки можно использовать такой метод:

    CIBlock::GetArrayByID($params["IBLOCK_ID"], "ELEMENT_EDIT")

    где вторым параметром будут как раз названия для кнопок (ELEMENT_EDIT, ELEMENT_ADD, ELEMENT_DELETE).

    Административные страницы в публичке

    • Метод генерирует Javascript, открывающий URL в popup-окне:
      $APPLICATION->GetPopupLink(Array(
      	"URL"=> "URL страницы, которая откроется в popup-окне",
      	"PARAMS" => Array(
      		"width" => 780,
      		"height" => 570,
      		"resizable" => true,
      		"min_width" => 780,
      		"min_height" => 400
      		)
      	)
      );
    • Метод генерирует кнопки управления элементами и разделами инфоблока:
      CIBlock::GetPanelButtons(
      	$IBLOCK_ID = 0, //ID инфоблока
      	$ELEMENT_ID = 0, //ID елемента инфоблока
      	$SECTION_ID = 0, //ID раздела инфоблока
      	$arOptions = Array(
      		"SECTION_BUTTONS" => true, //генерировать кнопки для управления разделами
      		"SESSID" => false, //добавлять ссылку в авторизованный token
      		"RETURN_URL" => "",
      		"LABELS" => Array() //надписи кнопок, по умолчанию берутся из настроек инфоблока
      	)
      );

    Пользовательские формы редактирования элементов

    Описание

    Форма добавления/изменения элементов информационных блоков является одной из самых часто используемых, а в интернет-магазинах или информационных изданиях эта форма однозначно является самой популярной в административном разделе. И несмотря на то, что внешний вид и поля формы изменяются в зависимости от настроек информационного блока, а также можно настроить вид формы редактирования элементов стандартными средствами системы, для специфических задач, иногда этого недостаточно.

    В этом случае в /bitrix/php_interface/include/ следует создать один или два (в зависимости от задачи) дополнительных файла:

    • с формой редактирования элемента;
    • отвечающий за обработку полей элемента перед его сохранением.

    Затем в настройках инфоблока задать пути к этим файлам:

    Файл с формой редактирования элемента

    Создадим в папке /bitrix/php_interface/include/, например, файл iblock_element_edit_my.php, затем скопируем в него код из файла /bitrix/modules/iblock/admin/iblock_element_edit.php от строки:

    <?
    	//////////////////////////
    	//START of the custom form
    	//////////////////////////
    

    до строки:

    	//////////////////////////
    	//END of the custom form
    	//////////////////////////
    

    Важно! Не забудьте в вашем файле подключить необходимые пространства имен (смотрите первые строки файла /bitrix/modules/iblock/admin/iblock_element_edit.php).

    Теперь можно приступать к редактированию файла, т.е. к изменению внешнего вида формы редактирования элемента инфоблока под собственные нужды. (Перед процедурой необходимо отменить настройки формы, если таковые ранее были сделаны.)

    • Вы можете удалить ненужные вам поля инфоблока. Для отображения полей формы используются конструкции следующего вида:
      <?
      $tabControl->BeginCustomField("ACTIVE_TO",
                                    GetMessage("IBLOCK_FIELD_ACTIVE_PERIOD_TO"),
                                    $arIBlock["FIELDS"]["ACTIVE_TO"]["IS_REQUIRED"] === "Y");
      ?>
      	<tr id="tr_ACTIVE_TO">
      		<td><?echo $tabControl->GetCustomLabelHTML()?>:</td>
      		<td><?echo CAdminCalendar::CalendarDate("ACTIVE_TO", $str_ACTIVE_TO, 19, true)?></td>
      	</tr>
      <?
      $tabControl->EndCustomField("ACTIVE_TO", '<input type="hidden" id="ACTIVE_TO" name="ACTIVE_TO" value="'.$str_ACTIVE_TO.'">');
      ?>
      
    • Для отображения в форме свойств элементов инфоблока используется функция _ShowPropertyField():
      <?
      $prop_code = "20"; //идентификатор свойства
      $prop_fields = $PROP[$prop_code];
      $prop_values = $prop_fields["VALUE"];  
      
      $tabControl->BeginCustomField("PROPERTY_".$prop_fields["ID"],
                                    $prop_fields["NAME"],
                                    $prop_fields["IS_REQUIRED"]==="Y"); 
      ?>
      
      <tr id="tr_PROPERTY_<?echo $prop_fields["ID"];?>">
        <td class="adm-detail-valign-top" width="40%"><?if($prop_fields["HINT"]!=""):
      	?><span id="hint_<?echo $prop_fields["ID"];?>"></span>
                <script>BX.hint_replace(BX('hint_<?echo $prop_fields["ID"];?>'), '<?echo CUtil::JSEscape($prop_fields["HINT"])?>');</script>
         <?endif;?><?echo $tabControl->GetCustomLabelHTML();?>:</td>
        <td width="60%"><?_ShowPropertyField('PROP['.$prop_fields["ID"].']',
                                             $prop_fields,
                                             $prop_fields["VALUE"],
                                             (($historyId <= 0) && (!$bVarsFromForm) && ($ID<=0)),
                                             $bVarsFromForm, 50000,
                                             $tabControl->GetFormName(),
                                             $bCopy);?></td>
      </tr>
      
      <?  
      $tabControl->EndCustomField("PROPERTY_".$prop_fields["ID"], $hidden);  
      ?>
      

    При использовании собственной формы на странице редактирования элемента пропадает кнопка Настроить, позволяющая отсортировать и настроить отображение полей формы элемента.

    Чтобы не добавлять механизм сортировки полей в iblock_element_edit_my.php и не отказываться от стандартной функции, необходимо добавить следующий код в файл:

    <?
    // Кнопка "Настроить"
    $aMenu = array();
    if (false == ((true == defined('BT_UT_AUTOCOMPLETE')) && (1 == BT_UT_AUTOCOMPLETE)))
    {
       $link = DeleteParam(array("mode"));
       $link = $GLOBALS["APPLICATION"]->GetCurPage()."?mode=settings".($link <> ""? "&".$link:"");
       $aMenu[] = array(
          "TEXT"=>GetMessage("IBEL_E_SETTINGS"),
          "TITLE"=>GetMessage("IBEL_E_SETTINGS_TITLE"),
          "LINK"=>"javascript:".$tabControl->GetName().".ShowSettings('".urlencode($link)."')",
          "ICON"=>"btn_settings",
       );
       
       $context = new CAdminContextMenu($aMenu);
       $context->Show();
    }
    ?>
    

    Важно! Не забудьте указать путь к данному файлу в настройках инфоблока.

    Файл, отвечающий за обработку полей элемента перед его сохранением

    Чтобы изменить сохраняемые поля, необходимо модифицировать одноименные поля в массивах $_POST и $_FILES, значения всех свойств необходимо модифицировать в массиве $PROP.

    Создадим в /bitrix/php_interface/include/, например, файл iblock_element_edit_before_save.php.

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

    if (strlen($_POST['DETAIL_TEXT'])<=0)
       $error = new _CIBlockError(2, 'DESCRIPTION_REQUIRED', 'Введите текст статьи');

    Конструктор объекта _CIBlockError принимает три параметра: степень серьезности ошибки, произвольный идентификатор и текст ошибки. Если на странице редактирования определить переменную $error со значением этого объекта, то сохранения внесённых изменений не произойдет. Для того чтобы значения, пришедшие из формы, не потерялись, после инициализации переменной $error также инициализируйте переменную $bVarsFromForm=true. Переменная $bVarsFromForm как раз указывает, что значения в полях необходимо показывать те, которые пришли из формы.

    Для автоматического создания маленькой картинки на основе большой воспользуемся функцией BXIBlockAfterSave. Если ее определить до сохранения элемента, то она автоматически будет вызвана после успешного сохранения элемента. Определим ее в начале файла /bitrix/php_interface/include/iblock_element_edit_before_save.php:

    <?
    function BXIBlockAfterSave($arFields)
    {
            $dbr = CIBlockElement::GetByID($arFields['ID']);
            if(($ar = $dbr->Fetch()) && $ar['DETAIL_PICTURE']>0)
            {
                $img_path = $_SERVER['DOCUMENT_ROOT'].CFile::GetPath($ar['DETAIL_PICTURE']);
                $width = 200;
                $height = 200;
                list($width_orig, $height_orig) = getimagesize($img_path);
                if($width && ($width_orig < $height_orig))
                   $width = ($height / $height_orig) * $width_orig;
                else
                   $height = ($width / $width_orig) * $height_orig;
                $image_p = imagecreatetruecolor($width, $height);
                $image = imagecreatefromjpeg($img_path);
                imagecopyresized($image_p, $image, 0, 0, 0, 0, $width, $height, $width_orig, $height_orig);
                $new_img_path = tempnam("/tmp", "FOO").".jpg";
                imagejpeg($image_p, $new_img_path);
                $be = new CIBlockElement();
                $be->Update($arFields['ID'], Array('PREVIEW_PICTURE' => CFile::MakeFileArray($new_img_path)), false);
                @unlink($new_img_path);
            }
    }
    ?>

    Примечание: в вышеприведенном скрипте на основе большой картинки будет создана маленькая и эта картинка будет подставлена в поле для маленькой картинки. Пример работает только с картинками в формате JPG.

    Приведем полный код страницы /bitrix/php_interface/include/iblock_element_edit_before_save.php:

    <?
    if($REQUEST_METHOD=="POST" && strlen($Update)>0 && $view!="Y" && (!$error) && empty($dontsave) && strlen($_POST['DETAIL_TEXT'])<=0)
       $error = new _CIBlockError(2, "DESCRIPTION_REQUIRED", "Введите текст статьи");
    
    function BXIBlockAfterSave($arFields)
    {
            $dbr = CIBlockElement::GetByID($arFields['ID']);
            if(($ar = $dbr->Fetch()) && $ar['DETAIL_PICTURE']>0)
            {
                $img_path = $_SERVER['DOCUMENT_ROOT'].CFile::GetPath($ar['DETAIL_PICTURE']);
                $width = 200;
                $height = 200;
                list($width_orig, $height_orig) = getimagesize($img_path);
                if($width && ($width_orig < $height_orig))
                   $width = ($height / $height_orig) * $width_orig;
                else
                   $height = ($width / $width_orig) * $height_orig;
                $image_p = imagecreatetruecolor($width, $height);
                $image = imagecreatefromjpeg($img_path);
                imagecopyresized($image_p, $image, 0, 0, 0, 0, $width, $height, $width_orig, $height_orig);
                $new_img_path = tempnam("/tmp", "FOO").".jpg";
                imagejpeg($image_p, $new_img_path);
                $be = new CIBlockElement();
                $be->Update($arFields['ID'], Array('PREVIEW_PICTURE' => CFile::MakeFileArray($new_img_path)), false);
                @unlink($new_img_path);
            }
    }
    ?>

    Важно! Не забудьте указать путь к данному файлу в настройках инфоблока.

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

    Дополнительные материалы

    • Создание формы редактирования
    • Создание списка элементов

    Кастомизация административной формы заказа

    С версии 16.0.33 модуля Интернет-магазин доступна возможность кастомизации форм просмотра, редактирования и создания заказа. Функционал основан на использовании специальных событий, тем самым вам не требуется вносить правки в ядро продукта и вы не теряете возможность установки обновлений системы.

    Добавление пользовательской закладки

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

    • OnAdminSaleOrderView — для формы просмотра заказа;
    • OnAdminSaleOrderCreate — для формы создания заказа;
    • OnAdminSaleOrderEdit— для формы редактирования заказа.

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

    В файл bitrix/php_interface/init.php необходимо добавить обработчик события OnAdminSaleOrderEdit:

    BitrixMainEventManager::getInstance()->addEventHandler("main", "OnAdminSaleOrderEdit", array("MyTab", "onInit"));
    
    class MyTab
    {
        public static function onInit()
        {
    	    return array(
                "TABSET" => "MyTab",
                "GetTabs" => array("MyTab", "mygetTabs"),
                "ShowTab" => array("MyTab", "myshowTab"),
                "Action" => array("MyTab", "myaction"),
                "Check" => array("MyTab", "mycheck"),
            ); 
        }
    
        public static function myaction($arArgs)
        {
            // Действие после сохранения заказа. Возвращаем true / false
            // Сообщение $GLOBALS["APPLICATION"]->ThrowException("Ошибка!!!", "ERROR");
            return true;
        }
        public static function mycheck($arArgs)
        {
    	    // Проверки перед сохранением. Возвращаем true / false
            return true;
        }
    
        public static function mygetTabs($arArgs)
        {
    	        return array(array("DIV" => "edit1", "TAB" => "Моя закладка",
    	        "ICON" => "sale", "TITLE" => "Пользовательские настройки",
     	    "SORT" => 1));
        }
    
        public static function myshowTab($divName, $arArgs, $bVarsFromForm)
        {
            if ($divName == "edit1")
            {
                ?><tr><td width="40%">Мое поле:</td>
                <td width="60%"><input type="text" name="myfield"></td></tr><?
            }
        }
    }
    
    

    В обработчике указываем какие методы будут вызываться. Так с помощью методов mygetTabs и myshowTab определяем добавляемые закладки и их содержимое. Метод mycheck определяет действия, которые будут выполняться перед сохранением заказа, а метод myaction — действия, которые будут выполняться при сохранении заказа.

    Кастомизация информационной панели

    Рассмотрим пример добавления новых строчек в информационную панель:

    Следует использовать обработчик построения информационной панели onSaleAdminOrderInfoBlockShow и из него вернуть параметры, которые нам необходимо добавить:

    BitrixMainEventManager::getInstance()->addEventHandler('sale', 'onSaleAdminOrderInfoBlockShow', 'onSaleAdminOrderInfoBlockShow');
    
    function onSaleAdminOrderInfoBlockShow(BitrixMainEvent $event)
    {
    	// $order = $event->getParameter("ORDER");
    
    	return new BitrixMainEventResult(
    			BitrixMainEventResult::SUCCESS,
    			array(
    				array('TITLE' => 'Параметр 1:', 
    				'VALUE' => 'Значение параметра 1', 'ID' => 'param1'),
    				array('TITLE' => 'Параметр 2::', 
    				'VALUE' => '<a href="http://1c-bitrix.ru">Значение 
    				параметра 2</a>'),
    				),
    			'sale'
    			);
    }
    

    Таким образом, вы можете вернуть необходимую дополнительную информацию, которую будете показывать менеджеру.

    Добавление пользовательских блоков

    Пользовательские блоки могут быть созданы наравне со стандартными блоками: они также могут сворачиваться и перетаскиваться:

    Чтобы добавить пользовательские блоки в форму просмотра заказа, следует использовать обработчик события OnAdminSaleOrderViewDraggable:

    BitrixMainEventManager::getInstance()->addEventHandler("main", "OnAdminSaleOrderViewDraggable", array("MyClass1", "onInit"));
    
    class MyClass1
    {
    	public static function onInit()
    		{
    			return array("BLOCKSET" => "MyClass1",
    				"getScripts"  => array("MyClass1", "mygetScripts"),
    				"getBlocksBrief" => array("MyClass1", "mygetBlocksBrief"),
    				"getBlockContent" => array("MyClass1", "mygetBlockContent"),
    				);
    		}
    		
    	public static function mygetBlocksBrief($args)
    		{
    			$id = !empty($args['ORDER']) ? $args['ORDER']->getId() : 0;
    			return array(
    				'custom1' => array("TITLE" => "Пользовательский блок для заказа №".$id),
    				'custom2' => array("TITLE" => "Еще один блок для заказа №".$id),
    				);
    		}
    	
    	public static function mygetScripts($args)
    		{
    			return '<script type="text/javascript">... </script>';
    		}
    		
    	public static function mygetBlockContent($blockCode, $selectedTab, $args)
    		{
    		$result = '';
    		$id = !empty($args['ORDER']) ? $args['ORDER']->getId() : 0;
    		
    		if ($selectedTab == 'tab_order')
    			{
    			if ($blockCode == 'custom1')
    				$result = 'Содержимое блока custom1<br> Номер заказа: '.$id;
    			if ($blockCode == 'custom2')
    				$result = 'Содержимое блока custom2<br> Номер заказа: '.$id;
    			}
    			
    		return $result;
    		}
    }
    

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

    • mygetScripts — нужен, чтобы инициализировать JavaScript, которые вам понадобятся;
    • mygetBlocksBrief — нужен для описания блока;
    • mygetBlockContent — нужен для создания содержимого блока.

    Чтобы добавить пользовательские блоки в форму создания/редактирования заказа, следует использовать обработчики событий OnAdminSaleOrderCreateDraggable/OnAdminSaleOrderEditDraggable:

    BitrixMainEventManager::getInstance()->addEventHandler("main", "OnAdminSaleOrderEditDraggable", array("MyEditClass", "onInit"));
    BitrixMainEventManager::getInstance()->addEventHandler("main", "OnAdminSaleOrderCreateDraggable", array("MyEditClass", "onInit"));
    
    class MyEditClass extends MyClass1
    {
    	public static function onInit()
    		{
    			return array(
    				"BLOCKSET" => "MyEditClass",
    				"check" => array("MyEditClass", "mycheck"),
    				"action" => array("MyEditClass", "myaction"),
    				"getScripts" => array("MyEditClass", "mygetScripts"),
    				"getBlocksBrief" => array("MyEditClass", "mygetBlocksBrief"),
    				"getBlockContent" => array("MyEditClass", "mygetBlockContent"),
    			);
    		}
    
    	public static function myaction($args)
    	{
    		// заказ сохранен, сохраняем данные пользовательских блоков
    		// возвращаем True в случае успеха и False - в случае ошибки
    		// в случае ошибки $GLOBALS["APPLICATION"]->ThrowException("Ошибка!!!", "ERROR");
    		return true;
    	}
    
    	public static function mycheck($args)
    	{
    		// заказ еще не сохранен, делаем проверки
    		// возвращаем True, если можно все сохранять, иначе False
    		// в случае ошибки $GLOBALS["APPLICATION"]->ThrowException("Ошибка!!!", "ERROR");
    		return true;
    	}
    
    	public static function mygetBlockContent($blockCode, $selectedTab, $args)
    	{
    		$result = '';
    		$id = !empty($args['ORDER']) ? $args['ORDER']->getId() : 0;
    	
    		if ($selectedTab == 'tab_order')
    			{
    				if ($blockCode == 'custom1')
    					{
    						$result = 'Содержимое блока custom1 для заказа №'.$id.'
    						<br><input name="custom1" value="cust1">';
    					}
    				if ($blockCode == 'custom2')
    					{
    						$result = 'Содержимое блока custom2 для заказа №'.$id.'
    						<br><input name="custom2" value="cust2" 
    						id="custom2_field">';
    					}
    			}
    		
    			return $result;
    	}
    
    	//вставим итоговую стоимость заказа в кастомное поле №2 на странице создания/изменения заказа
    
    	public static function mygetScripts($args)
    	{
    		$id = !empty($args['ORDER']) ? $args['ORDER']->getId() : 0;
    	
    		return '<script type="text/javascript"> BX.ready(function(){
    			BX.Sale.Admin.OrderEditPage.registerFieldsUpdaters({
    				"TOTAL_PRICES": function(prices){
    					var custom = BX("custom2_field");
    					if (custom2_field)
    						custom2_field.value = prices.PRICE_TOTAL;
    				}	
    			});
    		});	</script>';
    	}
    
    }
    

    В обработчик, помимо методов mygetScripts, mygetBlocksBrief и mygetBlockContent, добавляются еще 2 метода: mycheck и myaction, которые вызываются перед сохранением и при сохранении заказа соответственно.

    Создание компонентов

    Цитатник веб-разработчиков.

    Роман Петров: Методы, которые были актуальны 2 года назад — сейчас могут быть неактуальны из-за появления готовых компонентов, делающих то же самое

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

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

    Типовая последовательность действий

    • В веб-проекте, при составлении ТЗ и проектировании, выявляют и описывают возможные виды собственных компонентов.
    • Определяется пространство имен собственных компонентов, например, с использованием названия проекта. Системные компоненты Bitrix Framework размещены в пространстве имен bitrix, компоненты проекта могут размещаться в пространстве имен, к примеру, citybank.

      Внимание ! Названия создаваемых компонентов не должны пересекаться со стандартными.

    • Определяется, какой стандартный компонент можно взять за основу для создания собственного компонента. В коде стандартных компонентов много примеров типичного и правильного использования API и техник программирования, поэтому рекомендуется брать их за основу.
    • К каждому компоненту 2.0 продумывается интерфейс — какие параметры компонента должны быть доступны администратору веб-сайта для редактирования. Например, для компонента, отображающего прогноз погоды, можно в настройки для администратора вынести свойство Адрес веб-сервиса и Таймаут соединения с веб-сервисом и т.п.
    • Определяется, в каком разделе дерева компонентов в визуальном редакторе необходимо разместить данный компонент.
    • Компонент кодируется. Особое внимание уделяется настройке автокеширования компонента и профилированию его работы — он не должен выполнять запросы к базе данных в режиме кэширования, выполняет минимальное количество запросов к базе данных при устаревании кэша, хранит в кэше только необходимые данные, использует минимально возможный объем оперативной памяти (не сортирует массивы размером с десятки-сотни мегабайт и т.п).

    Порядок создания собственно компонента

    Выделить необходимый php-код в отдельный файл для того, чтобы использовать его потом в виде вызываемого файла несложно. Но компонент еще нужно подключить в систему с помощью файла описания, который опознается ядром Bitrix Framework, в результате чего пользователь видит в визуальном редакторе иконку с названием компонента и может настраивать его свойства.

    Напомним, что компонент – это выделенный в отдельный файл php-код с законченной функциональностью, файл регистрации компонента в системе и описания его параметров, а также файлы локализации.

    • Регистрация компонента
      • Выделение необходимого php-кода в отдельный файл.
      • Создание файла описания .description.php
      • Размещение файлов в папке в собственном пространстве имен.
    • Задание параметров в коде компонента
    • Локализация
      • Подготовка файлов с текстовыми константами для компонента и файла регистрации: /lang/ru/<имя_компонента>/component.php и /lang/ru/<имя_компонента>/.description.php
      • Внесение изменения в код обоих файлов компонента для использования этих констант (подключение файла локализации делается при помощи функции IncludeTemplateLangFile).

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

    При создании компонента для Marketplace нужно создавать мастер установки (аналогично модулю).

    Совет от М. Месилова:

    Если на сайте используются компоненты собственной разработки и требуется их большое обновление, то рекомендуется использовать в префиксе номер версии: news.list.v2 так вы не поломаете старые компоненты по всему сайту и у вас будет возможность сделать новый компонент с потерей обратной совместимости.

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

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

    • [ds]SEF режим[/ds][di]В комплексные компоненты встроена функция генерации ЧПУ. У этих компонентов всегда есть входной параметр SEF_MODE, который может принимать значения Y и N. Если параметр SEF_MODE равен N, то компонент работает с физическими ссылками и все параметры передает через стандартные параметры HTTP запроса.

      Подробнее …[/di]

    а в шаблоне компонента:

    • [ds]JS-класс[/ds][di]Иногда при разработке компонента его шаблон необходимо наделить js-функциональностью, событиями и прочим. Выглядеть это может примерно так:

      Подробнее …[/di],

    • [ds]буферизацию[/ds][di]Усовершенствованные методы буферизации в шаблоне позволяют более не использовать CBitrixComponentTemplate::EndViewTarget() ввиду того, что конец шаблона вызывает завершение буферизации автоматически.

      Подробнее …[/di].

    • Что круче, штатные компоненты или самописные? (блог)

    Дополнительные методы

    Дополнительные методы, доступные в компонентах и шаблонах

    В компонентах и шаблонах можно использовать дополнительные методы из класса CComponentEngine.

    string CComponentEngine::MakePathFromTemplate($pageTemplate, $arParams);

    где:

    $pageTemplate — шаблон вида /catalog/#IBLOCK_ID#/section/#SECTION_ID#.php или catalog.php?BID=#IBLOCK_ID#&SID=#SECTION_ID#,

    $arParams — ассоциативный массив замен параметров, в котором ключ — это название параметра, а значение — это значение параметра. Возвращает путь на основании шаблона пути $pageTemplate и массива замен.

    Пример:

    $url = CComponentEngine::MakePathFromTemplate
    ("/catalog/#IBLOCK_ID#/section/#SECTION_ID#.php", 
            array( 
                 "IBLOCK_ID" => 21, 
                 "SECTION_ID" => 452  
                 ) 
    );

    Организация явной связи между компонентами на одной странице комплексного компонента

    Явную связь между компонентами можно организовывать через возвращаемые значения и входящие параметры этих компонентов.

    Если из компонента comp1 нужно передать данные в компонент comp2, то в конце кода компонента comp1 нужно написать: return данные;

    Подключить компонент comp1 нужно следующим образом:

    $result = $APPLICATION->IncludeComponent(comp1, ...);

    Теперь данные находятся в переменной $result и их можно передать входящими параметрами в другой компонент comp2.

    Переопределение входящих переменных

    Каждый компонент имеет набор переменных, в которых он принимает извне коды или другие атрибуты запрашиваемых данных. Например, компонент bitrix:catalog.section имеет переменные IBLOCK_ID и SECTION_ID, в которых он принимает и обрабатывает коды каталога и группы товаров соответственно.

    Все компоненты, которые входят в состав комплексного компонента, должны иметь единообразный набор переменных. Например, комплексный компонент bitrix:catalog и все обычные компоненты (bitrix:catalog.section.list, bitrix:catalog.section и т.д.), которыми он управляет, работают с переменными IBLOCK_ID, SECTION_ID, ELEMENT_ID и другими.

    Если разработчик при размещении комплексного компонента на странице хочет переопределить переменные компонента, то он среди входящих параметров компонента должен задать параметр VARIABLE_ALIASES.

    При подключении компонента в режиме SEF (ЧПУ) этот параметр должен иметь вид:

    "VARIABLE_ALIASES" => array( 
          "list" => array(),
          "section" => array(
                            "IBLOCK_ID" => "BID",
                            "SECTION_ID" => "ID"
                            ),
          "element" => array(
          "SECTION_ID" => "SID",
          "ELEMENT_ID" => "ID"
          ),
    )

    Здесь коды массива соответствуют кодам в массиве шаблонов путей. Для каждого пути могут быть заданы свои переопределения переменных.

    При подключении компонента не в режиме SEF (ЧПУ) этот параметр должен имет вид:

    "VARIABLE_ALIASES" => array(
                               "IBLOCK_ID" => "BID",
                               "SECTION_ID" => "GID",
                               "ELEMENT_ID" => "ID",
    )

    Пример №1:

    Пусть требуется, чтобы компонент bitrix:catalog, лежащий в файле /fld/cat.php, работал с путями:

    /catalog/index.php – для списка каталогов,

    /catalog/section/код_группы.php?ID=код_каталога – для группы товаров,

    /catalog/element/код_товара.php?ID=код_группы – для детальной информации о товаре.

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

    "SEF_MODE" => "Y",    
    "SEF_FOLDER" => "/catalog/",
    "SEF_URL_TEMPLATES" => array(
                        "list" => "index.php",
                        "section" => "section/#SECTION_ID#.php?ID=#IBLOCK_ID#",
                        "element" => "element/#ELEMENT_ID#.php?ID=#SECTION_ID#"    
                                ),
    "VARIABLE_ALIASES" => array(
                         "list" => array(),
                         "section" => array(
                                       "IBLOCK_ID" => "ID"),
                         "element" => array(
                                       "SECTION_ID" => "ID",),    
    

    Пример №2:

    Пусть требуется, чтобы компонент bitrix:catalog, лежащий в файле /fld/cat.php, работал с путями

    /fld/cat.php – для списка каталогов,

    /fld/cat.php?BID=код_каталога&SID=код_группы – для группы товаров,

    /fld/cat.php?ID=код_товара&SID=код_группы – для детальной информации о товаре.

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

    "SEF_MODE" => "N",
    "VARIABLE_ALIASES" => array(
                               "IBLOCK_ID" => "BID",
                               "SECTION_ID" => "SID",
                               "ELEMENT_ID" => "ID",
                               ),

    Пользовательские движки шаблонизации

    Шаблонизация

    Компоненты могут работать с любыми движками шаблонизации, которые могут быть подключены из PHP. Чтобы добавить новый движок шаблонизации на сайт необходимо определить (или дополнить) глобальную переменную $arCustomTemplateEngines в файле /bitrix/php_interface/init.php. В этой переменной содержится ассоциативный массив, каждый элемент которого имеет вид:

       "код_шаблонизатора" => array(
          "templateExt" => array("расширение1"[, "расширение2"...]),
          "function" => "имя_функции_подключения_движка"
       )

    где:

    • код_шаблонизатора — произвольное уникальное в рамках сайта слово,
    • расширениеN — расширение файла, который должен обрабатываться этим движком шаблонизации,
    • имя_функции_подключения_движка — имя функции, которая будет вызываться, если шаблон компонента имеет указанное расширение. Функцию можно разместить в этом же файле /bitrix/php_interface/init.php.

    Например, если на сайте кроме стандартного движка шаблонизации (PHP) требуется использовать Smarty, то в файл /bitrix/php_interface/init.php необходимо добавить следующий код:

       global $arCustomTemplateEngines;
       $arCustomTemplateEngines = array(
          "smarty" => array(
             "templateExt" => array("tpl"),
             "function" => "SmartyEngine"
          ),
       );

    Тогда при подключении шаблона с расширением tpl будет запускаться не стандартный движок PHP, а функция SmartyEngine, которая должна подключить движок Smarty.

    Синтаксис функций подключения движков следующий:

       function имя_функции_подключения_движка($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)

    где:

    • $templateFile – путь к файлу шаблона относительно корня сайта,
    • $arResult – массив результатов работы компонента,
    • $arParams – массив входных параметров компонента,
    • $arLangMessages – массив языковых сообщений (переводов) шаблона,
    • $templateFolder – путь к папке шаблона относительно корня сайта (если шаблон лежит не в
      папке, то эта переменная пуста),
    • $parentTemplateFolder — путь относительно корня сайта к папке шаблона комплексного
      компонента, в составе которого подключается данный компонент (если компонент
      подключается самостоятельно, то эта переменная пуста),
    • $template – объект шаблона.

    Код функции подключения движка шаблонизации зависит от подключаемого движка.

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

    Полный пример подключения движка Smarty

    В файл /bitrix/php_interface/init.php необходимо добавить код:

    global $arCustomTemplateEngines;
    $arCustomTemplateEngines = array(
       "smarty" => array(
          "templateExt" => array("tpl"),
          "function" => "SmartyEngine"
       )
    );
    
    function SmartyEngine($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)
    {
       if (!defined("SMARTY_DIR"))
          define("SMARTY_DIR", "<абсолютный путь к движку Smarty>/libs/");
    
       require_once('<абсолютный путь к движку Smarty>/libs/Smarty.class.php');
    
       $smarty = new Smarty;
    
       $smarty->compile_dir = "<абсолютный путь к движку Smarty>/templates_c/";
       $smarty->config_dir = "<абсолютный путь к движку Smarty>/configs/";
       $smarty->template_dir = "<абсолютный путь к движку Smarty>/templates/";
       $smarty->cache_dir = "<абсолютный путь к движку Smarty>/cache/";
    
       $smarty->compile_check = true;
       $smarty->debugging = false;
    
       $smarty->assign("arResult", $arResult);
       $smarty->assign("arParams", $arParams);
       $smarty->assign("MESS", $arLangMessages);
       $smarty->assign("templateFolder", $templateFolder);
       $smarty->assign("parentTemplateFolder", $parentTemplateFolder);
    
       $smarty->display($_SERVER["DOCUMENT_ROOT"].$templateFile);
    }

    Строку <абсолютный путь к движку Smarty> нужно везде заменить на абсолютный путь к движку Smarty в рамках вашей установки. Подробности по установке движка на сайт есть в системе помощи по Smarty.

    В примере кода в массиве $arCustomTemplateEngines регистрируется движок Smarty. В функции SmartyEngine инициализируются параметры движка в соответствии с требованиями системы (см. документацию Smarty). Далее в Smarty передаются переменные результатов работы компонента, входных параметров, языковых сообщений и т.д. И в конце вызывается метод обработки и показа шаблона Smarty.

    Подключение XML/XSLT

    Полный пример подключения движка XML/XSLT

    В файл /bitrix/php_interface/init.php необходимо добавить код:

    global $arCustomTemplateEngines;
    $arCustomTemplateEngines = array(
       "xslt" => array(
          "templateExt" => array("xsl"),
          "function" => "XSLTEngine"
       ),
    );
    
    function CreateXMLFromArray($xDoc, $xNode, $ar)
    {
       foreach($ar as $key=>$val)
       {
          if(!is_string($key) || strlen($key)<=0)
             $key = "value";
    
          $xElement = $xDoc->createElement($key);
          if(is_array($val))
          {
             CreateXMLFromArray($xDoc, $xElement, $val);
          }
          else
          {
             $xElement->appendChild($xDoc->createTextNode(iconv(SITE_CHARSET, "utf-8", $val)));
          }
          $xNode->appendChild($xElement);
       }
       return $xNode;
    }
    
    function XSLTEngine($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)
    {
       $arResult["PARAMS"] = array(
          "templateFolder" => $templateFolder,
          "parentTemplateFolder" => $parentTemplateFolder,
          "arParams" => $arParams,
          "arLangMessages" => $arLangMessages
       );
    
       $xDoc = new DOMDocument("1.0", SITE_CHARSET);
       $xRoot = $xDoc->createElement('result');
       CreateXMLFromArray($xDoc, $xRoot, $arResult);
       $xDoc->appendChild($xRoot);
    
       $xXsl = new DOMDocument();
       $xXsl->load($_SERVER["DOCUMENT_ROOT"].$templateFile);
    
       $xProc = new XSLTProcessor;
       $xProc->importStyleSheet($xXsl);
    
       echo $xProc->transformToXML($xDoc);
    }

    Разработка верстки шаблона компонента

    При разработке компонентов рекомендуем использовать технологию, которая может упростить работу партнерам, студиям при разработке. Разработка с её помощью не обязательна и не нужна всем подряд. Для внесения незначительных изменений в шаблон компонента верстальщику не нужно использовать описываемый ниже файл template.html.php. Этот файл рекомендуется использовать, при новой верстке шаблона компонента и при разработке нового компонента. Для доработки существующей верстки он не годится в полной мере.

    В общем виде веб-технолог должен сделать следующие шаги:

    1. Если используется система контроля версий, то уточнить у разработчика название ветки, компонент и страницу, в которой требуется создать новую верстку. Если такого компонента нет, попросить разработчика подготовить вышеперечисленное. Если разработка ведётся без системы контроля версий, то работу можно вести непосредственно в папке создаваемого компонента.
    2. В папке шаблона компонента создать файл с названием template.html.php. Данный шаблон будет подключаться вместо стандартного template.php, если в /bitrix/php_interface/init.php зарегистрировать свой шаблонизатор:
      global $arCustomTemplateEngines;
      $arCustomTemplateEngines = array(
         "html" => array(
            "templateExt" => array("html.php"),
            "function" => "includeHtmlTemplate",
            "sort" => 50
         ),
      );
      
      function includeHtmlTemplate($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)
      {
         return $template->__IncludePHPTemplate($arResult, $arParams, $parentTemplateFolder);
      }

      Таким образом, веб-технолог будет работать с чистым шаблоном template.html.php. После размещения этого кода в init.php во всех компонентах, где в шаблоне есть файл template.html.php, происходит подключение именно его. Если нужно подключить файл template.php, то данный код нужно или удалить или закомментировать.

      Если же веб-технолог и программист работают одновременно на одной установке, то данный код можно подключать только по определенному условию: например, по id авторизованного пользователя.

    3. По окончании работ над template.html.php, разработчик должен интегрировать верстку и удалить временный шаблон.
    4. После этого веб-технолог работает по алгоритму модификации существующей верстки.

    Способы передачи данных между компонентами

    Способы передачи данных между компонентами:

    1. Глобальные переменные, Например:
      $GLOBALS['mycomponent_variable'] = $arResult["ID"];

      Кроме GLOBALS можно использовать $_SESSION при условиях, что:

      • данные небольшого объема;
      • сразу после передачи данные будут удалены из $_SESSION, так как в противном случае будут жить, пока сессия жива.
    2. Класс обертка, например:
      Class GarbageStorage{
         private static $storage = array();
         public static function set($name, $value){ self::$storage[$name] = $value;}
         public static function get($name){ return self::$storage[$name];}
      }

      соответственно, использование:

      GarbageStorage::set('MyCustomID', $arResult["ID"]); #установить значение
      GarbageStorage::get('MyCustomID'); #получить значение

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

    Простой пример создания компонента

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

    Предварительные действия

    Готовим php-код компонента

    Первым делом сразу напишем php-код, который выполняет, то, что нам нужно.

    <?
    echo date('Y-m-d');
    ?>

    Правда код просто выводит дату и нельзя выбрать другой формат. Лучше поместить в переменную формат вывода даты:

    <?
    $format = 'Y-m-d';
    echo date($format);
    ?>

    И последний штрих — нужно разделить логику и представление:

    <?
    // параметры
    $format = 'Y-m-d';
    // логика
    $d = date($format);
    // представление
    echo $d;
    ?>

    Создаем структуру папок и файлов компонента

    Теперь нужно создать свое пространство имен, например: dv. Для этого надо создать папку /local/components/dv. В ней делаем папку компонента — date.current. И в ней, в свою очередь, создаем два обязательных файла и папку для хранения шаблонов templates. В этой папке должна быть создана папка .default и в ней файл template.php.

    Получаем такую структуру в папке /local/components/dv/date.current:

    • component.php
    • .description.php
    • templates/.default/template.php

    Компонент без входных параметров

    Пока сделаем компонент без возможности задания входного параметра — формата даты.

    Содержимое файлов:

    • component.php
      <? if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
      $arResult['DATE'] = date('Y-m-d');
      $this->IncludeComponentTemplate();
      ?>
    • .description.php
      <? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die(); $arComponentDescription = array(
      "NAME" => GetMessage("Текущая дата"),
      "DESCRIPTION" => GetMessage("Выводим текущую дату"),
      );
      ?>
    • templates/.default/template.php
      <? if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
            echo $arResult['DATE'];
      ?>

    Как вы могли заметить в каждом из файлов компонента в начале пишется строка if (!defined(“B_PROLOG_INCLUDED”) || B_PROLOG_INCLUDED!==true) die();. Она нужна для того, чтобы данные файлы нельзя было вызвать напрямую из окна браузера.

    В простейшем виде компонент готов — его можно вызвать в коде страниц при помощи конструкции:

    <? $APPLICATION->IncludeComponent(
    "dv:date.current",
    ".default",
    Array(
    ),
    false
    );?>

    Компонент с входными параметрами

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

    Чтобы наш компонент появился в визуальном редакторе нужно дополнить файл описания компонента.

    .description.php:

    <? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die(); 
    $arComponentDescription = array(
    "NAME" => GetMessage("Текущая дата"),
    "DESCRIPTION" => GetMessage("Выводим текущую дату"),
    "PATH" => array(
    "ID" => "dv_components",
    "CHILD" => array(
    "ID" => "curdate",
    "NAME" => "Текущая дата"
    )
    ),
    "ICON" => "/images/icon.gif",
    );
    ?>

    Для размещения компонента в дереве компонентов мы добавили элемент массива описания PATH. Таким образом, наш компонент будет показан в отдельной папке. Опционально, можно задать иконку компонента — она будет показываться в дереве и в визуальном редакторе.

    Разберемся с настройками компонента. Будем считать, что опцию шаблон даты мы будем задавать строкой. Создаем файл .parameters.php с таким содержанием:

    <? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
     $arComponentParameters = array(
    "GROUPS" => array(),
    "PARAMETERS" => array(
    "TEMPLATE_FOR_DATE" => array(
    "PARENT" => "BASE",
    "NAME" => "Шаблон для даты",
    "TYPE" => "STRING",
    "MULTIPLE" => "N",
    "DEFAULT" => "Y-m-d",
    ),
    ),
    );
    ?>

    И изменяем файл с логикой компонента, чтобы он мог использовать параметр, который мы задаем component.php:

    <? if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    $arResult['DATE'] = date($arParams["TEMPLATE_FOR_DATE"]);
    $this->IncludeComponentTemplate();
    ?>

    Результат

    Мы создали компонент, в самом простом виде. Мы не учитывали мультиязычность, не учитывали возможность создания помощи к компоненту и прелести кеширования компонентов.

    Большая часть заказных компонентов для Bitrix Framework создается путем изменения компонентов, идущих в поставке продуктов. Поэтому очень нужно хорошо изучить стандартные компоненты, перед тем как программировать новые. Скорее всего, задачу, которую вы хотите решить — уже решили разработчики компании 1С-Битрикс.

    Пример создания компонента

    Рассмотрим пример создания компонента для сообщений администратору об ошибке.

    Описание

    С помощью этого компонента можно реализовать функционал, который бы позволял пользователям сообщать ответственным за контент о найденной на сайте ошибке. Ошибка будет высылаться почтовым уведомлением. Алгоритм работы пользователя с компонентом очень простой: если пользователь находит на портале ошибку, то он выделяет текст, нажимает Ctrl+Enter и получает форму:

    Форма заполняется и сообщение отправляется ответственному лицу.

    Создание почтового шаблона

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

    • Перейдите на страницу Настройки > Настройки продукта > Почтовые события > Типы почтовых событий.
    • Заполните поля формы:

    • Перейдите на страницу Настройки > Настройки продукта > Почтовые события > Почтовые шаблоны.
    • Нажмите в контекстной панели на Добавить шаблон, откроется форма создания шаблона.
    • Задайте шаблон для созданного типа почтового события:

    Создание компонента

    Создайте в собственном пространстве имен папку feedback.error со следующей структурой:

    • папка /images
      • файл feedback.gif
    • папка /templates
      • папка /.default
        • файл script.js
        • файл template.php
    • файл .description.php
    • файл .parameters.php
    • файл component.php

    Файл feedback.gif — иконка, которая будет отображаться в визуальном редакторе.

    код файла script.js:

    BX.bind(document, "keypress", SendError);
    
    function SendError(event, formElem)
    {
    		event = event || window.event;
    
    		if((event.ctrlKey) && ((event.keyCode == 0xA)||(event.keyCode == 0xD)))
    		{
    			var Dialog = new BX.CDialog({
    								title: "На сайте обнаружена ошибка!!",
    								head: "В чём заключается ошибка?",
    								content: 	'<form method="POST" id="help_form">
    											<textarea name="error_desc" style="height: 78px; width: 374px;"></textarea>
    											<input type="hidden" name="error_message"value="'+getSelectedText()+'">
    											<input type="hidden" name="error_url" value="'+window.location+'">
    											<input type="hidden" name="error_referer" value="'+document.referrer+'">
    											<input type="hidden" name="error_useragent" value="'+navigator.userAgent+'">
    											<input type="hidden" name="sessid" value="'+BX.bitrix_sessid()+'"></form>',
    								resizable: false,
    								height: '198',
    								width: '400'});
    
    			Dialog.SetButtons([
                {
                    'title': 'Отправить',
    				'id': 'action_send',
    				'name': 'action_send',
                    'action': function(){
    					BX.ajax.submit(BX("help_form"));
                        this.parentWindow.Close();
                    }
                },
    			{
                    'title': 'Отмена',
    				'id': 'cancel',
    				'name': 'cancel',
                    'action': function(){
                        this.parentWindow.Close();
                    }
                }
    			]);
    			Dialog.Show();
    		}
    }
    function getSelectedText(){
      if (window.getSelection){
        txt = window.getSelection();
      }
      else if (document.getSelection) {
        txt = document.getSelection();
      }
      else if (document.selection){
        txt = document.selection.createRange().text;
      }
      else return;
      return txt;
    }
    

    код файла template.php:

    <?
    CUtil::InitJSCore(array('window', 'ajax'));
    ?>

    код файла .description.php:

    <?
    if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    $arComponentDescription = array(
        "NAME" => "Send Error",
        "DESCRIPTION" => "Send Error",
        "ICON" => "/images/feedback.gif",
        "PATH" => array(
            "ID" => "utility",
        ),
    );
    ?>

    код файла .parameters.php:

    <?
    if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    $arComponentParameters = array();
    ?>

    код файла component.php:

    <?
    if (check_bitrix_sessid() && $_SERVER['REQUEST_METHOD'] == "POST" && !empty($_REQUEST["error_message"]) && !empty($_REQUEST["error_url"]))
    {
    	$arMailFields = Array();
    	$arMailFields["ERROR_MESSAGE"] = trim ($_REQUEST["error_message"]);
    	$arMailFields["ERROR_DESCRIPTION"] = trim ($_REQUEST["error_desc"]);
    	$arMailFields["ERROR_URL"] = $_REQUEST["error_url"];
    	$arMailFields["ERROR_REFERER"] = $_REQUEST["error_referer"];
    	$arMailFields["ERROR_USERAGENT"] = $_REQUEST["error_useragent"];
    
    	CEvent::Send("ERROR_CONTENT", SITE_ID, $arMailFields);
    }
    $this->IncludeComponentTemplate();
    ?>

    Теперь подробнее для разработчиков о том, что находится в файле feedback.errortemplates.defaultscript.js.

    Объявление функции-обработчика, которая выводится на <body>:

    function SendError(event, formElem)

    Ожидание нажатия Ctrl+Enter:

    if((event.ctrlKey) && ((event.keyCode == 0xA)||(event.keyCode == 0xD)))

    Определение параметров будущего окна и его содержимого:

    var Dialog = new BX.CDialog({
                title: "На сайте обнаружена ошибка!!",
                head: "В чём заключается ошибка?",
                content:    '<form method="POST" id="help_form" action="/bitrix/templates/.default/send_error.php">
                                     <textarea name="error_desc" style="height: 78px; width: 374px;"></textarea>
                                     <input type="hidden" name="error_message"value="'+getSelectedText()+'">
                                     <input type="hidden" name="error_url" value="'+window.location+'">
                                     <input type="hidden" name="sessid" value="'+BX.bitrix_sessid()+'"></form>',
                resizable: false,
                height: '198',
                width: '400'});

    Определение набора кнопок:

    Dialog.SetButtons([
    {
       'title': 'Отправить',
       'id': 'action_send',
       'name': 'action_send',
       'action': function(){
          BX.ajax.submit(BX("help_form"));
          this.parentWindow.Close();
       }
    },
    {
       'title': 'Отмена',
       'id': 'cancel',
       'name': 'cancel',
       'action': function(){
          this.parentWindow.Close();
       }
    },
    ]);

    Вывод окна:

    Dialog.Show();

    Функция getSelectedText() получает выделенный мышью текст. И далее идет отправка письма в тексте файла component.php:

    if (check_bitrix_sessid() && $_SERVER['REQUEST_METHOD'] == "POST" && !empty($_REQUEST["error_message"]) && !empty($_REQUEST["error_url"]))
    {
       $arMailFields = Array();
       $arMailFields["ERROR_MESSAGE"] = trim ($_REQUEST["error_message"]);
       $arMailFields["ERROR_DESCRIPTION"] = trim ($_REQUEST["error_desc"]);
       $arMailFields["ERROR_URL"] = trim ($_REQUEST["error_url"]);
       CEvent::Send("BX", SITE_ID, $arMailFields);
    };

    Список ссылок по теме:

    • Философия двух компонент (блог)

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

    Создадим компонент, интегрирующий популярный редактор TinyMCE в Bitrix Framework.

    Загрузите свежую версию редактора с сайта производителя.

    В своем пространстве имен в папке /local создайте структуру папок и файлов:

    • /bitrix/components/tools/;
      • /bitrix/components/tools/editor.tiny.mce/;
        • /bitrix/components/tools/editor.tiny.mce/templates/;
          • /bitrix/components/tools/editor.tiny.mce/templates/.default/;
        • /bitrix/components/tools/editor.tiny.mce/tiny_mce/ — папка для дистрибутива редактора;
        • component.php — логика компонента;
        • .parameters.php — файл для описания входящих параметров.

    Скопируйте скаченный дистрибутив в папку /tiny_mce.

    В файл component.php добавляем код:

    <? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    	$APPLICATION->AddHeadScript($this->__path .'/tiny_mce/tiny_mce.js');
    
    	$sNameTextArea   =  (isset($arParams['TEXTAREA_NAME'])   == false) ? 'content'   : $arParams['TEXTAREA_NAME'];
    	$sIdTextArea   	 =  (isset($arParams['TEXTAREA_ID'])     == false) ? '' 		 : $arParams['TEXTAREA_ID'];
                 if ('' == trim($sIdTextArea))
                 $sIdTextArea = 'content';
    	$sEditorID 		 =  (isset($arParams['INIT_ID'])  	     == false) ? 'textareas' : $arParams['INIT_ID'];
    	$iTextareaWidth  =  (isset($arParams['TEXTAREA_WIDTH'])  == false) ? '100%'      : $arParams['TEXTAREA_WIDTH'];
    	$iTextareaHeight =  (isset($arParams['TEXTAREA_HEIGHT']) == false) ? '300'       : $arParams['TEXTAREA_HEIGHT'];
    	$sText 			 =  (isset($arParams['TEXT']) 			 == false) ? ''       	 : $arParams['TEXT'];
    	?>
    
    <script type="text/javascript">
    
    <? 
    if ($arParams['TYPE_EDITOR'] == 'TYPE_1')
    {
    	?>
    	tinyMCE.init(
    		{
    			language : 'ru',
    			mode 	 : "textareas",
    			//elements : "<?=$sEditorID?>",
    			editor_selector : "<?=$sEditorID?>",
    			theme    : "advanced",
    			plugins  : "safari, spellchecker, upload.images.komka, wordcount, fullscreen",
    			theme_advanced_buttons1 : "formatselect,fontselect,fontsizeselect,bold,italic,underline,link,justifyleft,justifycenter,
                                           justifyright,pasteword,pastetext,images,|,bullist,numlist,|,undo,redo,|,spellchecker,fullscreen",
    			theme_advanced_buttons2 : "",
    			theme_advanced_buttons3 : "",
    			theme_advanced_toolbar_location   : "top",
    			theme_advanced_toolbar_align      : "left",
    			theme_advanced_statusbar_location : "bottom",
    			theme_advanced_resizing           : false,
    			content_css                       : "<?=$this->__path?>/example.css",
    			height : "<?=$iTextareaHeight?>",
    			spellchecker_languages : '+Русский=ru,English=en',
    			spellchecker_word_separator_chars : '\s!"#$%&()*+,-./:;<=>?@[]^_{|}'
    		}
    	);
    	<? 
    }
    elseif($arParams['TYPE_EDITOR'] == 'TYPE_2')
    {
    	?>
    		tinyMCE.init({
    				language : 'ru',
    				mode 	 : "textareas",
    				editor_selector : "<?=$sEditorID?>",
    				theme    : "advanced",
    				plugins : "safari,spellchecker,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,
                               iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,
                               fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,imagemanager,filemanager",
    				theme_advanced_buttons1 : "save,newdocument,|,bold,italic,underline,strikethrough,|,justifyleft,
                                               justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect",
    				theme_advanced_buttons2 : "cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,
                                               numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,
                                               insertdate,inserttime,preview,|,forecolor,backcolor",
    				theme_advanced_buttons3 : "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,
                                               emotions,iespell,media,advhr,|,print,|,ltr,rtl,|,fullscreen",
    				theme_advanced_buttons4 : "insertlayer,moveforward,movebackward,absolute,|,styleprops,
                                               spellchecker,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,blockquote,
                                               pagebreak,|,insertfile,insertimage",
    				theme_advanced_toolbar_location : "top",
    				theme_advanced_toolbar_align : "left",
    				theme_advanced_statusbar_location : "bottom",
    				theme_advanced_resizing : true,
    				content_css : "<?=$this->__path?>/example.css",
    				height : "<?=$iTextareaHeight?>",
    				template_external_list_url : "js/template_list.js",
    				external_link_list_url : "js/link_list.js",
    				external_image_list_url : "js/image_list.js",
    				media_external_list_url : "js/media_list.js",
    				template_replace_values : {username : "Some User", staffid : "991234"}
    			}
    		);
    	<? 
    }
    ?>
    
    </script>
    <textarea id="<?=$sIdTextArea?>" class="<?=$sEditorID?>"  name="<?=$sNameTextArea?>" style="width:<?=$iTextareaWidth?>"><?=$sText?></textarea>
    <? $this->IncludeComponentTemplate();?>
    

    Файл .parameters.php

    <? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    $arComponentParameters = array(
    	"PARAMETERS" => array(
    		"TYPE_EDITOR" => Array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Режим редактора",
    	         "TYPE" => "LIST",
    	         "VALUES" => array('TYPE_1' => 'Упрощенные редактор', 'TYPE_2' => 'Полной редактор'),
    		),
    
    		'INIT_ID' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "ID редатора (уникальный)",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => '',
    		),
    		
    		'TEXT' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Контент который нужно вставить в редактор",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => $_POST['content'],
    		),
    		
    		'TEXTAREA_NAME' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Имя поля TEXTAREA",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => 'content',
    		),
    		
    		'TEXTAREA_ID' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "ID поля TEXTAREA",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => 'content',
    		),
    		
    		'TEXTAREA_WIDTH' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Ширина редактора",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => '100%',
    		),
    
    		'TEXTAREA_HEIGHT' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Высота редактора",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => '300',
    		),
    	)
    );
    ?>

    Важные моменты в коде файла component.php, которые надо пояснить. Подключение собственно редактора:

    <?  $APPLICATION->AddHeadScript($this->__path .’/tiny_mce/tiny_mce.js’); ?>

    Подключение стилей, которые вынесены в папку с компонентом чтобы было удобнее, это настройка уже в js, в коде инициализации:

    content_css : ‘<?=$this->__path?>/example.css’,

    Внимание! если на странице 2 или более редакторов, то мы идентифицируем их по имени класса editor_selector : ‘<?=$sEditorID?>’.

    Собственно сама область текста для которой это все и делается:

    <textarea id=’<?=$sIdTextArea?>’  name=’<?=$sNameTextArea?>’ style=’width:<?=$iTextareaWidth?>’><?=$sText?></textarea>

    Как использовать

    Подключается вот в таком виде:

    <? echo $_POST['content'] ?>
    <? echo $_POST['content2'] ?>
    <form action="" method="post" name="">
    <? $APPLICATION->IncludeComponent("tools:editor.tiny.mce", ".default", array(
    "TEXT" => $_POST["content"], // контент из запроса который нужно вставить
    "TEXTAREA_NAME" => "content", // имя поля
    "TEXTAREA_ID" => "content",         // id поля
    "TEXTAREA_WIDTH" => "100%",  // ширина
    "TEXTAREA_HEIGHT" => "300",    // высота
     
    "INIT_ID" => "ID" // ID самого редактора
    ),
    false
    );
    ?>
    <input value="submit" name="sub" type="submit" />
    </form>

    Кеширование в собственных компонентах

    Описание

    Для чего нужно кэширование в собственных компонентах

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

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

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

    При использовании кэширования в собственных компонентах 2.0:

    • резко увеличивается производительность веб-проекта и его устойчивость к нагрузкам, т.к. нагрузка на базу данных качественно минимизируется и веб-решение сможет обслужить уже, к примеру, не 50 000 пользователей в сутки, а 1 000 000 и больше
    • веб-страницы загружаются в браузер пользователя значительно быстрее (десятые доли секунды), т.к. информация для их построения сохранена на сервере и не берется из базы данных

    Время кеширования

    Примечание: В Bitrix Framework время кеширования учитывается в секундах.

    Период времени кэширования зависит от типа кеширования. Если используется Авто+Управляемое кэширование – информация будет отдаваться из кэша до тех пор, пока она не поменяется в базе данных и кэш сбросится автоматически. Время кэширования для этого режима должно быть большим, к примеру, 1 год.

    Если используется Авто кэширование – рекомендуется устанавливать максимально допустимый с учетом бизнес-логики интервал кэширования. Время кэширования для этого режима зависит от частоты обновления информации — для некоторых компонентов устанавливается период в 24 часа, а для часто обновляемых либо рекомендуется использовать управляемое кэширование или установить значение, к примеру , в 10 минут.

    Встроенная поддержка кеширования

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

    // Проверка и инициализация входных параметров
    if ($arParams["ID"] <= 0)
       $arParams["ID"] = 10;
    
    // Если нет валидного кеша (то есть нужно запросить
    // данные и сделать валидный кеш)
    if ($this->StartResultCache())
    {
       // Запрос данных и заполнение $arResult
       $arResult = array(
          "ID" => rand(1, 100)
       );
    
       for ($i = 0; $i < 5; $i++)
          $arResult["FIELDS"][] = rand(1, 100);
    
       // Если выполнилось какое-то условие, то кешировать
       // данные не надо
       if ($arParams["ID"] < 10)
          $this->AbortResultCache();
    
       // Подключить шаблон вывода
       $this->IncludeComponentTemplate();
    }
    
    // Установить заголовок страницы с помощью отложенной
    // функции
    $APPLICATION->SetTitle($arResult["ID"]); 

    Пояснения по коду

    Метод StartResultCache имеет следующее описание: bool $this->StartResultCache($cacheTime = False, $additionalCacheID = False, $cachePath = False)

    где:

    • $cacheTime — время кеширования (если False — подставляется IntVal($arParams["CACHE_TIME"]));
    • $additionalCacheID — от чего дополнительно зависит кеш кроме текущего сайта SITE_ID, имени компонента и входных параметров;
    • $cachePath — путь к файлу кеша (если False — подставляется "/".SITE_ID.<путь к компоненту относительно bitrix/components>).

    Если есть валидный кеш, то метод отправляет на экран его содержимое, заполняет $arResult и возвращает False; если нет валидного кеша, то он возвращает True.

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

    if ($this->StartResultCache(false, $USER->GetGroups()))
    {
       // Валидного кеша нет. Выбираем данные из 
       // базы в $arResult
    }

    Если в результате выборки данных (в случае отсутствия валидного кеша) выяснилось, что кешировать данные не надо, то нужно вызвать метод
    $this->AbortResultCache();. Например, если выяснилось, что новости с таким ID нет, то нужно прервать кеширование и выдать сообщение, что такой новости нет. Если кеширование не прерывать, то злоумышленники смогут забить кешем все отведенное сайту дисковое пространство вызывая страницу с произвольными (в том числе и не существующими) ID.

    Метод $this->IncludeComponentTemplate(); подключает шаблон компонента и сохраняет в кеш-файл вывод и массив результатов $arResult. Все изменения $arResult и вывод после вызова метода подключения шаблона не будут сохранены в кеш.

    Если при исполнении кода компонента мы не вошли в тело условия if ($this->StartResultCache()), то значит для данного компонента, страницы и входных параметров есть валидный кеш. После вызова этого метода HTML из кеша отправлен на вывод и мы имеем заполненный массив $arResult. Здесь можно выполнить некоторые действия. Например, установить заголовок страницы с помощью отложенных функций.

    Если при выполнении некоторых условий нужно очистить кеш компонента (например, компонент знает, что данные изменились), то можно воспользоваться методом $this->ClearResultCache($additionalCacheID = False, $cachePath = False). Параметры этого метода соответствуют одноименным параметрам метода StartResultCache.

    Сложное кеширование

    Если компоненту требуется какое-либо особое кеширование, которое не может быть выполнено с помощью встроенной поддержки кеширования, то можно использовать стандартный класс CPHPCache. Структура компонента с использованием класса CPHPCache будет примерно такова:

    // Проверка и инициализация входных параметров
    if ($arParams["ID"] <= 0)
       $arParams["ID"] = 10;
    
    $arParams["CACHE_TIME"] = IntVal($arParams["CACHE_TIME"]);
    $CACHE_ID = SITE_ID."|".$APPLICATION->GetCurPage()."|";
    // Кеш зависит только от подготовленных параметров без "~"
    foreach ($this->arParams as $k => $v)
       if (strncmp("~", $k, 1))
          $CACHE_ID .= ",".$k."=".$v;
    $CACHE_ID .= "|".$USER->GetGroups();
    
    $cache = new CPHPCache;
    if ($cache->StartDataCache($arParams["CACHE_TIME"], $CACHE_ID, "/".SITE_ID.$this->GetRelativePath()))
    {
       // Запрос данных и формирование массива $arResult
       $arResult = array("a" => 1, "b" => 2);
    
       // Подключение шаблона компонента
       $this->IncludeComponentTemplate();
    
       $templateCachedData = $this->GetTemplateCachedData();
    
       $cache->EndDataCache(
          array(
             "arResult" => $arResult,
        "templateCachedData" => $templateCachedData
          )
       );
    }
    else
    {
       extract($cache->GetVars());
       $this->SetTemplateCachedData($templateCachedData);
    }

    Пояснения по коду

    Кеш должен зависеть только от подготовленных параметров. То есть от параметров, которые инициализированы нужным образом, приведены к нужному типу (например, с помощью IntVal()) и т.д. В массиве $arParams содержатся как подготовленные параметры, так и исходные параметры (с тем же ключем, но с префиксом «~»). Если кеш будет зависеть от неподготовленных параметров, то злоумышленники смогут забить кешем все отведенное сайту дисковое пространство вызывая страницу с ID равными «8a», «8b», … (которые после IntVal() дадут 8).

    Метод $this->IncludeComponentTemplate() не запрашивает данные из базы. Но его тоже лучше включить в кешируемую область, так как этот метод производит определенные дисковые операции.

    Перед вызовом метода завершения кеширования и сохранения кеша (метод EndDataCache) необходимо запросить у шаблона параметры, которые должны быть использованы даже в том случае, если сам шаблон не подключается и данные берутся из кеша. В текущей реализации такими параметрами являются стили css шаблона, которые подключаются отложенными функциями, а значит не попадают в кеш. Структура возвращаемых шаблоном данных не документирована и не имеет значения для компонента. Это просто какие-то данные, которые нужно положить в кеш, а затем взять из кеша и вернуть в шаблон.

    Чтобы вернуть шаблону те данные, которые он просил сохранить в кеше, можно пользоваться методами $this->SetTemplateCachedData($templateCachedData); или CBitrixComponentTemplate::ApplyCachedData($templateCachedData);.
    Один из этих методов должен быть вызван в той области компонента, которая выполняется в случае наличия валидного кеша. В параметрах ему должны быть переданы те данные, которые шаблон просил сохранить.

    Пример сложного кеширования с классом кеша из D7:

    use BitrixMainDataCache;
    
    // Проверка и инициализация входных параметров
    if ($arParams['ID'] <= 0) {
        $arParams['ID'] = 10;
    }
    
    $arParams['CACHE_TIME'] = intval($arParams['CACHE_TIME']);
    
    $cacheId = implode('|', [
        SITE_ID,
        $APPLICATION->GetCurPage(),
        $USER->GetGroups()
    ]);
    
    // Кеш зависит только от подготовленных параметров без "~"
    foreach ($this->arParams as $k => $v) {
        if (strncmp('~', $k, 1)) {
            $cacheId .= ',' . $k . '=' . $v;
        }
    }
    
    $cacheDir = '/' . SITE_ID . $this->GetRelativePath();
    $cache    = Cache::createInstance();
    
    if ($cache->startDataCache($arParams['CACHE_TIME'], $cacheId, $cacheDir)) {
        // Запрос данных и формирование массива $arResult
        $arResult = ['a' => 1, 'b' => 2];
    
        // Подключение шаблона компонента
        $this->IncludeComponentTemplate();
    
        $templateCachedData = $this->GetTemplateCachedData();
    
        $cache->endDataCache([
            'arResult'           => $arResult,
            'templateCachedData' => $templateCachedData,
        ]);
    } else {
        extract($cache->GetVars());
        $this->SetTemplateCachedData($templateCachedData);
    }

    Несколько советов

    Если в компоненте используется стандартное кэширование, но при этом не подключается шаблон (по причине ненадобности), нужно использовать:

    if ($this->startResultCache())
    {
    	\Код который модифицирует $arResult
    	$this->endResultCache();
    }

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

    $cache_id = serialize(array($arParams, ($arParams['CACHE_GROUPS']==='N'? false: $USER->GetGroups()))); 
    $obCache = new CPHPCache; 
    if ($obCache->InitCache($arParams['CACHE_TIME'], $cache_id, '/')) 
    { 
       $vars = $obCache->GetVars(); 
       $arResult = $vars['arResult']; 
    } 
    elseif ($obCache->StartDataCache()) 
    { 
    
       // делаем то, что надо 
    
       $obCache->EndDataCache(array( 
          'arResult' => $arResult, 
       )); 
    } 
    

    Если код написан правильно и в template.php нет «тяжелого» кода, то этот вариант может работать достаточно хорошо.

    Внешняя авторизация

    Иногда для авторизации (проверки имени входа и пароля) пользователя бывает необходимо использовать свои алгоритмы проверки и(или) внешние БД для хранения пользователей. Например, уже имеется база пользователей и необходимо дать им возможность авторизовываться на сайте под управлением CMS. В таких случаях, иногда, можно просто перенести всех пользователей в БД CMS, используя API функции, но зачастую это просто невозможно по двум причинам:

    • Первая — пароли пользователей во внешней БД не хранятся в открытом виде, хранится только хэш от них, поэтому после переноса пользователи не смогут авторизоваться по причине несовпадения пароля.
    • Вторая причина заключается в том, что иногда необходимо чтобы база пользователей была единой, т.е. пароли пользователей хранились на одном удаленном сервере и использовались для проверки в нескольких местах, в том числе и в CMS.

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

    Для начала создадим файл, например, /bitrix/php_interface/scripts/phpbb.php. В этом файле будет размещён класс с внешним обработчиком, назовем его __PHPBB2Auth:

    class __PHPBB2Auth
    {
    }

    Чтобы во время попытки авторизоваться вызвалась наша функция, необходимо установить обработчик события OnUserLoginExternal, который будет вызываться автоматически каждый раз при вводе пользователем имени входа и пароля, перед встроенной проверкой. Для этого в файле /bitrix/php_interface/init.php воспользуемся функцией AddEventHandler:

    AddEventHandler(
         "main", 
         "OnUserLoginExternal", 
         Array("__PHPBB2Auth", "OnUserLoginExternal"), 
         100, 
         $_SERVER['DOCUMENT_ROOT'].'/bitrix/php_interface/scripts/phpbb.php'
    );

    В качестве обработчика мы указали метод нашего класса __PHPBB2Auth::OnUserLoginExternal. Обработчик события OnUserLoginExternal принимает в качестве параметра ссылку на массив с полями для проверки:

    define("PHPBB2_TABLE_PREFIX", "phpbb_");
    function OnUserLoginExternal(&$arArgs)
    {
        $table_user = PHPBB2_TABLE_PREFIX."users";
        $table_user_group = PHPBB2_TABLE_PREFIX."user_group";
        extract($arArgs);
    
        global $DB, $USER, $APPLICATION;
    
        $strSql = "SELECT * FROM ".
                  $table_user.
                  " WHERE username='".
                  $DB->ForSQL($login).
                  "' AND user_password='".
                  $DB->ForSql(md5($password))."'";
        $dbRes = $DB->Query($strSql);
        if($arRes = $dbRes->Fetch())
        {
            if($arRes['user_active']!='0')
            {
                // имя пользователя и пароль подошел
            }
        }
    }

    После того как имя входа и пароль проверены по алгоритму PHPBB, необходимо создать внешнего пользователя во внутренней БД, чтобы к нему можно было привязывать внутренние объекты (новости, голосования и т.п.). Для этого воспользуемся методом CUser::GetList с фильтром по имени входа и коду внешнего источника. Если такой пользователь не существует — создадим его, если существует — обновим информацию о нем.

    $arFields = Array(
        "LOGIN" => $login,
        "NAME" => $login,
        "PASSWORD" => $password,
        "EMAIL" => $arRes['user_email'],
        "ACTIVE" => "Y",
        "EXTERNAL_AUTH_ID"=>"PHPBB2",
        "LID" => SITE_ID
        );
    $oUser = new CUser;
    $res = CUser::GetList($O, $B, Array("LOGIN_EQUAL_EXACT"=>$login, "EXTERNAL_AUTH_ID"=>"PHPBB2"));
    if(!($ar_res = $res->Fetch()))
        $ID = $oUser->Add($arFields);
    else
    {
        $ID = $ar_res["ID"];
        $oUser->Update($ID, $arFields);
    }
    if($ID>0)
    {
       // можно авторизовывать
       return $ID;
    }

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

    $USER->SetParam("PHPBB2_USER_ID", $arRes['user_id']);
    $groups_map = Array(
        /*'PhpBB2 Group ID' => 'Local Group ID',*/
         '2' => '1'
        );
    
    $user_groups = Array();
    $dbUserGroup = $DB->Query('SELECT * FROM '.$table_user_group.' WHERE user_id='.$arRes['user_id']);
    while($arUserGroup = $dbUserGroup->Fetch())
        $user_groups[] = $arUserGroup['group_id'];
    
    if(count($user_groups)>0)
    {
        $arUserGroups = CUser::GetUserGroup($ID);
        foreach($groups_map as $ext_group_id => $group_id)
        {
            if(in_array($ext_group_id, $user_groups))
                $arUserGroups[] = $group_id;
            else
            {
                $arUserGroupsTmp = Array();
                foreach($arUserGroups as $grid)
                    if($grid != $group_id)
                        $arUserGroupsTmp[] = $grid;
                $arUserGroups = $arUserGroupsTmp;
            }
        }
        CUser::SetUserGroup($ID, $arUserGroups);
    }

    Все, теперь локальный аккаунт пользователя соответствует удаленному, можно возвращать код пользователя и он будет авторизован. Отключим предварительно функцию запомнить меня на этом компьютере, если пользователь нажал на «флажок», т.к. при этом мы не сможем корректно проверить права доступа:

    $arArgs["store_password"] = "N";
    return $ID;

    Для того чтобы зарегистрировать новую внешнюю проверку авторизации в системе, необходимо обработать событие OnExternalAuthList. Добавим в файл /bitrix/php_interface/init.php соответствующий вызов:

    AddEventHandler(
         "main", 
         "OnExternalAuthList", 
         Array("__PHPBB2Auth", "OnExternalAuthList"), 
         100, 
         $_SERVER['DOCUMENT_ROOT'].'/bitrix/php_interface/scripts/phpbb.php'
         );

    Функция обработчик должна вернуть массив из набора обработчиков с полямим ID и NAME.

    function OnExternalAuthList()
    {
        return Array(
            Array("ID"=>"PHPBB2", "NAME"=>"PhpBB2")
            );
    }

    Теперь на странице редактирования пользователя появится выпадающий список со списком внешних источников авторизации. Приведем полный текст файла /bitrix/php_interface/scripts/phpbb.php. В нем дополнительно реализован обратный механизм: при авторизации пользователя в нашей системе он автоматически авторизуется на форуме.

    <?
    define("PHPBB2_TABLE_PREFIX", "phpbb_");
    
    class __PHPBB2Auth
    {
        function OnUserLoginExternal(&$arArgs)
        {
            ////////// <settings> ////////////
            $table_user = PHPBB2_TABLE_PREFIX."users";
            $table_user_group = PHPBB2_TABLE_PREFIX."user_group";
            $groups_map = Array(
                /*'PhpBB2 Group ID' => 'Local Group ID',*/
                '2' => '1'
                );
            ////////// </settings> ////////////
            extract($arArgs);
    
            global $DB, $USER, $APPLICATION;
    
            $strSql = "SELECT * FROM ".
                      $table_user.
                      " WHERE username='".
                      $DB->ForSQL($login).
                      "' AND user_password='".
                      $DB->ForSql(md5($password))."'";
            $dbRes = $DB->Query($strSql);
            if($arRes = $dbRes->Fetch())
            {
                if($arRes['user_active']!='0')
                {
                    $arFields = Array(
                        "LOGIN" => $login,
                        "NAME" => $login,
                        "PASSWORD" => $password,
                        "EMAIL" => $arRes['user_email'],
                        "ACTIVE" => "Y",
                        "EXTERNAL_AUTH_ID"=>"PHPBB2",
                        "LID" => SITE_ID
                        );
                    $oUser = new CUser;
                    $res = CUser::GetList($O, $B, 
                                          Array("LOGIN_EQUAL_EXACT"=>$login, 
                                                "EXTERNAL_AUTH_ID"=>"PHPBB2"));
                    if(!($ar_res = $res->Fetch()))
                        $ID = $oUser->Add($arFields);
                    else
                    {
                        $ID = $ar_res["ID"];
                        $oUser->Update($ID, $arFields);
                    }
    
                    if($ID>0)
                    {
                        $USER->SetParam("PHPBB2_USER_ID", $arRes['user_id']);
    
                        $user_groups = Array();
                        $dbUserGroup = $DB->Query('SELECT * FROM '.
                                                  $table_user_group.
                                                  ' WHERE user_id='.$arRes['user_id']);
                        while($arUserGroup = $dbUserGroup->Fetch())
                            $user_groups[] = $arUserGroup['group_id'];
    
                        if(count($user_groups)>0)
                        {
                            $arUserGroups = CUser::GetUserGroup($ID);
                            foreach($groups_map as $ext_group_id => $group_id)
                            {
                                if(in_array($ext_group_id, $user_groups))
                                    $arUserGroups[] = $group_id;
                                else
                                {
                                    $arUserGroupsTmp = Array();
                                    foreach($arUserGroups as $grid)
                                        if($grid != $group_id)
                                            $arUserGroupsTmp[] = $grid;
                                    $arUserGroups = $arUserGroupsTmp;
                                }
                            }
                            CUser::SetUserGroup($ID, $arUserGroups);
                        }
                        $arArgs["store_password"] = "N";
    
                        return $ID;
                    }
                }
            }
        }
    
        function OnExternalAuthList()
        {
            return Array(
                Array("ID"=>"PHPBB2", "NAME"=>"PhpBB2 Board")
                );
        }
    
        function OnAuthorize(&$arArgs)
        {
            extract($arArgs);
    
            global $DB, $APPLICATION, $USER;
            $user_id = $USER->GetParam("PHPBB2_USER_ID");
            if($user_id<=0)
                return;
            $table_user = PHPBB2_TABLE_PREFIX."users";
            $table_sessions = PHPBB2_TABLE_PREFIX."sessions";
            $table_config = PHPBB2_TABLE_PREFIX."config";
    
            $dbConfig = $DB->Query("SELECT * FROM ".
                                   $table_config.
                                   " WHERE config_name
                                     IN ('cookie_name', 'cookie_path', 'cookie_domain', 'cookie_secure')");
            while($arConfig = $dbConfig->Fetch())
                ${$arConfig['config_name']} = $arConfig['config_value'];
    
            if (isset($HTTP_COOKIE_VARS[$cookie_name . '_sid']) || 
                isset($HTTP_COOKIE_VARS[$cookie_name . '_data']))
                $session_id = isset($HTTP_COOKIE_VARS[$cookie_name . '_sid']) ? 
                                   $HTTP_COOKIE_VARS[$cookie_name . '_sid'] : '';
    
            $ip_sep = explode('.', $_SERVER['REMOTE_ADDR']);
            $user_ip = sprintf('%02x%02x%02x%02x', $ip_sep[0], $ip_sep[1], $ip_sep[2], $ip_sep[3]);
            $current_time = time();
            $sql =
                "UPDATE ".$table_sessions." SET ".
                "    session_user_id = ".$user_id.", ".
                "    session_start = ".$current_time.", ".
                "    session_time = ".$current_time.", ".
                "    session_page = 0, ".
                "    session_logged_in = 1 ".
                "WHERE session_id = '".$DB->ForSQL($session_id)."' ".
                "    AND session_ip = '".$user_ip."'";
    
            $r = $DB->Query($sql);
            if($r->AffectedRowsCount()<=0)
            {
                $session_id = md5(uniqid($user_ip));
                $sql =
                    "INSERT INTO ".
                    $table_sessions.
                    "(session_id, session_user_id, session_start, session_time, session_ip, session_page, session_logged_in)".
                    "VALUES ('".$session_id."', ".$user_id.", ".$current_time.", ".$current_time.", '".$user_ip."', 0, 1)";
                $DB->Query($sql);
            }
    
            $sql =
                "UPDATE ".$table_user." SET ".
                "    user_session_time = ".$current_time.", ".
                "    user_session_page = 0, ".
                "    user_lastvisit = ".$current_time." ".
                "WHERE user_id = ".$user_id;
    
            $DB->Query($sql);
    
            $sessiondata = Array('userid' => $user_id);
    
            setcookie($cookie_name.'_data', 
                      serialize($sessiondata), 
                      $current_time + 31536000, 
                      $cookie_path, 
                      $cookie_domain, $cookie_secure);
            setcookie($cookie_name.'_sid',
                      $session_id, 0, $cookie_path, 
                      $cookie_domain, $cookie_secure);
        }
    }
    ?>

    Следующие строки необходимо добавить в /bitrix/php_interface/init.php:

    <?
    AddEventHandler(
        "main", 
        "OnUserLoginExternal", 
        Array("__PHPBB2Auth", "OnUserLoginExternal"),
        100, 
        $_SERVER['DOCUMENT_ROOT'].'/bitrix/php_interface/scripts/phpbb.php'
        );
    
    AddEventHandler(
        "main", 
        "OnExternalAuthList", 
        Array("__PHPBB2Auth", "OnExternalAuthList"),
        100,
        $_SERVER['DOCUMENT_ROOT'].'/bitrix/php_interface/scripts/phpbb.php'
        );
    
    AddEventHandler(
        "main", 
        "OnAfterUserAuthorize", 
        Array("__PHPBB2Auth", "OnAuthorize"),
        100
        );
    ?>

    В качестве примера ещё один скрипт внешней авторизации — для форума Invision Power Board:

    <?
    define("IPB_TABLE_PREFIX", "ibf_");
    define("IPB_VERSION", "2");
    
    AddEventHandler("main", "OnUserLoginExternal", Array("__IPBAuth", "OnUserLoginExternal"));
    AddEventHandler("main", "OnExternalAuthList", Array("__IPBAuth", "OnExternalAuthList"));
    
    class __IPBAuth
    {
        function OnUserLoginExternal(&$arArgs)
        {
            extract($arArgs);
    
            ////////// <settings> ////////////
            $table_user = IPB_TABLE_PREFIX."members";
            $table_converge = IPB_TABLE_PREFIX."members_converge";
            $groups_map = Array(
                /*'IPB Group ID' => 'Local Group ID',*/
                '4' => '1'
                );
            ////////// </settings> ////////////
    
            global $DB, $USER, $APPLICATION;
    
            if(IPB_VERSION == '1')
            {
                $strSql = "SELECT * FROM ".$table_user." WHERE name='".$DB->ForSql($login)."' AND password='".md5($password)."'";
            }
            else
            {
                $strSql =
                    "SELECT t1.* ".
                    "FROM ".$table_user." t1, ".$table_converge." t2 ".
                    "WHERE t1.name='".$DB->ForSql($login)."' ".
                    "    AND t1.email = t2.converge_email ".
                    "    AND t2.converge_pass_hash = MD5(CONCAT(MD5(t2.converge_pass_salt), '".md5($password)."'))";
            }
    
            $dbAuthRes = $DB->Query($strSql);
            if($arAuthRes = $dbAuthRes->Fetch())
            {
                $arFields = Array(
                    "LOGIN" => $login,
                    "NAME" => $arAuthRes['title'],
                    "PASSWORD" => $password,
                    "EMAIL" => $arAuthRes['email'],
                    "ACTIVE" => "Y",
                    "EXTERNAL_AUTH_ID"=>"IPB",
                    "LID" => SITE_ID
                    );
    
                $oUser = new CUser;
                $res = CUser::GetList($O, $B, Array("LOGIN_EQUAL_EXACT"=>$login, "EXTERNAL_AUTH_ID"=>"IPB"));
                if(!($ar_res = $res->Fetch()))
                    $ID = $oUser->Add($arFields);
                else
                {
                    $ID = $ar_res["ID"];
                    $oUser->Update($ID, $arFields);
                }
    
                if($ID>0)
                {
                    $USER->SetParam("IPB_USER_ID", $arAuthRes['id']);
    
                    $user_group = $arAuthRes['mgroup'];
                    $arUserGroups = CUser::GetUserGroup($ID);
                    foreach($groups_map as $ext_group_id => $group_id)
                    {
                        if($ext_group_id==$user_group)
                            $arUserGroups[] = $group_id;
                        else
                        {
                            $arUserGroupsTmp = Array();
                            foreach($arUserGroups as $grid)
                                if($grid != $group_id)
                                    $arUserGroupsTmp[] = $grid;
                            $arUserGroups = $arUserGroupsTmp;
                        }
                    }
                    CUser::SetUserGroup($ID, $arUserGroups);
                    $arArgs["store_password"] = "N";
    
                    return $ID;
                }
            }
        }
    
        function OnExternalAuthList()
        {
            return Array(
                Array("ID"=>"IPB", "NAME"=>"Invision Power Board")
                );
        }
    }
    ?>

    Для того чтобы данный скрипт начал работать, его необходимо подключить в /bitrix/php_interface/init.php.

    Безопасность

    Вопросы безопасности при программировании в Bitrix Framework.

    Санитайзер

    Санитайзер — инструмент, анализирующий введённый пользователем html код. Основная задача санитайзера — предотвратить внедрение/вывод на экран потенциально опасного кода в HTML.

    Санитайзер удобно использовать там, где пользователь вводит произвольный html. Например, в визуальном редакторе или при копировании текста из MS Word. Кроме функций контроля введённого кода санитайзер частично отслеживает валидность вёрстки, в частности закрывает незакрытые теги.

    Как фильтровать текст

    Если необходимо отфильтровать текст (содержащий HTML — тэги) введенный пользователем от нежелательных тэгов HTML с помощью санитайзера, то можно это сделать так:

    $Sanitizer = new CBXSanitizer;
    
    $Sanitizer->AddTags( array (
                      'a' = > array('href','id','style','alt'...),
                      'br' => array(),
                         .... ));
    
    $pureHtml = $Sanitizer->SanitizeHtml($html);

    Санитайзер отфильтрует все тэги и атрибуты, которые не содержатся в «белом» списке, сформированном функцией AddTags().

    В санитайзер включены 3 преднастроенных уровня фильтрации:

    SECURE_LEVEL_HIGH (высокий уровень) включает следующий список:

    $arTags = array(
                            'b'        => array(),
                            'br'        => array(),
                            'big'        => array(),
                            'blockquote'    => array(),
                            'code'        => array(),
                            'del'        => array(),
                            'dt'        => array(),
                            'dd'        => array(),
                            'font'        => array(),
                            'h1'        => array(),
                            'h2'        => array(),
                            'h3'        => array(),
                            'h4'        => array(),
                            'h5'        => array(),
                            'h6'        => array(),
                            'hr'        => array(),
                            'i'        => array(),
                            'ins'        => array(),
                            'li'        => array(),
                            'ol'        => array(),
                            'p'        => array(),
                            'small'        => array(),
                            's'        => array(),
                            'sub'        => array(),
                            'sup'        => array(),
                            'strong'    => array(),
                            'pre'        => array(),
                            'u'        => array(),
                            'ul'        => array()
                        );

    SECURE_LEVEL_MIDDLE (средний уровень) включает в себя:

    $arTags = array(
                            'a'        => array('href', 'title','name','alt'),
                            'b'        => array(),
                            'br'        => array(),
                            'big'        => array(),
                            'blockquote'    => array('title'),
                            'code'        => array(),
                            'caption'    => array(),
                            'del'        => array('title'),
                            'dt'        => array(),
                            'dd'        => array(),
                            'font'        => array('color','size'),
                            'color'        => array(),
                            'h1'        => array(),
                            'h2'        => array(),
                            'h3'        => array(),
                            'h4'        => array(),
                            'h5'        => array(),
                            'h6'        => array(),
                            'hr'        => array(),
                            'i'        => array(),
                            'img'        => array('src','alt','height','width','title'),
                            'ins'        => array('title'),
                            'li'        => array(),
                            'ol'        => array(),
                            'p'        => array(),
                            'pre'        => array(),
                            's'        => array(),
                            'small'        => array(),
                            'strong'    => array(),
                            'sub'        => array(),
                            'sup'        => array(),
                            'table'        => array('border','width'),
                            'tbody'        => array('align','valign'),
                            'td'        => array('width','height','align','valign'),
                            'tfoot'        => array('align','valign'),
                            'th'        => array('width','height'),
                            'thead'        => array('align','valign'),
                            'tr'        => array('align','valign'),
                            'u'        => array(),
                            'ul'        => array()
                        );

    SECURE_LEVEL_LOW (низкий уровень) включает в себя:

    $arTags = array(
                            'a'        => array('href', 'title','name','style','id','class','shape','coords','alt','target'),
                            'b'        => array('style','id','class'),
                            'br'        => array('style','id','class'),
                            'big'        => array('style','id','class'),
                            'blockquote'    => array('title','style','id','class'),
                            'caption'    => array('style','id','class'),
                            'code'        => array('style','id','class'),
                            'del'        => array('title','style','id','class'),
                            'div'        => array('title','style','id','class','align'),
                            'dt'        => array('style','id','class'),
                            'dd'        => array('style','id','class'),
                            'font'        => array('color','size','face','style','id','class'),
                            'h1'        => array('style','id','class','align'),
                            'h2'        => array('style','id','class','align'),
                            'h3'        => array('style','id','class','align'),
                            'h4'        => array('style','id','class','align'),
                            'h5'        => array('style','id','class','align'),
                            'h6'        => array('style','id','class','align'),
                            'hr'        => array('style','id','class'),
                            'i'        => array('style','id','class'),
                            'img'        => array('src','alt','height','width','title'),
                            'ins'        => array('title','style','id','class'),
                            'li'        => array('style','id','class'),
                            'map'        => array('shape','coords','href','alt','title','style','id','class','name'),
                            'ol'        => array('style','id','class'),
                            'p'        => array('style','id','class','align'),
                            'pre'        => array('style','id','class'),
                            's'        => array('style','id','class'),
                            'small'        => array('style','id','class'),
                            'strong'    => array('style','id','class'),
                            'span'        => array('title','style','id','class','align'),
                            'sub'        => array('style','id','class'),
                            'sup'        => array('style','id','class'),
                            'table'        => array('border','width','style','id','class','cellspacing','cellpadding'),
                            'tbody'        => array('align','valign','style','id','class'),
                            'td'        => array('width','height','style','id','class','align','valign','colspan','rowspan'),
                            'tfoot'        => array('align','valign','style','id','class','align','valign'),
                            'th'        => array('width','height','style','id','class','colspan','rowspan'),
                            'thead'        => array('align','valign','style','id','class'),
                            'tr'        => array('align','valign','style','id','class'),
                            'u'        => array('style','id','class'),
                            'ul'        => array('style','id','class')
                        );

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

    $Sanitizer = new CBXSanitizer;
    
    $Sanitizer->SetLevel(CBXSanitizer::SECURE_LEVEL_MIDDLE);
    
    $pureHtml = $Sanitizer->SanitizeHtml($html);

    Для работы с санитайзером доступны функции класса CBXSanitizer.

    Защита от фреймов

    На странице Защита от фреймов (Настройки > Проактивная защита > Защита от фреймов) можно включить/отключить ограничение работы во фрейме, а также задать исключения на действие защиты.

    Запрет на использование кросс-доменных фреймов ссылающихся на страницы ресурса задается установкой заголовка X-Frame-Options в значение SAMEORIGIN.

    X-Frame-Options

    Данный заголовок указывает браузеру, можно ли загружать страницы сайта через <frame>/<iframe>.

    Значение DENY запретит загрузку через фреймы, значение SAMEORIGIN разрешит загрузку через фреймы, но только если и фрейм, и страница, его загружающая, находятся на одном домене (Same Origin Policy).

    Основная функция данной защиты — предотвращение кликджекинга; в качестве дополнительного бонуса это позволит предотвратить атаку, описанную Ben Schmidt.

    Вирус на сайте

    Под вирусом на сайте понимается определенный зашифрованный JavaScript-код, который размещается в коде страницы сайта. При его выполнении формируется так называемый iframe (HTML-элемент, позволяющий включить при отображении содержимое одной страницы в другую). Вставленный iframe указывает, как правило, на зараженную страницу, которая уже содержит более опасный код, использующий различные уязвимости браузеров для загрузки и запуска исполняемых файлов вирусов. В данной главе будет рассмотрено как с ними бороться.

    Основные методы взлома

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

    1. Первая и самая распространенная причина — это виртуальный (разделяемый) хостинг. Архитектурно он не обеспечивает полную изоляцию клиентов, поэтому взлом одного сайта на хостинге часто ведет к взлому соседних. К сожалению, практически невозможно защититься от уязвимости хостинга. Единственное, что можно сделать, это вовремя зафиксировать заражение и обратиться к хостеру.
    2. Устаревшее или уязвимое ПО, которое располагается на том же или соседнем аккаунте хостинга. Для устаревших версий в интернете можно найти давно существующие способы взлома. Чтобы уберечься от заражения, не рекомендуется размещать на одном аккаунте хостинга коммерческий сайт и частную страницу работающую на небезопасном ПО.
    3. Чаще всего злоумышленники похищают сохраненные ftp пароли и сохраненные пароли браузера. Поэтому немаловажное значение имеет выбор паролей достаточной сложности, в том числе включающих и спецсимволы. Сильно повысить защищенность сайта может подключение CAPTCHA и двухэтапной авторизации.
    4. Не столь часто, но все-таки случается ситуация, когда низкоквалифицированные программисты (иногда и фрилансеры) наносят вред сайту. К сожалению, защититься от человеческого фактора практически нельзя.

    Ложные срабатывания антивируса

    Работа веб-антивируса основана на эвристическом анализе потенциально опасных частей кода, поэтому возможно и ложное срабатывание антивируса.

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

    Определение безопасности блоков кода с помощью персональных антивирусов может быть достаточно сложной задачей, а может вовсе не дать результата. Такие блоки html-кода не содержат ни троянов, ни вирусов, а содержат лишь ссылки на них, которые могу не вызвать срабатывания антивируса.

    Итак, если выяснено, что блок кода, на который срабатывает веб-антивирус 1С-Битрикс не является вредоносным и не содержит ссылки на загрузку вирусов, то нужно добавить этот блок в исключение. Для этого необходимо взять некоторую строку из этого блока (достаточно длинную и уникальную) и добавить ее в исключение веб-антивируса. В результате, антивирус перестанет срабатывать на любые, обрабатываемые им блоки, содержащие указанную строку.

    Если обнаружен вирус

    Если все-таки выяснено, что блок содержит ссылку на вирус, то меры необходимо принимать незамедлительно. Наличие стороннего кода на сервере говорит о том, что злоумышленники могли получить доступ к файлам на сервере. В подавляющем большинстве случаев наличие вирусных блоков означает, что один из компьютеров, имеющих доступ на сервер через FTP (SSH, SFTP и т.п.), заражен вирусом и с него был похищен пароль от сервера. Поэтому при обнаружении вируса на сайте необходимо выполнить следующие шаги:

    1. Проверить все компьютеры людей, имеющим доступ к сайту (в том числе к панели администрирования 1С-Битрикс), персональным антивирусным программным обеспечением.
    2. После того, как все компьютеры были вылечены, необходимо сменить все пароли на сервере (пароли на FTP, SSH, пароли к базе данных, пароли пользователей, имеющих доступ к административному разделу сайта).
    3. Затем следует вычистить весь сторонний код на сервере. Проще всего для отслеживания изменившихся файлов использовать контроль целостности файлов 1С-Битрикс. Но это только в том случае, если вы заранее озаботились безопасностью и периодически запускаете контроль целостности файлов.
    4. Если контроль целостности не используется, то поиск изменений, оставленных хакером может быть весьма сложной задачей. Для поиска таких изменений, можно провести поиск по всем файлам на сервере, содержащим строки из блока, на который сработал веб-антивирус, поиск и ручная проверка всех недавно изменившихся файлов, проанализировать логи http сервера.

    Чистка сайта от вирусов

    Важно! Перед выполнением скрипта необходимо сделать полную резервную копию.

    Даже если почистить компьютер от вирусов персональным антивирусом, то сайт все равно останется в нерабочем состоянии. Удалить вирусный код вручную очень сложно, к тому же искать зараженные файлы на дальнем хостинге не всегда возможно. Скачивать сайт на локальный компьютер тоже неразумно. Поэтому мы будем пользоваться php-скриптом.

    По сути создаваемый скрипт будет обычным поиском текста в файле, только с добавлением к нему рекурсивного сканирования директорий. Скрипт показывает форму для ввода кода вируса (или иного текста, который надо найти), затем начинает сканировать все файлы с расширением .php в текущей папке и подпапках.

    После выполнения скрипта список найденных файлов сохраняется в файл filelist.txt, затем он выводится на экран.

    Автоматическая замена

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

    Как можно заметить, после выполнения скрипта создается файл со списком зараженных файлов. Почему бы в таком случае просто не удалить все зараженные элементы? Автоматически удалять вирус тоже очень опасно, потому что есть серьёзная опасность потерять данные из своих скриптов.

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

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

    Скачать полученный скрипт можно здесь.

    Примечание: скрипт написан для php5, на php4 потребуется заменять функцию file_get_contents.

    Защита от троянов

    Практически невозможно найти способ защититься на 100% от троянов. Однако можно воспользоваться бесплатным решением от 1С-Битрикс Поиск троянов, которое может обнаруживать наиболее характерные из них. Решение оформлено в виде модуля и бесплатно доступно в marketplace.

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

    Обнаруженные трояны нужно поместить в карантин, просканировать компьютер на вирусы персональным антивирусным ПО, после чего нужно сменить все пароли и обратиться к хостеру с требованием проанализировать причину взлома.

    Важно! Перед началом сканирования обязательно необходимо проверить целостность ядра используя функционал Монитора качества.

    Модули

    Bitrix Framework имеет модульную структуру. Каждый модуль отвечает за управление определенными элементами и параметрами сайта: информационным наполнением и структурой сайта, форумами, рекламой, рассылкой, распределением прав между группами пользователей, сбором статистики посещений, оценкой эффективности рекламных кампаний и т.д.

    Модуль — это блок, отвечающий за определенную функциональность продукта. Модуль может содержать API всех уровней (включая API доступа к данным, бизнес-логику и API пользовательского интерфейса), HTML верстку, пользовательские интерфейсы, компоненты, роботы, административные страницы и тому подобное.

    Модули системы, главным образом, работают независимо друг от друга. Однако в целом ряде случаев функционал одних модулей основан на возможностях других. Например:

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

    После установки системы список используемых модулей можно просмотреть на странице Управление модулями (Настройки > Настройки продукта > Модули) в административном разделе системы:

    Для экономии дискового пространства неиспользуемые модули рекомендуется удалить, при этом дистрибутив модуля остается в системе, и он в любое время может быть снова установлен. При деинсталляции некоторых модулей система предлагает сохранить накопленные модулем данные (таблицы модуля). Если вы в дальнейшем планируете использовать эти данные, то при удалении модуля необходимо отметить соответствующую опцию.

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

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

    • с помощью административного меню: Настройки > Настройки продукта > Настройки модулей > имя_модуля;
    • с помощью кнопки , расположенной на административной панели. Данная кнопка позволяет перейти к настройкам модуля, страницы (формы) которого открыты в текущий момент в основной рабочей области.

    Примечание: Перед использованием модуля необходимо проверить установлен ли он и подключить его при помощи конструкции:

    <?
    use BitrixMainLoader;
    
    if (Loader::includeModule('******')) {
        // здесь можно использовать функции и классы модуля
    }?>

    где **** — идентификатор модуля.

    Модули и компоненты

    Модули в Bitrix Framework представляют собой модели и контроллеры нижнего уровня (в понятиях MVC), а компоненты — контроллеры верхнего уровня, включающие представления, которые основаны на иерархии файловой структуры сайта. Весь функционал любого сайта, как правило, реализуется на стандартных модулях, но приходится кастомизировать компоненты (или написать свои) для формирования и вывода страниц и подключать их на соответствующих страницах сайта.

    Разработка модулей

    Bitrix Framework допускает разработку пользовательских модулей.

    Модули в D7

    Модули системы (как штатные, так и загруженные из Marketplace) располагаются в системной папке bitrix/modules. В папке /local/modules могут располагаться [dw]пользовательские модули[/dw][di]Партнерские модули отличаются от стандартных модулей следующим…
    Подробнее…[/di]. Такое разделение позволит сторонним разработчикам просто организовать контроль версий своих разработок с сохранением обновляемости продукта стандартной системой обновлений.

    Структура файлов и папок модулей на D7 осталась такой же, как и в старой версии ядра.

    API (классы, логика) модуля располагается в подпапке [dw]/lib[/dw][di]Данная папка не является обязательной, если у вашего класса нет собственных методов.[/di] папки модуля. Например, для Главного модуля путь будет таким: bitrix/modules/main/lib. Из этой же папки API будет автоматически подключаться при соблюдении некоторых правил:

    • Файлы классов должны именоваться в нижнем регистре, при этом название класса равно название файла. Пример: /lib/myclass.php.
    • Файлы должны содержать правильный namespace. Пример: если модуль подключается как:
      Loader::includeModule('company.shotname');
      то в классе должен быть прописан namespace: namespace CompanyShotname.
    • Там где классы модуля используются в административной части — модуль должен быть подключен.

    Структура файлов

    Файлы модуля располагаются в папке /bitrix/modules/ID модуля/. Структура папки:

    • admin/ — каталог с административными скриптами модуля;
      • menu.php — файл с административным меню модуля;
    • classes/ — скрипты с классами модуля;
      • general/ — классы модуля, не зависящие от используемой базы данных;
      • mysql/ — классы модуля, предназначенные для работы только с MySQL;
      • mssql/ — классы модуля, предназначенные для работы только с MS SQL;
      • oracle/ — классы модуля, предназначенные для работы только с Oracle;
    • lang/ID языка/ — каталог с языковыми файлами скриптов модуля;
    • lib/ — каталог с файлами (API: классы, логика) нового ядра D7 (может не присутствовать, если у модуля нет собственных методов);
    • install/ — каталог с файлами используемыми для инсталляции и деинсталляции модуля;
      • admin/ — каталог со скриптами подключающими административные скрипты модуля (вызывающие скрипты);
      • js/ — каталог с js-скриптами модуля. Копируются в /bitrix/js/ID_модуля/;
      • db/ — каталог с SQL скриптами для инсталляции/деинсталляции базы данных;
        • mysql/ — SQL скрипты для инсталляции/деинсталляции таблиц в MySQL;
        • mssql/ — SQL скрипты для инсталляции/деинсталляции таблиц в MS SQL;
        • oracle/ — SQL скрипты для инсталляции/деинсталляции таблиц в Oracle;
      • images/ — каталог с изображениями используемыми модулем; после инсталляции модуля они должны быть скопированы в каталог /bitrix/images/ID модуля/;
      • templates/ — каталог с компонентами 1.0 модуля. (Каталог сохраняется только с целью совместимости версий.);
        • ID модуля/ — каталог с основными файлами компонент;
        • lang/ID языка/ID модуля/ — в данном каталоге находятся языковые файлы компонент модуля;
      • components/пространство имен/имя компонента/ — каталог с компонентами 2.0 модуля;
      • themes/имя_модуля/ — содержит css и картинки для стилей административной панели, если модуль в таковых нуждается (Устаревшая, до версии 12.0);
      • panel/имя_модуля/ — содержит css и картинки для стилей административной панели, если модуль в таковых нуждается.
      • index.php — файл с описанием модуля;
      • version.php — файл с номером версии модуля. Версия не может быть равной нулю.
    • include.php — данный файл подключается в тот момент, когда речь идет о подключении модуля в коде, в нем должны находиться включения всех файлов с библиотеками функций и классов модуля;
    • default_option.php — содержит массив с именем $ID модуля_default_option, в котором заданы значения по умолчанию для параметров модуля;

      Примечание: В случае партнерских модулей, в названии которых содержится точка (пример — mycompany.forum) в имени переменной точка будет автоматически заменена на символ подчеркивания.

    • options.php — данный файл подключается на странице настройки параметров модулей в административном меню Настройки;
    • prolog.php — файл должен подключаться во всех административных скриптах модуля. Обычно в нем определяется константа ADMIN_MODULE_NAME (идентификатор модуля), используемая в панели управления;
    • .settings.php — файл настроек модуля, описывающий настройки модуля, которые можно прочитать через BitrixMainConfigConfiguration::getInstance($module).

    Описание и параметры

    Описание

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

    Основным файлом, используемым системой для манипуляции модулем, является /bitrix/modules/ID модуля/install/index.php. (ID модуля в этом случае — это полный код партнерского модуля, который задается в формате: код_партнера.код_модуля.) Основное назначение этого файла — это размещение в нем класса с именем, совпадающим с ID модуля. (ID модуля здесь используется в формате код_партнера_код_модуля, так как в имени класса точка недопустима.)

    Пример:

    01	<?
    02	Class mymodule extends CModule
    03	{
    04	    var $MODULE_ID = "mymodule";
    05	    var $MODULE_NAME;
    06	 
    07	    function DoInstall()
    08	    {
    09	        global $DB, $APPLICATION, $step;
    10	        $APPLICATION->IncludeAdminFile(GetMessage("FORM_INSTALL_TITLE"), $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/mymodule/install/step1.php");
    11	    }
    12	 
    13	    function DoUninstall()
    14	    {
    15	        global $DB, $APPLICATION, $step;
    16	        $APPLICATION->IncludeAdminFile(GetMessage("FORM_INSTALL_TITLE"), $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/mymodule/install/unstep1.php");
    17	 
    18	    }
    19	}
    20	?>

    Обязательные методы этого класса:

    • DoInstall — запускается при нажатии кнопки Установить на странице Модули административного раздела, осуществляет инсталляцию модуля.
    • DoUninstall — запускается при нажатии кнопки Удалить на странице Модули административного раздела, осуществляет деинсталляцию модуля.

    Необязательный метод этого класса:

    • GetModuleRightList — возвращает список уникальных прав (или ролей) модуля.

    Обязательные свойства объекта этого класса:

    • MODULE_ID — хранит ID модуля (полный код партнерского модуля);
    • MODULE_VERSION — текущая версия модуля в формате XX.XX.XX;
    • MODULE_VERSION_DATE — строка содержащая дату версии модуля; дата должна быть задана в формате YYYY-MM-DD HH:MI:SS;
    • MODULE_NAME — имя модуля;
    • MODULE_DESCRIPTION — описание модуля;
    • MODULE_GROUP_RIGHTS — если задан метод GetModuleRightList, то данное свойство должно содержать Y.

    Примеры

    Пример файла с описанием модуля Веб-формы:

    <?
    global $MESS;
    $PathInstall = str_replace("\", "/", __FILE__);
    $PathInstall = substr($PathInstall, 0, strlen($PathInstall)-strlen("/index.php"));
    IncludeModuleLangFile($PathInstall."/install.php");
    include($PathInstall."/version.php");
    if(class_exists("form")) return;
    Class form extends CModule
    {
        var $MODULE_ID = "form";
        var $MODULE_VERSION;
        var $MODULE_VERSION_DATE;
        var $MODULE_NAME;
        var $MODULE_DESCRIPTION;
        var $MODULE_GROUP_RIGHTS = "Y";
    
        function __construct()
        {
            $this->MODULE_VERSION = FORM_VERSION;
            $this->MODULE_VERSION_DATE = FORM_VERSION_DATE;
            $this->MODULE_NAME = GetMessage("FORM_MODULE_NAME");
            $this->MODULE_DESCRIPTION = GetMessage("FORM_MODULE_DESCRIPTION");
        }
    
        function DoInstall()
        {
            global  $APPLICATION;
            $FORM_RIGHT = $APPLICATION->GetGroupRight("form");
            if ($FORM_RIGHT=="W")
            {
                $step = IntVal($step);
                if($step<2)
                    $APPLICATION->IncludeAdminFile(GetMessage("FORM_INSTALL_TITLE"),
                    $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/form/install/step1.php");
                elseif($step==2)
                    $APPLICATION->IncludeAdminFile(GetMessage("FORM_INSTALL_TITLE"),
                    $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/form/install/step2.php");
            }
        }
    
        function DoUninstall()
        {
            global $APPLICATION;
            $FORM_RIGHT = $APPLICATION->GetGroupRight("form");
            if ($FORM_RIGHT=="W")
            {
                $step = IntVal($step);
                if($step<2)
                    $APPLICATION->IncludeAdminFile(GetMessage("FORM_UNINSTALL_TITLE"),
                    $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/form/install/unstep1.php");
                elseif($step==2)
                    $APPLICATION->IncludeAdminFile(GetMessage("FORM_UNINSTALL_TITLE"),
                    $_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/form/install/unstep2.php");
            }
        }
    
        function GetModuleRightList()
        {
            global $MESS;
            $arr = array(
                "reference_id" => array("D","R","W"),
                "reference" => array(
                    GetMessage("FORM_DENIED"),
                    GetMessage("FORM_OPENED"),
                    GetMessage("FORM_FULL"))
                );
            return $arr;
        }
    }
    ?>

    Пример файла с указанием версии модуля

    <?
    $arModuleVersion = array(
        "VERSION" => "11.0.4",
        "VERSION_DATE" => "2011-11-17 14:00:00"
    );
    ?>

    Параметры

    Параметры модуля доступны для изменения в административном интерфейсе на странице Настройки модулей (Настройки > Настройки продукта > Настройки модулей). При выборе модуля на данной странице, система подключает файл /bitrix/modules/my_module_id/options.php, предназначенный для управления параметрами модуля, назначения прав на модуль и т.п.

    Параметры модуля хранятся в базе данных.

    При получении параметров модуля, может использоваться значение по умолчанию, задаваемое в файле /bitrix/modules/my_module_id/default_option.php. В данном файле определяется массив my_module_id_default_option, хранящий значения по умолчанию.

    Пример файла /bitrix/modules/my_module_id/default_option.php:

    <?
    /*
     * Файл local/modules/my_module_id/default_option.php
     */
    $my_module_id_default_option = array(
        "MY_PARAMETER_ID"  =>  "DEFAULT_VALUE"
        );
    ?>

    Для работы с параметрами модуля предназначен класс COption. Методы класса:

    • SetOptionString — установка строковых параметров
    • SetOptionInt — установка числовых параметров
    • GetOptionString — получение строковых параметров
    • GetOptionInt — получение числовых параметров
    • RemoveOption — удаление параметра

    Примеры использования

    Строковый параметр

    <?
    // установим строковый параметр
    COption::SetOptionString("my_module_id", "MY_PARAMETER_ID", "VALUE");
    
    // получим строковый параметр
    $value = COption::GetOptionString("my_module_id", "MY_PARAMETER_ID", "DEFAULT_VALUE");
    ?>

    Примечание: При использовании файла default_option.php, заданные в нем значения параметров по умолчанию, не нужно передавать третьим аргументом def при вызове метода GetOptionString.
    Названия параметров, должны быть идентичными в файле default_option.php и в методах класса COption.

    Параметр типа файл. (Загрузка файла в модуле)

    <table>
    <tr> <td width="40%">
           <?
           $path_file = COption::GetOptionString("my_module_id", 'CLEVERSCRIPT_IMG_DESCTOP_BTN');
           CAdminFileDialog::ShowScript
           (
               Array(
                   "event" => "BtnClick_0",
                   "arResultDest" => array("FORM_NAME" => "cleverscriptwantcheaper", "FORM_ELEMENT_NAME" => "CLEVERSCRIPT_IMG_DESCTOP_BTN"),
                   "arPath" => array("PATH" => GetDirPath($path_file)),
                   "select" => 'F',// F - file only, D - folder only
                   "operation" => 'S',// O - open, S - save
                   "showUploadTab" => true,
                   "showAddToMenuTab" => false,
                   "fileFilter" => 'jpg,jpeg,gif,png',
                   "allowAllFiles" => true,
                   "SaveConfig" => true,
               )
           );
           ?>
           <?echo Loc::getMessage("T_CLEVERSCRIPT_IMG_DESCTOP_BTN")?>
       </td>
       <td width="60%">
           <input type="text" name="CLEVERSCRIPT_IMG_DESCTOP_BTN" size="50" maxlength="255" value="<?echo $path_file?>"> <input type="button" name="browse_0" value="..." onClick="BtnClick_0()">
       </td>
    </tr>
    </table>
    
    

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

    • Option — класс для работы с параметрами модулей, хранимых в базе данных. Аналог класса COption в старом ядре.

    Административные скрипты

    Административные скрипты — это скрипты используемые модулем в административной части системы. Они должны располагаться в каталоге /bitrix/modules/ID модуля/admin/.

    Необходимо учитывать, что напрямую в браузере административные скрипты нельзя вызывать (как в общем-то и любые скрипты каталога /bitrix/modules/). Поэтому для их вызова используются дополнительные одноименные вызывающие скрипты, находящиеся в каталоге /bitrix/admin/. Они, как правило, состоят только из подключения одноименного административного скрипта:

    <?
    require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/ID модуля/admin/имя скрипта");
    ?>

    При инсталляции модуля, вызывающие скрипты должны быть скопированы из каталога /bitrix/modules/ID модуля/install/admin/ в каталог /bitrix/admin/. В момент деинсталляции модуля вызывающие скрипты должны быть удалены из этого каталога.

    Примечание: Необходимо учитывать, что вызывающие скрипты всех инсталлированных модулей находятся в одном каталоге /bitrix/admin/, поэтому во избежание дублирования, желательно давать им имена начинающиеся с какого-либо префикса характерного только для соответствующего модуля.

    В каждом административном скрипте до подключения визуальной части административного пролога необходимо определять константу ADMIN_MODULE_NAME, необходимую для формирования иконки над заголовком страницы. Константа нужна чтобы при нажатии кнопки Настройки сразу перейти к настройкам модуля. Она обычно задается в prolog.php.

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

    define("ADMIN_MODULE_NAME", "statistic");
    

    Языковые файлы для административных скриптов модуля

    Языковые файлы должны располагаться в каталоге /bitrix/modules/ID модуля/lang/ID языка/.
    Особенностью их расположения внутри этого каталога является то, что они должны располагаться строго по тому же пути относительно каталога /bitrix/modules/ID модуля/, что одноименные файлы в которых они подключаются. В этом случае подключение языковых файлов может осуществляться только функцией IncludeModuleLangFile.

    К примеру для скрипта /bitrix/modules/ID модуля/admin/body/my_script.php языковой файл должен быть расположен: /bitrix/modules/ID модуля/lang/ID языка/admin/body/my_script.php.

    И подключаться кодом:

    IncludeModuleLangFile(__FILE__);

    Административное меню

    Меню административной части выводится стандартной функцией CMain::GetMenuHtmlEx.

    Меню формируется на основе перебора всех файлов /bitrix/modules/ID модуля/admin/menu.php. В каждом таком файле содержится определение массива $aModuleMenuLinks, содержащего пункты меню соответствующего модуля. Все эти массивы затем будут объединены в стандартный массив $arMenuSections, содержащий информацию о всех пунктах меню.

    Образец структуры меню на примере bitrixmodulesmainadminmenu.php

    $aMenu[] = array(
       "parent_menu" => "global_menu_settings",
       "sort" => 1800,
       "text" => GetMessage("MAIN_MENU_TOOLS"),
       "title" => GetMessage("MAIN_MENU_TOOLS_TITLE"),
       "url" => "tools_index.php?lang=".LANGUAGE_ID,
       "icon" => "util_menu_icon",
       "page_icon" => "util_page_icon",
       "items_id" => "menu_util",
       "items" => array(
          array(
             "text" => GetMessage("MAIN_MENU_SITE_CHECKER"),
             "url" => "site_checker.php?lang=".LANGUAGE_ID,
             "more_url" => array(),
             "title" => GetMessage("MAIN_MENU_SITE_CHECKER_ALT"),
          ),
          array(
             "text" => GetMessage("MAIN_MENU_FILE_CHECKER"),
             "url" => "file_checker.php?lang=".LANGUAGE_ID,
             "more_url" => array(),
             "title" => GetMessage("MAIN_MENU_FILE_CHECKER_ALT"),
          ),
          array(
             "text" => GetMessage("MAIN_MENU_PHPINFO"),
             "url" => "phpinfo.php?test_var1=AAA&test_var2=BBB",
             "more_url" => array("phpinfo.php"),
             "title" => GetMessage("MAIN_MENU_PHPINFO_ALT"),
          ),
          array(
             "text" => GetMessage("MAIN_MENU_SQL"),
             "url" => "sql.php?lang=".LANGUAGE_ID."&del_query=Y",
             "more_url" => array("sql.php"),
             "title" => GetMessage("MAIN_MENU_SQL_ALT"),
          ),
          array(
             "text" => GetMessage("MAIN_MENU_PHP"),
             "url" => "php_command_line.php?lang=".LANGUAGE_ID."",
             "more_url" => array("php_command_line.php"),
             "title" => GetMessage("MAIN_MENU_PHP_ALT"),
          ),
          array(
             "text" => GetMessage("MAIN_MENU_AGENT"),
             "url" => "agent_list.php?lang=".LANGUAGE_ID,
             "more_url" => array("agent_list.php", "agent_edit.php"),
             "title" => GetMessage("MAIN_MENU_AGENT_ALT"),
          ),
          array(
             "text" => GetMessage("MAIN_MENU_DUMP"),
             "url" => "dump.php?lang=".LANGUAGE_ID,
             "more_url" => array("dump.php", "restore_export.php"),
             "title" => GetMessage("MAIN_MENU_DUMP_ALT"),
          ),
    (strtoupper($DBType) == "MYSQL"?
       Array(
          "text" => GetMessage("MAIN_MENU_REPAIR_DB"),
          "url" => "repair_db.php?lang=".LANGUAGE_ID,
          "more_url" => array(),
          "title" => GetMessage("MAIN_MENU_REPAIR_DB_ALT"),
       )
    :null
    ),
    ($USER->CanDoOperation('view_event_log')?
       Array(
          "text" => GetMessage("MAIN_MENU_EVENT_LOG"),
          "url" => "event_log.php?lang=".LANGUAGE_ID,
          "more_url" => array(),
          "title" => GetMessage("MAIN_MENU_EVENT_LOG_ALT"),
       )
    :null
          ),
       ),
    );

    Пункты административного меню можно добавлять с помощью события OnBuildGlobalMenu.

    Если вы пишите свой модуль можете использовать /bitrix/modules/ID_модуля/admin/menu.php для добавления пунктов административного меню.

    Старый способ формирования меню

    Взаимодействие модулей

    Модули могут взаимодействовать между собой двумя способами: явно (прямым вызовом) и скрыто (через систему событий).

    Явное взаимодействие

    Явное взаимодействие с помощью API. Подразумевает подключение модуля с помощью метода CModule::IncludeModule(указав в качестве параметра id модуля) с последующим непосредственным вызовом метода класса или функции модуля.

    Пример явного взаимодействия:

    <?
    // подключаем модуль mymodule
    if (CModule::IncludeModule("mymodule"))
    {
        // выполним его метод
        CMyModuleClass::DoIt();
    }
    ?>

    Взаимодействие через события

    Событие — это какое-либо произвольное действие, в момент выполнения которого (до или после) собираются все обработчики этого события и выполняются по одному.

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

    Схема работы с событиями. Модуль, инициирующий событие, в том месте кода, где это событие происходит, должен выполнить следующее:

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

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

    • Зарегистрировать в момент инсталляции свой обработчик с помощью функции RegisterModuleDependences.
    • Соответственно необходимо иметь эту функцию-обработчик и убедиться, что скрипт, в котором эта функция находится, подключается в файле /bitrix/modules/ID модуля/include.php.

    Пример взаимодействия

    Ярким примером взаимодействия такого типа является взаимодействие модулей системы с модулем Поиска. Этот модуль не имеет никакой информации о данных других модулей, особенностях их хранения и обработки. Он только предоставляет интерфейс для индексации данных. Любой модуль системы, который должен индексироваться, при инсталляции регистрирует обработчик на событие OnReindex. Каждый такой обработчик в свою очередь возвращает данные для индексации, которые модуль Поиска использует для наполнения своей базы.

    Коды примеров взаимодействия через события:

    <?
    // регистрация обработчика:
    // когда в модуле init_module возникнет событие OnSomeEvent
    // будет вызван метод CMyModuleClass::Handler модуля handler_module
    
    RegisterModuleDependences(
        "init_module", "OnSomeEvent",
        "handler_module", "CMyModuleClass", "Handler"
        );
    ?>
    <?
    // произвольная функция модуля init_module
    // в которой генерируется событие
    
    function MyFunction()
    {
        // здесь располагается произвольный код 
        // представляющий из себя событие
    
        // далее следует сбор зарегистрированных обработчиков
        $rsHandlers = GetModuleEvents("init_module", "OnSomeEvent");
        while($arHandler = $rsHandlers->Fetch())
        {
            // и их выполнение по одному
            if(!ExecuteModuleEvent($arHandler, $param1, $param2))
            {
                // если обработчик вернет false, 
                // то например, вернем надпись "I can't do it..."
                return "I can't do it...";
            }
        }
        return "I have done it!";
    }
    ?>
    <?
    // обработчик
    
    class CMyModuleClass
    {
        function Handler($param1, $param2)
        {
            if($param1=="delete all")
                return false;
            return true;
        }
    }
    ?>

    Установка и удаление

    Установка модуля

    Инсталляция модуля осуществляется в административном интерфейсе на странице Настройки > Настройки продукта > Модули нажатием кнопки Установить.

    При этом будет вызван метод DoInstall класса с именем, представляющим собой ID модуля, где точка заменяется на нижнее подчеркивание. Этот класс должен быть описан в файле /bitrix/modules/ID модуля/install/index.php.

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

    • Регистрация модуля, которая осуществляется с помощью функции RegisterModule.
    • Если модуль обладает административными скриптами, то для их вызова в каталог /bitrix/admin/ должны быть скопированы вызывающие скрипты.
    • Все изображения, используемые модулем, должны быть скопированы в каталог /bitrix/images/ID модуля/.

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

    Удаление модуля

    Деинсталляция модуля осуществляется нажатием кнопки Удалить. При этом будет вызван метод DoUninstall класса с именем совпадающим с ID модуля. Этот класс должен быть описан в файле /bitrix/modules/ID модуля/install/index.php.

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

    • Удаление регистрационной записи модуля, которая осуществляется с помощью функции UnRegisterModule
    • Если модуль обладает административными скриптами, то вызывающие их скрипты должны быть удалены из каталога /bitrix/admin/.
    • Все изображения, используемые модулем, должны быть удалены из каталога /bitrix/images/ID модуля/.

    Примечание: Главный модуль и модуль Управление структурой удалить нельзя.

    Кастомизация и создание модулей

    Цитатник веб-разработчиков.

    Войтенко Андрей: Скажу по своему опыту (3 года): проблем, которые нельзя было решить без правки ядра, не встречал. Понятно, что не считая багов самого Битрикса. Они решались по принципу: ТП и ручная правка.

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

    Внимание! Кастомизация модуля — это модификация ядра системы со всеми вытекающими отсюда последствиями: рисками получить неработоспособную систему после обновления, потерей права на ТП.

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

    Создание собственных модулей описано в курсе Маркетплейс Bitrix Framework

    Список ссылок по теме:

    • Конструктор модулей для Marketplace
    • Собираем обновление собственного модуля
    • Запись вебинара по Marketplace

    Пример изменения работы модуля

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

    Пример модификации модуля для версии 11.5. В более поздних версиях этот функционал работает по другому.

    Решим задачу выгрузки контактов из корпоративного портала в Outlook 2003. Напомним, что в стандартной поставке «1С-Битрикс: Корпоративный портал» рассчитан на взаимодействие с Outlook 2007.

    За формирование и подготовку данных для Outlook отвечает файл /bitrix/modules/intranet/classes/general/ws_contacts.php.

    Есть две стандартные проблемы:

    • В Outlook 2003 не выгружаются аватарки — вследствие чего сразу же возникает ошибка.
    • Не для всех пользователей выгружается компания, к которой они принадлежат (поле в Outlook — Организация).

    Решаем первую проблему с помощью функции __getRow($arRes, $listName, &$last_change). Комментируем строки установки атрибута картинки:

    /*if ($this->bGetImages && $arRes['PERSONAL_PHOTO'] > 0)
          {
             $arImage = CIntranetUtils::InitImage($arRes['PERSONAL_PHOTO'], 100, 100);
    
             $obRow->setAttribute('ows_Attachments', ';#'.($APPLICATION->IsHTTPS() ? 'https://' : 'http://')
                                  .$_SERVER['HTTP_HOST'].$arImage['CACHE']['src'].';#'.CIntranetUtils::makeGUID(md5($arRes['PERSONAL_PHOTO'])).',1;#');
             $obRow->setAttribute('ows_MetaInfo_AttachProps', '<File Photo="-1">'.$arImage['FILE']['FILE_NAME'].'</File>');
          }
          else
          {*/
             $obRow->setAttribute('ows_Attachments', 0);
          //}

    Решаем вторую проблему с помощью функции GetListItemChangesSinceToken($listName, $viewFields = '', $query = '', $rowLimit = 0, $changeToken = ''). В цикле while ($arUser = $obUsers->NavNext()) комментируем все строки, начинающиеся с $arUser['WORK_COMPANY']
    (т.е. где просто меняется значение этого атрибута).

    Примечание: В функции GetList происходит формирование схемы аттрибутов. Если перед строчкой: return array('GetListResult' => $data); сделать вывод массива $data, то можно ознакомиться со схемой выгрузки.

    BigData (сервис персонализации)

    В данной главе рассматривается механизм сбора данных для сервиса 1С-Битрикс BigData, поясняется логика его работы и приводится описание API запросов.

    Примечание: информация по подключению сервиса на сайте представлена в главе Персонализация учебного курса Администратор. Бизнес.

    Сбор данных

    Сервис персонализации использует 2 механизма сбора данных:

    1. Клиентский — у посетителей сайта при просмотре товаров собирается информация из браузеров и пересылается в BigData кластер. При использовании стандартного компонента catalog.element генерится следующий java-скрипт:
      <script type="text/javascript">var _ba = _ba || []; _ba.push(["aid", "5b2ab8a860eebf174046f4d0b2ce52a6"]); 
      _ba.push(["host", "mysite.ru"]); _ba.push(["ad[ct][value]", 
      "eyJ1c2VyX2lkIjoiMSIsInByb2R1Y3RfaWQiOiI4IiwiaWJsb2NrX2lkIjoyLCJwcm9kdWN0X3RpdGxlIjoiXHUwNDFmXHUwNDNiXHUwNDM
      wXHUwNDQyXHUwNDRjXHUwNDM1IFx1MDQxMlx1MDQzNVx1MDQ0MVx1MDQzNVx1MDQzZFx1MDQzZFx1MDQ0Zlx1MDQ0ZiBcdTA0MWJcdTA0MzV
      cdTA0MzNcdTA0M2FcdTA0M2VcdTA0NDFcdTA0NDJcdTA0NGMiLCJjYXRlZ29yeV9pZCI6IjciLCJjYXRlZ29yeSI6eyI3IjoiXHUwNDFmXHU
      wNDNiXHUwNDMwXHUwNDQyXHUwNDRjXHUwNDRmIn0sInJlY29tbWVuZGF0aW9uIjowLCJwcmljZSI6IiIsImN1cnJlbmN5IjoiIn0="]);
      _ba.push(["ad[ct][v]", "1"]);(function() {var ba = document.createElement("script"); 
      ba.type = "text/javascript"; ba.async = true;ba.src = (document.location.protocol == 
      "https:" ? "https://" : "http://") + "bitrix.info/ba.js";var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(ba, s);})();</script>
      

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

      $productData = array(
          'product_id' => ... // идентификатор продукта (не товарного предложения, а именно продукта)
          'product_title' => ... // название продукта
          'iblock_id' => ... // идентификатор инфоблока продукта
          'category_id' => ... // идентификатор категории продукта
          'category' => ... // перечислены все названия ветки категорий продукта
          'price' => ... // цена
          'currency' => ... // валюта
      ));
      
      $counterData = array(
          'item' => base64_encode(json_encode($productData)),
          'user_id' => ... // идентификатор пользователя на сайте
          'recommendation' => ... // уникальный идентификатор выданной рекомендации, если просмотр по рекомендации. Для собственного компонента идентификатор получается в момент получения рекомендации из облака.
          'v' => '2'
      );
      
      BitrixMainAnalyticsCounter::sendData('ct', $counterData);
    2. Cерверный — с помощью внутренних запросов сервера фиксируются следующие операции (события): добавление товара в корзину, оформление заказа, его оплата. Данные сохраняются в таблицу b_counter_data и отправляются агентом в сервис рекомендаций.

    Важно! Пересылаемая информация не содержит персональных данных клиентов.

    Сбор данных для рекомендаций можно отключить в [dw]настройках главного модуля[/dw][di][/di] (Настройки > Настройки продукта > Настройки модулей > Главный модуль).

    Логика работы сервиса

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

    • кука пользователя;
    • хеш лицензии;
    • домен;
    • идентификатор товара;
    • название товара;
    • категории товара;
    • идентификатор рекомендации.

    Работа с данными ведется в несколько этапов:

    • На первом этапе все события поступают в единую точку входа bitrix.info, куда в секунду поступает порядка тысячи запросов. (Точка входа реализована на NGINX с модулем Lua.) Входящие данные накапливаются в буфере Kinesis, который хранит данные одни сутки. Затем данные достаются из буфера и обрабатываются большим количеством php-воркеров. Воркеры фильтруют данные и сохраняют их в таблицах хранилища Amazon DinamoDB.

    • На втором этапе данные достаются из хранилища Amazon DynamoDB и при этом, чтобы каждый раз не сканировать базу, используются простые файлы данных, хранящиеся в Amazon S3. Затем выполняется обработка данных: данные обсчитываются с помощью batch-процессинга (используется инструментарий Apache Spark) и с помощью онлайн алгоритмов (Apache Tomcat, Apache Mahout, алгоритмов похожести пользователей, похожести товаров и семантического анализа на основе контента). В результате выбранная и обсчитанная информация в виде рекомендаций выдается из analytics.bitrix.info.

    Метрики качества

    Дополнительно используются следующие метрики качества сервиса рекомендаций:

    • Отношение просмотров по рекомендации к просмотрам, т.е. определяется эффективность размещения компонента.
    • Отношение заказов по рекомендации к просмотрам по рекомендации – этот показатель оценивает качество алгоритма, насколько релевантной оказалась рекомендация покупателю.
    • Отношение заказов по рекомендации ко всем заказам.

    API запросов

    Персональная рекомендация

    Для конкретной [ds]куки[/ds][di]
    Cookie — это текстовая строка информации, которую веб-сервер передает в браузер посетителя сайта и которая

    сохраняется в файле на устройстве посетителя сайта

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

    Подробнее…[/di] дается персональная рекомендация. Используется комбинированный алгоритм определения похожести товаров.

    Запрос:

    https://analytics.bitrix.info/crecoms/v1_0/recoms.php?op=recommend&uid=#кука#&count=3&aid=#хэш_лицензии#

    Параметры:

    • op=recommend;
    • uid – cookie пользователя (нужна исключительно для привязки выданной рекомендации к пользователю, в расчетах не используется );
    • aid – хэш от лицензии сайта;
    • сount – число элементов (размер выдачи).

    Ответ:

    {"id":"24aace52dc0284950bcff7b7f1b7a7f0de66aca9","items":["1651384","1652041","1651556"]}
    

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

    Товары, похожие на данный

    Это не персональная рекомендация. Используется комбинированный алгоритм определения похожести товаров. Товар похож на данный по коллаборативной функции, т.е. насколько этот товар также покупают (в цепочке покупки этот товар встречается также, как и данный, поэтому они похожи).

    Запрос:

    https://analytics.bitrix.info/crecoms/v1_0/recoms.php?op=simitems&aid=#хэш_лицензии#&eid=#id_товара#&count=3&type=combined&uid=#кука#
    

    Параметры:

    • op=simitems;
    • uid – cookie пользователя (нужна исключительно для привязки выданной рекомендации к пользователю, в расчетах не используется );
    • aid – хэш от лицензии сайта;
    • eid – идентификатор товара;
    • type — view|order|combined, если не задать, то по умолчанию выбираются максимально похожие по комбинированному алгоритму (combined);
    • сount – число элементов (размер выдачи).

    Топ товаров на сайте

    Это не персональная рекомендация. Топ товаров на сайте — это топ по продажам, топ по просмотрам и комбинированный тип.

    Запрос:

    https://analytics.bitrix.info/crecoms/v1_0/recoms.php?op=sim_domain_items&aid=#хэш_лицензии#&domain=#домен#&count=50&type=combined&uid=#кука#

    Параметры:

    • op=sim_domain_items;
    • uid – cookie пользователя (нужна исключительно для привязки выданной рекомендации к пользователю, в расчетах не используется );
    • aid – хэш от лицензии сайта;
    • domain – домен сайта;
    • type — view|order|combined, если не задать, то по умолчанию выбираются максимально похожие по комбинированному алгоритму (combined);
    • сount – число элементов (размер выдачи).

    Веб-сервисы

    Веб-служба, веб-сервис (англ. web service) — программная система, идентифицируемая строкой URI, чьи публичные интерфейсы и привязки определены и описаны языком XML. Описание этой программной системы может быть найдено другими программными системами, которые могут взаимодействовать с ней согласно этому описанию посредством сообщений, основанных на XML, и передаваемых с помощью интернет-протоколов.

    Достоинства веб-сервисов:

    Веб-службы обеспечивают взаимодействие программных систем независимо от платформы. Веб-службы основаны на базе открытых стандартов и протоколов. Благодаря использованию XML достигается простота разработки и отладки веб-служб. Использование интернет-протокола HTTP обеспечивает взаимодействие программных систем через межсетевой экран.

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

    Пример создания windows-приложения для добавления новостей

    Описание

    Рассмотрим, как на его основе модуля Веб-сервисы можно сделать простейшее Windows-приложение для добавления новостей при помощи Visual Studio 2012 и .NET Framework 4.5. Для этого можно скачать одну из бесплатных версий Express 2012 for Windows Desktop.

    Создание информационного блока

    Прежде всего, чтобы иметь возможность добавлять новости, необходимо определить информационный блок, в котором они будут храниться. Для примера будем использовать уже имеющийся у нас на сайте инфоблок с ID=3.

    Создание компонента

    Создание компонента веб-сервиса для добавления новостей

    При установке модуля Веб-сервисы создается новый компонент bitrix:webservice.server. Он предназначен для простого создания, тестирования и вывода в читабельном виде информации о ваших веб-сервисах. Для нашего веб-сервиса он будет являться «сервером».

    Сначала создадим свой компонент. Для этого создадим новую папку в /bitrix/components/demo, назовем ее webservice.addnews. Как и любой другой компонент 2.0, компонент веб-сервиса должен содержать стандартные файлы описаний:

    • Файл .description.php:
      <?
      if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
      
      $arComponentDescription = array(
         "NAME" => "Веб-сервис добавления новостей",
         "DESCRIPTION" => "Веб-сервис добавления новостей",
         "CACHE_PATH" => "Y",
         "PATH" => array(
            "ID" => "service",
            "CHILD" => array(
               "ID" => "webservice",
               "NAME" => "Веб-сервис добавления новостей."
            )
         ),
      );
      ?>
      
    • Файл .parameters.php:
      <?
      if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
      
      $arComponentParameters = array(
         "GROUPS" => array(),
         "PARAMETERS" => array(),
         );
      ?>
      
    • И исполняемый файл component.php. Создадим первоначально его в следующем виде:
      <?
      if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
      
      if(!CModule::IncludeModule("webservice") || !CModule::IncludeModule("iblock"))
         return;
         
      // наш новый класс наследуется от базового IWebService
      class CAddNewsWS extends IWebService
      {
         // метод GetWebServiceDesc возвращает описание сервиса и его методов
         function GetWebServiceDesc() 
         {
            $wsdesc = new CWebServiceDesc();
            $wsdesc->wsname = "bitrix.webservice.addnews"; // название сервиса
            $wsdesc->wsclassname = "CAddNewsWS"; // название класса
            $wsdesc->wsdlauto = true;
            $wsdesc->wsendpoint = CWebService::GetDefaultEndpoint();
            $wsdesc->wstargetns = CWebService::GetDefaultTargetNS();
      
            $wsdesc->classTypes = array();
            $wsdesc->structTypes = Array();
            $wsdesc->classes = array();
      
            return $wsdesc;
         }
      }
      
      $arParams["WEBSERVICE_NAME"] = "bitrix.webservice.addnews";
      $arParams["WEBSERVICE_CLASS"] = "CAddNewsWS";
      $arParams["WEBSERVICE_MODULE"] = "";
      
      // передаем в компонент описание веб-сервиса
      $APPLICATION->IncludeComponent(
         "bitrix:webservice.server",
         "",
         $arParams
         );
      
      die();
      ?>
      

    Это минимальное содержимое для определения веб-сервиса. Как видно он содержит наследованный от IWebService класс CAddNewsWS, переопределенный метод GetWebServiceDesc, возвращающий описание сервиса в формате CWebServiceDesc и вызов компонента bitrix:webservice.server, которому в качестве параметров передается описание нашего зарождающегося веб-сервиса.

    Для проверки работоспособности создадим новую страницу, например, ws_addnews.php, разместим на ней новый компонент и сохраним. В публичном разделе результат будет следующим

    Как мы видим, появилось описание нашего веб-сервиса. Но у него нет ни одного метода. Для создания метода нам потребуется добавить в класс новый метод, который принимает на вход в качестве параметров некоторые поля новости, а в качестве результата возвращает ID добавленной новости или ошибку:

    function AddNews($NAME, $DATE, $PREVIEW_TEXT, $DETAIL_TEXT, $KEYWORDS, $SOURCE)
    {
       $iblock_permission = CIBlock::GetPermission(3);   
       if ($iblock_permission < "W")
       {
          $GLOBALS["USER"]->RequiredHTTPAuthBasic();
          return new CSOAPFault('Server Error', 'Unable to authorize user.');
       }
       $arFields = Array(
             "IBLOCK_ID"=>3, // инфоблок "Новости магазина"
             "NAME"=>$NAME,
             "DATE_ACTIVE_FROM"=>$DATE,
             "PREVIEW_TEXT"=>$PREVIEW_TEXT,
             "DETAIL_TEXT"=>$DETAIL_TEXT,
             "PROPERTY_VALUES" => Array(
                "KEYWORDS"=>$KEYWORDS,
                "SOURCE"=>$SOURCE,
                )
          );
       $ib_element = new CIBlockElement();
       $result = $ib_element->Add($arFields);
       if($result>0)
          return Array("id"=>$result);
    
       return new CSOAPFault( 'Server Error', 'Error: '.$ib_element->LAST_ERROR );
    }
    

    Зарегистрируем новый метод в массиве $wsdesc->classes:

    $wsdesc->classes = array(
       "CAddNewsWS"=> array(
          "AddNews" => array(
             "type"      => "public",
             "input"      => array(
                "NAME" => array("varType" => "string"),
                "DATE" => array("varType" => "string"),
                "PREVIEW_TEXT" => array("varType" => "string"),
                "DETAIL_TEXT" => array("varType" => "string"),
                "KEYWORDS" => array("varType" => "string"),
                "SOURCE" => array("varType" => "string"),
                ),
             "output"   => array(
                "id" => array("varType" => "integer")
             ),
             "httpauth" => "Y"
          ),
       )
    );
    

    В массиве содержится название класса и названия методов, с описанием входных и выходных параметров.

    Вот и все, теперь если обновить страницу, на которой расположен компонент, то появится новый метод и также можно протестировать его работу непосредственно из браузера:

    Создание приложения

    Теперь можно приступать к завершающему этапу — созданию в Visual Studio простого Windows-приложения. В нашем примере мы будем делать приложение в Express 2012 for Windows Desktop на C#.

    • Создаем новый проект в Visual Studio, в качестве шаблона выбираем Приложение Windows Forms:

    • На вкладке Обозреватель решений добавляем ссылку на службу:

    • В открывшейся форме указываем ссылку на страницу с компонентом в следующем виде: http://_ваш_домен_/ws_addnews.php?wsdl. Нажимаем кнопку Перейти.

      В поле Службы отобразится наш веб-сервис bitrix.webservice.addnews с интерфейсом CAddNewsWSInterface, для которого доступна операция AddNews:

      Переименовываем пространство имен в myws.addnews и нажимаем кнопку OK.

    • В файле конфигурации приложения App.config необходимо добавить настройки basicHttpBinding:
      <binding name="CAddNewsWSBinding">
                      <security mode="TransportCredentialOnly">
                        <transport clientCredentialType="Basic" proxyCredentialType="None"
                            realm="AXIS" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                     </security>
      </binding>
      
    • В настройках ссылки на службу необходимо отметить использование System.Web.Services:

    • Далее приступаем к созданию непосредственно самого окна ввода новости, для этого разместим на форме необходимые поля и кнопку Отправить:

    • Двойным кликом по кнопке открываем обработчик события нажатия на кнопку и размещаем код сохраняющий новость на сайте. Можно использовать обычный код:
      private void button1_Click(object sender, EventArgs e)
              {
                  CAddNewsWSInterfaceClient news = new CAddNewsWSInterfaceClient();
                  news.ClientCredentials.UserName.UserName = "admin";
                  news.ClientCredentials.UserName.Password = "password";
                  try
                  {
                      long result = news.AddNews(NAME.Text, NEWS_DATE.Text, PREVIEW_TEXT.Text, DETAIL_TEXT.Text, KEYWORDS.Text, SOURCE.Text);
                      MessageBox.Show("Новость №" + result + " успешно добавлена");
                  }
                  catch (System.Web.Services.Protocols.SoapHeaderException exception)
                  {
                      MessageBox.Show("Ошибка добавления новости [" + exception.Message + "]");
                  }
             }
      

      или асинхронный:

      private async void button1_Click(object sender, EventArgs e)
                             {
                                 CAddNewsWSInterfaceClient news = new CAddNewsWSInterfaceClient();
                                 news.ClientCredentials.UserName.UserName = "admin";
                                 news.ClientCredentials.UserName.Password = "password";
                                 try
                                 {
                                    AddNewsResponse result = await news.AddNewsAsync(NAME.Text, NEWS_DATE.Text, PREVIEW_TEXT.Text, DETAIL_TEXT.Text, KEYWORDS.Text, SOURCE.Text);
                                    MessageBox.Show("Новость №" + result.Body.id + " успешно добавлена");
                                 }
                                 catch (System.Web.Services.Protocols.SoapHeaderException exception)
                                 {
                                     MessageBox.Show("Ошибка добавления новости [" + exception.Message + "]");
                                 }
                     }
      

      Важно! в описании используемых пространств имен необходимо добавить:

      using System.Net; 
      using WindowsFormsApplication1.myws.addnews;
      
    • Компилируем приложение, запускаем, заполняем поля, нажимаем кнопку Отправить:

    В данном уроке показан очень простой пример. Конечно, данный веб-сервис можно дорабатывать, например, чтобы в параметрах компонента можно было управлять в какой инфоблок, какую информацию добавлять, поддержку произвольных свойств и т.п. Также можно доработать и Windows-приложение, например, чтобы оно выгружало новости на сайт «пачками», содержало больше полей и настроек.

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

    Примечание: чтобы добавлять новости с картинкой, придется на стороне Windows-приложения читать файл, конвертировать его при помощи System.Convert.ToBase64String в BASE64, а на стороне компонента конвертировать назад функцией base64_decode, сохранять его во временный файл и передавать на вход методу CIBlockElement:Add(), как одно из полей. Помимо этого, нам необходимо знать на сервере как минимум расширение (тип) файла, поэтому вместе с содержимым будем передавать оригинальное имя файла.
    Таким образом вот так будет выглядеть наш класс веб-сервиса в компоненте:

    class CAddNewsWS extends IWebService
    {
        function AddNews($NAME, $DATE, $PREVIEW_TEXT, $DETAIL_TEXT, $KEYWORDS, $SOURCE, $IMAGE_NAME, $IMAGE_CONTENT)
        {
            $iblock_permission = CIBlock::GetPermission(3);
            if ($iblock_permission < "W")
            {
                $GLOBALS["USER"]->RequiredHTTPAuthBasic();
                return new CSOAPFault('Server Error', 'Unable to authorize user.');
            }
            
            $arFields = Array(
                    "IBLOCK_ID"=>3, // инфоблок "Новости магазина"
                    "NAME"=>$NAME,
                    "DATE_ACTIVE_FROM"=>$DATE,
                    "PREVIEW_TEXT"=>$PREVIEW_TEXT,
                    "DETAIL_TEXT"=>$DETAIL_TEXT,
                    "PROPERTY_VALUES" => Array(
                        "KEYWORDS"=>$KEYWORDS,
                        "SOURCE"=>$SOURCE,
                        )
                );
            if(strlen($IMAGE_NAME)>0 && strlen($IMAGE_CONTENT)>0)
            {
                $IMAGE_CONTENT = base64_decode($IMAGE_CONTENT);
                if(strlen($IMAGE_CONTENT)>0)
                {
                    $tmp_name = $_SERVER['DOCUMENT_ROOT'].'/bitrix/tmp/'.md5(uniqid(rand(), true)).".tmp";
                    CheckDirPath($tmp_name);
                    $f = fopen($tmp_name, "wb");
                    fwrite($f, $IMAGE_CONTENT);
                    fclose($f);
                    $arFields["DETAIL_PICTURE"] = Array("name"=>$IMAGE_NAME, "tmp_name"=>$tmp_name, "size"=>strlen($IMAGE_CONTENT), "type"=>"image/jpeg");
                }
            }
            $ib_element = new CIBlockElement();
            $result = $ib_element->Add($arFields);
            if($tmp_name)
                @unlink($tmp_name);
            if($result>0)
                return Array("id"=>$result);
    
            return new CSOAPFault( 'Server Error', 'Error: '.$ib_element->LAST_ERROR );
        }
    
        // метод GetWebServiceDesc возвращает описание сервиса и его методов
        function GetWebServiceDesc()
        {
            $wsdesc = new CWebServiceDesc();
            $wsdesc->wsname = "bitrix.webservice.addnews";
            $wsdesc->wsclassname = "CAddNewsWS";
            $wsdesc->wsdlauto = true;
            $wsdesc->wsendpoint = CWebService::GetDefaultEndpoint();
            $wsdesc->wstargetns = CWebService::GetDefaultTargetNS();
    
            $wsdesc->classTypes = array();
            $wsdesc->structTypes = Array();
    
            $wsdesc->classes = array(
                "CAddNewsWS"=> array(
                    "AddNews" => array(
                        "type"        => "public",
                        "input"        => array(
                            "NAME" => array("varType" => "string"),
                            "DATE" => array("varType" => "string"),
                            "PREVIEW_TEXT" => array("varType" => "string"),
                            "DETAIL_TEXT" => array("varType" => "string"),
                            "KEYWORDS" => array("varType" => "string"),
                            "SOURCE" => array("varType" => "string"),
                            "IMAGE_NAME" => array("varType" => "string"),
                            "IMAGE_CONTENT" => array("varType" => "string"),
                            ),
                        "output"    => array(
                            "id" => array("varType" => "integer")
                        ),
                        "httpauth" => "Y"
                    ),
                )
            );
    
            return $wsdesc;
        }
    }
    

    А вот так обработчик нажатия на кнопку в Windows-приложении (IMAGE — это новый контрол на форме, в нем должен быть путь к файлу на диске):

    private void button1_Click(object sender, EventArgs e)
    {
        Byte[] binaryData;
        string base64String = "";
        if(IMAGE.Text.Length>0)
        {
            try
            {
                System.IO.FileStream imageFile = new System.IO.FileStream(IMAGE.Text, System.IO.FileMode.Open,System.IO.FileAccess.Read);
                binaryData = new Byte[imageFile.Length];
                imageFile.Read(binaryData, 0, (int)imageFile.Length);
                imageFile.Close();
                base64String = System.Convert.ToBase64String(binaryData, 0, binaryData.Length);
            }
            catch (System.Exception exp)
            {
                MessageBox.Show("Ошибка чтения картинки [" + exp.Message + "]");
                return;
            }
        }
    
                CAddNewsWSInterfaceClient news = new CAddNewsWSInterfaceClient();
                news.ClientCredentials.UserName.UserName = "admin";
                news.ClientCredentials.UserName.Password = "password";
                try
                {
                    long result = news.AddNews(NAME.Text, NEWS_DATE.Text, PREVIEW_TEXT.Text, DETAIL_TEXT.Text, KEYWORDS.Text, SOURCE.Text, IMAGE_FILE.Text, base64String);
                    MessageBox.Show("Новость №" + result + " успешно добавлена");
                }
                catch (System.Web.Services.Protocols.SoapHeaderException exception)
                {
                    MessageBox.Show("Ошибка добавления новости [" + exception.Message + "]");
                }
    }
    

    Vue.js и Bitrix Framework

      Что такое Vue.js?

    Vue (произносится /vjuː/, примерно как view) — это среда JavaScript для создания пользовательских интерфейсов. Он построен на основе стандартных HTML, CSS и JavaScript и предоставляет декларативную и основанную на компонентах модель программирования, которая помогает эффективно разрабатывать пользовательские интерфейсы, будь то простые или сложные.

    Vue.JS базируется на идее [ds]виртуального DOM.[/ds][di]
    DOM (аббревиатура от Document Object Model) — способ представления

    структурного документа с помощью объектов. Это кроссплатформенное и

    языко-независимое соглашение для представления и взаимодействия с

    данными в HTML, XML и т.д.

    Главная проблема DOM — он никогда не был рассчитан для создания динамического

    пользовательского интерфейса (UI). Мы можем работать с ним, используя

    JavaScript и библиотеки наподобие jQuery, но их использование не решает

    проблем с производительностью.

    Вместо того, чтобы взаимодействовать с DOM напрямую, мы работаем с его

    легковесной копией. Мы можем вносить изменения в копию, исходя из

    наших потребностей, а после этого применять изменения к реальному DOM.

    Подробнее…

    Управление DOM-структурой в документации Битрикс.
    [/di]
    Это исключает прямое взаимодействие с узлами интерфейса. Первоначальная работа ведется с виртуальной копией (virtual DOM), а уже потом изменения применяются к реальным узлам интерфейса. Параллельно сравнивается реальное дерево DOM и его виртуальная копия, выявляется разница и переписывается только то, что претерпело изменения. Т.е. за короткий промежуток времени накапливаются все изменения, а потом эти изменения применяются к реальным узлам интерфейса (нодам) единым патчем. За счет этого скорость выполнения гораздо выше.

    Vue.js удобно использовать, когда какие-либо данные используются в нескольких компонентах. В таком случае эти данные выносятся в отдельную библиотеку.

    Преимущества Vue:

    • Простой вход, не нужно настраивать окружение, отличная документация;
    • Сочетает в себе плюсы React и Angular;
    • Фреймворк прошел этап становления;
    • Популярен среди разработчиков.

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

    Библиотека Vue входит в состав продукта «1С-Битрикс: Управление сайтом», что дает возможность использования и кастомизации компонентов Vue, встроенных в ядро BitrixFramework. К тому же, решена проблема версионности библиотеки Vue – в рамках текущего окружения BitrixFramework всегда единая версия.

    Примечание: Данное решение не подойдет для проектов, использующих SSR (Server side rendering) и серверную компиляцию (например, для однофайловых компонентов).

    Подробнее с фреймворком (в том числе и с примерами его использования) вы можете ознакомиться [ds]на официальном сайте.[/ds][di]
    Если вы хотите узнать больше о Vue перед тем как начать, мы создали видео с рассказом об основных принципах работы на примере проекта.

    Если вы — опытный фронтенд-разработчик, и хотите узнать, чем Vue отличается от остальных библиотек или фреймворков, обратите внимание на сравнение с другими фреймворками.

    Подробнее…[/di]

      Почему фреймворк включен в продукт?

    Преимущества и особенности использования обёртки Vue от Битрикса в следующем:

    Мы включили фреймворк в поставку продукта и сделали BitrixVue для простого взаимодействия между Vue и системой BitrixFramework.

    BitrixVue — это расширение для стандартной библиотеки Vue. Оно добавляет новые возможности и при этом не модифицирует стандартное поведение Vue.js.

    В чём же преимущества и особенности её использования?

    Первое: используя BitrixVue, Вы сможете интегрироваться в систему BitrixFramework и эффективно работать с другим компонентам и внутренними системами (такими как локализации и динамическая загрузка компонентов).

    Второе: решается вопрос версионирования, если два разработчика (например, два приложения из Маркетплейса) будут использовать Vue разных версий, то возникнут конфликты. В первую очередь BitrixVue сделан для того, чтобы этого избежать.

    Третье: разработчики-партнёры получают возможность кастомизировать и клонировать компоненты в модулях BitrixFramework (где это возможно) без изменения исходного кода продукта.

    И последнее: фреймворк Vue, который является основной частью BitrixVue, не экспортируется в глобальную область видимости. Тем самым она не может создать проблем сторонним приложениям. Они могут спокойно использовать обычное подключение Vue, в том числе использовать его в Webpack, не беспокоясь о конфликтах с библиотекой внутри BitrixFramework.

    Мобильное приложение BitrixMobile

    BitrixMobile 3.0 — технология создания кроссплатформенных мобильных приложений.

    Гибридные мобильные приложения имеют доступ к функциям смартфона:

    • GPS;
    • Информация об устройстве;
    • Акселерометр;
    • Файловая структура;  
    • Адресная книга.

    BitrixMobile прибавляет к этим возможностям широкий набор нативных интерфейсов, чтобы приложение работало быстрее и выглядело привычнее для пользователя. Разработка ведется полностью на HTML5/Javascript/CSS. Взаимодействие с функциями телефона и нативным интерфейсом осуществляется через функции специальной JS-библиотеки — BXMobileApp

    В рамках курса «BitrixMobile — создание кроссплатформенных мобильных приложений» подробно рассмотрены возможности библиотеки BXMobileApp, которая позволяет создавать сложные бизнес-приложения на BitrixMobile.

    Стандарты программирования для платформы 1С-Битрикс

    В этом руководстве описаны рекомендуемые техники при разработке под платформу 1С-Битрикс (далее просто Битрикс).

    Весь PHP код должен написан в соответствии со стандартом PSR-1. Стиль написания кода должен соответствовать стандарту PSR-2.

    Общие положения

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

    • РЕКОМЕНДУЕТСЯ использовать только <?php и <?= теги. Тег <? использовать НЕ РЕКОМЕНДУЕТСЯ.
    • РЕКОМЕНДУЕТСЯ использование кодировки UTF-8 без BOM.
    • Используйте Tab для каждого отступа. Никаких пробелов для блоков кода.

    Отладка

    Вывод на экран

    • Воспользуйтесь модулем Bitrix Debug, GitHub.

    Вывод в файл

    • api_help
    1. Определите константу LOG_FILENAME в файле /bitrix/php_interface/dbconn.php, задавая путь к лог-файлу за пределами DOCUMENT_ROOT.
    // определяем константу LOG_FILENAME, в которой зададим путь к лог-файлу
    define('LOG_FILENAME', $_SERVER['DOCUMENT_ROOT'] . '/_main.log');
    1. Отправьте сообщение в лог
    AddMessage2Log('Произвольный текст сообщения', 'module_id');

    Пример:

    AddMessage2Log( print_r($arResult, true) );

    SQL

    • Определите переменную DBDebugToFile для логирования всех SQL запросов.

    Базовые правила при разработке под Битрикс

    • при добавлении кода в файл init.php РЕКОМЕНДУЕТСЯ выносить логически сгруппированный код в отдельные файлы и подключать их внутри init.php
    // SomeClass
    require_once __DIR__ . '/include/some_class.php';
    • НЕ РЕКОМЕНДУЕТСЯ использовать цифровые значения в GetList, GetByID и схожих методах, которые принимают различные ID. РЕКОМЕНДУЕТСЯ создать файл со всеми необходимыми константами и вызывать их имена. У каждой константы ДОЛЖНО быть «говорящее» именование и комментарий.

    Не правильно:

    $comments = CIBlockElement::GetList(array(), array('IBLOCK_ID' => 12));

    Правильно:
    Создаем файл constants.php и указываем в нем:

    //ИБ с комментариями пользователей
    const COMMENTS_IBLOCK_ID = 12;

    Подключаем этот файл в init.php

    //Константы проекта
    require_once __DIR__ . '/includes/constants.php';

    Используем константу

    $comments = CIBlockElement::GetList(array(), array('IBLOCK_ID' => COMMENTS_IBLOCK_ID));
    • при выборках данных (например, GetList) ОБЯЗАТЕЛЬНО указывать поля (для GetList это 5-ый параметр arSelectFields), которые нужны для дальнейших манипуляций, кроме случаев, когда нужны все поля;
    $filter = array('IBLOCK_ID' => COMMENTS_IBLOCK_ID);
    $select = array('ID', 'NAME');
    $comments = CIBlockElement::GetList(array(), $filter, false, array(), $select);
    • при необходмости выбрать несколько элементов по ID, ОБЯЗАТЕЛЬНО использовать GetList вместо GetByID

    Не правильно:

    $element1 = CIBlockElement::GetByID(1);
    $element2 = CIBlockElement::GetByID(2);

    Правильно:

    $elements = CIBlockElement::GetList(array(), array(1, 2));
    • НЕ РЕКОМЕНДУЕТСЯ использовать прямые запросы к базе данных без крайней необходимости;
    • если к файлу не предусмотрен прямой доступ ОБЯЗАТЕЛЬНО в первой строке файла добавить
    if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
    	die();
    }

    Работа с компонентами

    • РЕКОМЕНДУЕТСЯ давать шаблонам компонентов осмысленные названия и в каждом проекте придерживаться общего стиля. Например, Раздел/страница_сайта.Название.Тип
      Примеры:
    • index.user.auth
    • profile.orders.list
    • cart.products.additional
    • НЕЛЬЗЯ модифицировать стандартные компоненты. Если возникает такая необходимость — создается копия компонента в своем пространстве имен в папке /bitrix/components/
    • РЕКОММЕНДУЕТСЯ все шаблоны компонентов сохранять в шаблоне .default в папке /bitrix/templates/.default/
    • НЕ РЕКОМЕНДУЕТСЯ делать любые манипуляции с данными в файле template.php. При необходимости правки логики стандартных компонентов, но недостаточной для того, что делать свой используются файлы result_modifier.php и component_epilog.php
    • РЕКОМЕНДУЕТСЯ использовать файлы style.css и script.js в шаблонах только если они переопределяют стандартное поведение схожих элементов. РЕКОМЕНДУЕТСЯ оставить комментарий об этой особенности

    Работа с шаблонами

    • РЕКОМЕНДУЕТСЯ использовать минимальное количество шаблонов
    • РЕКОМЕНДУЕТСЯ подключать header.php и footer.php из одного места для всех шаблонов, если это позволяет дизайн и верстка
    • РЕКОМЕНДУЕТСЯ общие картинки, скрипты и стили шаблонов сохранять в одном месте, например, в /bitrix/templates/.default/

    Работа с инфоблоками

    • ОБЯЗАТЕЛЬНО называть все свойства инфоблоков в верхнем регистре, осмысленно (используя связку сущность-наименование), разделяя слова нижним подчеркиванием. Например:
    • Имя пользователя: USER_NAME
    • Валюта заказа: ORDER_CURRENCY
    • Список заказов: ORDER_LIST
    • Использовать уникальные CODE для каждого отдельного инфо-блока. Рекомендуется поставить защиты:
    AddEventHandler('iblock', 'OnBeforeIBlockAdd', 'checkCode');
    AddEventHandler('iblock', 'OnBeforeIBlockUpdate', 'checkCode');
    function checkCode($params)
    {
    	if (isset($params['CODE']) && $params['CODE'] !== '') {
    		CModule::IncludeModule('iblock');
    
    		$iblocks = CIBlock::GetList(array(), array('CODE' => $params['CODE']));
    		if ($iblock = $iblocks->Fetch()) {
    			$APPLICATION->ThrowException(new CApplicationException('Инфоблок с таким CODE уже существует.'));
    
    			return false;
    		}
    	}
    }
    • Рекомендуется использовать библиотеку для простого и быстрого получения ID.

    AJAX-обработчики

    Довольно часто нужно создать пустую страницу — без вывода шапки и футера, но с возможностью обращаться к классам Битрикс. Например, такое нужно для работы с AJAX. Делается это очень просто. Создаем файл и пишем в нем.

    define('NO_KEEP_STATISTIC', true);
    define('NOT_CHECK_PERMISSIONS', true);
    require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';

    Теперь в этом файле можно легко обращаться к любым классам Битрикс. Не забывайте только модули подключить нужные:

    CModule::IncludeModule('название_модуля');

    Источник

    When specifying the PHP condition for choosing the site template, it should be considered that the use of section properties is allowed, while page properties are not allowed. This is because the section properties are stored in a separate file .section.php, and the page properties are specified usually in a page body, after the including the prologue service section.

    Note: In the public section, the current site template ID is stored in the constant SITE_TEMPLATE_ID.

    A site page is formed dynamically based on the page template used, data retrieved by the components, and the statistic information located on the page. The creation of site templates and the allocation of components in them are taken care of by site developers.

    Top and bottom parts of the design are formed based on the site design template. I.e. the information displayed in these areas is determined by the parameters of the site template.

    Thanks to the technology of deferred functions a part of visual elements displayed in the prolog may be set in the page body. These are such elements as:

    The key feature of this technology consists in its possibility to defer the performance of certain functions by performing them in the epilog, with the results of their performance substituted to the aforementioned code.

    A number of tasks cannot be resolved using the technology of deferred functions, for example when certain actions must be performed in the Prolog with values that in the previous example would be set in the page body (for example, page properties). In this case, the prolog must be divided into a service and a visual part, and the values must be set between them.

    The service part of the prolog has a particularity that it does not display any data (does not send the header to the browser).

    In the visual part of the prolog the file /bitrix/templates/site template ID/header.php is connected, where site template ID is the identifier of the current site template. This file stores top left part of the current site template.

    In the visual part of the epilog the file /bitrix/templates/site template ID/footer.php is connected, where site template ID is the identifier of the current site template. This file stores bottom right part of the current site template. In addition to this, a number of invisible IFRAMEs used by the technology of redirection of visitors is displayed.

    Tasks often occur when there is no need to connect visual parts of the prolog and epilog. In this case, the connection of service parts of the prolog and epilog will suffice.

    For the correct operating of the system, the service parts of the prolog and epilog must be connected.

    Page template is a PHP file wherein the contents are strictly consistent with the rules of forming the page structure. The templates may be used to create a new page.

    Each such catalog may contain the proper page template files and also the service file .content.php of which the principal task is to store descriptions and the procedure for sorting page templates. This information is stored in the $TEMPLATE array of which its structure is presented below:

    The following algorithm is used during the formation of the list of page templates:

    The section properties are stored in the file .section.php of the relevant catalog (site section). The page properties are set, as a rule, either in the page body or between the service and visual parts of the prolog.

    The section properties are automatically inherited by all subsections and pages of this section. If necessary, you can edit the properties of any separate page of the section by correcting its parameters as needed.

    CMain::ShowProperty — displays a page or section property using technology of deferred functions.

    CMain::GetDirPropertyList — returns array of section properties collected recursively up to the site root.

    Page and section properties are used to work with meta tags. The following functions are used to work with them:

    CMain::ShowMeta — displays a page property or a section property with an HTML tag frame using a deferred function feature.

    CMain::GetMeta — returns a page property or a section property with an HTML tag frame:

    Page parameters are intended to translate parameters into module functions in order to change their standard behavior. E.g., if it is necessary to deactivate the memorization of the last page in a session (when using a page by page navigation) or to change a standard data display mode in the functions of the Information Blocks module.

    Page parameters are accessible only within a page. They cannot be saved either in the database or in a session.

    Component and template parameters can be accessed from the component and template program modules as $arParams array. The result of work of the component program module is $arResult array submitted to the component template entry. The regular echo operator streams the resulting HTML code (and it gets incorporated into a proper place within the page).

    During the work on a component and template it is possible to use the functionality of Bitrix Framework modules which, in their turn, may access the product database.

    Page is a PHP file, consisting of a prolog, page body (main work area) and epilog:

    Page has a certain structure, properties and parameters. It can use its own templates.

    Both in the public and in the administrative parts the language is used first of all in order to select a specific language file.

    Language determines the time and date format and page encoding in the administrative part. In the public part, these parameters are determined by the site settings.

    Language file is a PHP script storing translations of language phrases to a specific language.

    This script contains $MESS arrays containing language phrase identifiers with values that are translations to a relevant language.

    Each language has its own set of language files stored in subcatalogs /lang/ of the system or module file structure.

    As a rule, language files are used in the administrative scripts of modules or in components and are connected accordingly using one of the following functions:

    For the sake of search convenience and the further modification of language phrases, the page parameter show_lang_files=Y may be used, which allows to quickly find and correct any language phrase using the module of Localization.

    In order to obtain the name of the month in 2 cases instead of its sequence number:

    The same procedure is applicable to the days of the week, names of the countries, etc.

    Here, the general information on processing techniques and principles fed into the system is provided. The course describes those of them that are most frequently used. For information about other processing techniques and principles, please refer to the documentation.

    Control Panel toolbar – an HTML code that may be displayed to an authorized user if such user has sufficient rights to perform the operations listed in the control panel. HTML code represents an area with buttons at the very top of the page. Each of these buttons is intended for a specific operation.

    The panel is connected using the function of CMain::ShowPanel. This function applies a processing technique of deferred functions that permits you to add buttons to the panel directly in the page body.

    A button may be added to the panel using the function of CMain::AddPanelButton. The same function may be used in the script /bitrix/php_interface/include/add_top_panel.php that will be automatically connected upon the invocation of the panel.

    The system automatically checks the agent’s availability, requiring to be launched and executes it (if required) at the end of each page upload, after passing content to a browser.

    In order for an agent to be executed at a set time, it must be registered in the system using the method of CAgent::AddAgent. An agent’s registration may be deleted using the function of CAgent::RemoveAgent.

    If the agent function belongs to a module, this module will be automatically connected before the execution of such an agent function. Namely, the file /bitrix/modules/module ID/include.php will be connected. In this case, you have to make sure that the agent function will be available after the connection of this file.

    If an agent function doesn’t belong to any module, it must be located in the file /bitrix/php_interface/init.php. This file is automatically connected in the prologue.

    The particularity of agent function creation is that the agent function must return a PHP code as function’s return value that will be used during the next execution of this function.

    List of agents, employed in the system is available at the Agents page (Settings > System Settings > Agents).

    Historically, agents are called «periodic» and «non-periodic» which also can be named as «recurring» and «non-recurring».

    The user interface within the admin panel for the agent settings likewise has remained the same:

    Type of agent depends on the software developer who wrote the agent’s code. This developer can create an agent that could repeat an infinite amount of times. Or only 2-3 times depending on set conditions. Example of agent function recurring infinitely:

    Type of agent is defined by the method for calculating time of the next agent start:

    In case when you need to dynamically add specific agents, use the agent API. Be advised, that it’s easier to add one or two agents manually.

    Go to the page Settings > Product Settings > Agents and click Add an Agent button on the context panel:

    You can dd the function to the file /bitrix/php_interface/init.php.

    To activate the agent, execute the following code in the admin section PHP console:

    Practical example: Update a currency rate at the site. It’s recommended to assign an agent to cron.

    Specified code is added to the file /bitrix/php_interface/init.php. Do not forget to add the agent AgentGetCurrencyRate(); at the page Settings > System Settings > Agents.

    Agent that checks the availability of iblock items without completed prices.

    Caching is a technology which enables to cache the results of rarely updated and resource consuming code areas (for example, those handling the database).

    In case of a large database, a performance problem may occur due to the following reasons:

    Multilevel caching is used precisely to relieve the most loaded places in terms of resources and time. Each caching technique may be used for each component separately by choosing an optimal option for a specific case.

    Note: Until the developer decides on the caching strategy and on what they want to obtain from it, a blind activation of caching might bring no visible results.

    If we take an Internet store as an example, then for each item of goods a file in cache memory will be created so that the server will not have to send requests to the database in case of any future queries of the buyer.

    Caching is a process technique that permits caching the outputs of rarely updated and resource-intensive parts of the code (for example, those actively working with the database).

    Main caching settings are located on the page Caching settings (Control Panel > Settings > System settings > Cache Settings).

    The correct use of cache allows to greatly increase the total site efficiency. However, it should be taken into account that unwise use of caching may bring about a serious grow of the /bitrix/cache/ directory size.

    In this example, the method CPageCache::StartDataCache checks the existence of the cache file which is valid and not expired. If such file exists, it is included and displayed, otherwise buffering is turned on. The buffered output is written to the cache file using the method CPageCache::EndDataCache.

    This example differs from the previous in caching the PHP variable $SECTION_TITLE in addition to the HTML code. Structure and type of the cached PHP variables is arbitrary. The method CPHPCache::InitCache checks if the unexpired and valid cache file exists. If this file is found, it is included; all the cached variables are available after the method CPHPCache::GetVars returns. The method CPHPCache::EndDataCache writes PHP variables and the buffered HTML code to the cache file.

    Note: To disable caching for a single page, you should authorize with administrative rights and open this page with the parameter &clear_cache=Y. Calling a page with parameter &clear_cache_session=Y after authorization will disable caching for all the session pages. Cache files can be deleted in the administrative section on the settings page of the Kernel module.

    Sometimes it is necessary to adjust the execution process of an API function. However, if the function is changed, these changes will be lost after the next update. For these cases, an event system has been developed. During the execution of certain API functions specific function invocations called event handlers are set at specific points.

    Note: Event handlers should be handled very carefully. Since the event model in Bitrix Framework is rich enough, hard-to-find errors may appear in the handler’s code without due care. They may seriously unnerve the developer.

    Invocation of a handler registering function determines which handler functions must be invoked in a place (in case of which event). At present, there are two such handler registering functions – AddEventHandler and RegisterModuleDependences. The set of events for each module is described in the documentation for each module. Here is, for example, the link to the main module events.

    Each module may provide other modules with an interface for implicit interaction – an event set. Such interaction permits making modules independent from one another to the fullest extent. The module knows nothing about the functioning particulars of the other module, but may interact with it through the event interface.

    The actions to be performed using events must be physically written somewhere, and they must be set to respond to a specific event.

    RegisterModuleDependences performs registration in the database, and AddEventHandler in the file init.php. I.e. the use of the first function results in an additional load on the database. It should be used in situations when the actions performed must be fixed once and for all precisely in the database.

    As a rule, events are divided by place of occurrence and purpose into the following group:

    Deferred functions are a process technique that permits to set the page header, navigation chain points, CSS styles, additional buttons in the control panel, meta tags, etc. with the help of the functions used directly in the page body. Relevant function outputs are displayed in the prologue, i.e. up the code they were set.

    The processing technique was created first of all in order to be used in components that are usually displayed in the page body, but at the same time a page header, a navigation chain point, and a control panel button, etc. may be added inside these components. Deferred functions cannot be used in the files of the component template template.php and result_modifier.php (because the results of their execution are cached).

    Components may be connected inside a deferred function but in this case CSS and js files must be connected manually.

    Note: There is a number of new functions that may work under the conditions of caching (SetViewTarget and EndViewTarget). But such functions are new and are not described in the documentation; hence they are rather considered an exception.

    Thus, the processing technique permits fragmenting all the page content by dividing it into parts using special functions that ensure the temporary deferral of execution of other functions (deferred functions). At the foot of the page, all the deferred functions are executed one by one, and their results are introduced into specially designated places inside the fragmented content of the page. After that, all the content is stuck together and sent to the site visitor’s browser.

    Attention! When using this processing technique it must be taken into account that no actions can be performed with the results of the functions that ensure the deferral of other functions.

    An example of the code in which the deferred function will not execute code in the template as expected:

    this code will not work because all the deferred functions are executed at the very foot of the page, in the service part of the epilogue.

    The page will display «Old header» and the browser – «New header«.

    The processing technique permits creating deferred functions using the method CMain::AddBufferContent.

    Site performance is complex phenomenon. Page opening speed is affected by the following:

    Performance failure can occur at any one or several stages. Application development plays a significant, albeit not the singular role in this list. At the end of the day, any advantages granted by the most sophisticated state-of-the-art platform can be messed up a lacking development effort.

    Attention! Methods for sessions handling described in this chapter are applicable from the main module version 20.5.0.

    You can handle the variable $_SESSION directly, however such approach is not advised. All data changes in the global variable will be saved, but we strongly recommend to use the new Bitrix24 API instead of this variable.

    Instead of directly using the variable, it’s better to use the object, returned by the method BitrixMainApplication::getSession():

    Sometimes, there is an objective to cache data, associated with the current user. However, one of available options is to use the session. Extra caution is required, however, since such approach is not always suitable:

    One of alternative approaches is to create a cache, associated with session_id(). In essence, it’s a simple session simulation. Starting from main version 20.5.400 there is a new option available.

    Operational principle is fairly simple: calling BitrixMainApplication::getLocalSession($name) always returns an instance of BitrixMainDataLocalStorageSessionLocalStorage. This is a cache element that automatically employs session_id().

    At this moment, if this is a first query and no data is available, the system creates an empty container. If cache contained data by $name, the container will be filled with data.

    Attention! SessionLocalStorage operates using cache, described in the .settings.php.

    Note: If this is a file-related cache, the SessionLocalStorage will use $_SESSION for storage Otherwise an issue of deleting and verifying legacy files occurs, affecting the file system operation.

    By default, a session in PHP supports a sequential access. it means that the session’s parallel hits are blocked and enqueued.

    This sequence is convenient for developers, but not always suitable for users, due to possible delays interface and application response. This results in necessity to reduce number of session interruptions.

    One of solutions on how to avoid blocking is to write SessionHandlerInterface. This solution, however, is not suitable, due to being excessively used in the Bitrix Framework and Partner-related code.

    Encrypted cookies serves as storage for KernelSession session.

    Storage for cold session: standard session with the same operational principle as before. That’s why the data will be stored in Redis, Memcache, Database.

    To enable a divided session mode, you need to change session[mode] to separated in bitrix/.settings.php. Add 'kernel' => 'encrypted_cookies' and 'lifetime' => 14400,.

    Kernel supports four variants (files, redis, database, memcache) for session data storage. Storage method is described in bitrix/.settings.php inside the ‘session’ section:

    Specific AJAX-action requires action naming convention awareness. In our case: Item::addAction -> vendor:example.item.add Item::viewAction -> vendor:example.item.view.

    vendor:example.item.add, vendor:example.item.view can be used for calling actions using BX.ajax.runAction:

    Or retrieve the link to action and send individual http-query.

    Controllers must be inherited from BitrixMainEngineController or its descendants. Controllers can be located inside the module, or inside the component in the file ajax.php and be a controller for component.

    Creating actions means creating methods in a specific controller. Method requires to be public and have an Action suffix.

    Returns action value is response data that can be sent to client.

    If action returns BitrixMainHttpResponse or its descendants, this object is sent to client. When action returns data, it must be converted to scalar or object format, to be converted to JSON for generating BitrixMainEngineResponseAjaxJson.

    Cumulatively, the action can return both scalars but objects as well that implement the following interfaces:

    There is an option to create action classes, inherited from BitrixMainEngineAction. This option may be required, when logic must be repeatedly used in several controllers. For example, for implementing the same exchange protocol in different modules (e. g. standard search, executing step-by-step actions with progress and etc.). You need to describe the method configureActions in the controller config map:

    To call this action, query testoPresto, as described in the config map. Class action supports pre- and post- filters and are no different from the standard action method. run() method reason is similar to other methods/ajax-actions.

    Preliminarily create and use controller classes, located in modules, as listed in this article, because this allows to organize better a repeated use of auxiliary code and business logic.

    In simple cases, when component is self-sufficient and is not actively used with module’s API, you can use controllers inside the component.

    Application query create controller based on naming convention. Next, controller executes the following work:

    Indicating several namespaces in the module.

    You can indicate several namespaces in .settings.php, in addition to defaultNamespace. This can may be necessary, when controllers are located next to their workflow entities. For example, in a «Drive» module we have integration with clouds.

    Now you have call-available controllers, located in both namespaces. Both of them support calling via action full name and via abbreviated record, due to available cloud alias.

    When you need to query the controller, implemented in the module, use the following method from component context by getting signed parameters:

    You can use the following two approaches for processing AJAX queries in component:

    Note: Executing component in Ajax mode sequentially executes CBitrixComponent::onIncludeComponentLang, CBitrixComponent::onPrepareComponentParams and launching an action with filters.

    Attention! Executing component in ajax-mode doesn’t launch method CBitrixComponent::executeComponent().

    Query controller handler in the ajax.php file allows creating lightweight ajax-query handler, by directly dedicating logic from component.

    The controller logic is fully the same as module controller description.

    For convenient error debugging in AJAX life cycle, enable debug => true in .settings.php, then you will be able to view error and exception traces.

    Frequently, AJAX-query must retrieve the same parameters from component that were used for its page display.

    As the result, your can use the parameters STORAGE_ID, PATH_TO_SOME_ENTITY in your AJAX-action. These parameters are signed and integrity is controlled by kernel.

    When you need to work with signed parameters inside ajax.php, use the method Controller::getUnsignedParameters() inside the controller action; it will contain array with unpacked data.

    You can organize page navigation in AJAX-action by implementing in the method parameters BitrixMainUIPageNavigation and return BitrixMainEngineResponseDataTypePage.

    Attention! ResponseDataTypePage($id, $items, $totalCount) $totalCount can be both an integer and Closure, which can be a lazy evaluation. It’s done for improved performance.

    For example, in case of REST, calculation of total records is always required, but for standard AJAX it’s optional. More performance and convenient is to use a separate AJAX-action for getting total records as per specific filter.

    You can make controllers, programmed inside a module, available for REST module. This is very convenient, because we re-use already written code.

    You need to correct .settings.php module config.

    Important! This is a new method that requires dependency from REST 18.5.1).

    In case AJAX-action must use CRestServer for a specific task, it can be done by declaring CRestServer as one of parameters.

    Please, be advised, the example above cannot work via standard AJAX, because CRestServer $restServer is not available in it and cannot be implemented. It can be available only for the REST module. If you declare it as optional, everything will work.

    It can happen that you need to distinguish in which context the action is presently executed: in REST or AJAX? You need to ask the controller:

    Scalar parameters $userId, $newName, $groups will be retrieved automatically from REQUEST.

    Failed search without matching results means that action won’t be launched; server will send response with error message informing about missing parameter.

    The parameter name can be arbitrary. Uses class for association:

    We have a standard AJAX-controller for a specific Folder. However, all actions are eventually performed for an object, with an attempt to load folder and etc. It’s preferable to receive Folder $folder on input.

    It’s important that closure after $className can indicate unlimited number of parameters needed for created object. Such parameters will be associated with data from $_REQUEST in the same way as scalars in standard action methods.

    In detail: first, we have declared class name which subclasses we will attempt to create when encounter them in AJAX-actions. Anonymous function will created an action instance.

  • $className — specific class name, specified type-hinting.
  • $mappedId — value, retrieved from $_REQUEST. Accordingly, searches folderId in this $_REQUEST. Parameter name that we will search in $_REQUEST by default will be created as {variable name} + Id.
  •       Folder $folder   => folderId
          Folder $nene     => neneId
          File $targetFile => targetFileId 
    

    As the result, you can described the type, if the module has class, for example, Model, from which all entities are inherited:

    new BitrixMainEngineAutoWireParameter(
    	Model::class, 
    	function($className, $mappedId){
    		/** @var Model $className */
    		return $className::buildById($mappedId);
    	}
    );
    

    Subsequently, you can easily use type-hinting in your AJAX-actions, directly handling the entities.

    Routing

    Available starting from the Main version 21.400.0. Presently, Bitrix24 doesn’t support custom modules employing their own routes in module folder.

      Launch

    To launch a new system of routing, you need to forward processing of 404 errors to the file routing_index.php inside the file .htaccess:

    #RewriteCond %{REQUEST_FILENAME} !/bitrix/urlrewrite.php$
    #RewriteRule ^(.*)$ /bitrix/urlrewrite.php [L]
    
    RewriteCond %{REQUEST_FILENAME} !/bitrix/routing_index.php$
    RewriteRule ^(.*)$ /bitrix/routing_index.php [L]

      Configuration

    Files with route configuration are located in the folders /bitrix/routes/ and /local/routes/. Connected file must be described in its [ds].settings.php[/ds][di]Bitrix Framework has several specific kernel settings without visual edit UI. This is done to prevent errors and setting update easily causing system crashes (database connection settings, caching settings and etc.).

    Learn more …[/di] in routing section:

    'routing' => ['value' => [
      'config' => ['web.php', 'api.php']
    ]], 
    
    // connects the following files:
    // /bitrix/routes/web.php, /local/routes/web.php,  
    // /bitrix/routes/api.php, /local/routes/api.php
    

    File format provides for returned closure, passed to the routing config object:

    <?php
    
    use BitrixMainRoutingRoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        // routes
    };

    Searches for matches in the same sequence, as described routes in the config.

    Routes

      Queries

    Route description starts from defining a query method. There are 3 combinations of methods available:

    $routes->get('/countries', function () {
        // triggered only by GET query
    });
    
    $routes->post('/countries', function () {
        // triggered only by POST query
    });
    
    $routes->any('/countries', function () {
        // triggered only by any query type
    });

    Use the method methods for specifying arbitrary set of methods:

    $routes->any('/countries', function () {
        // triggers any type of query type
    })->methods(['GET', 'POST', 'OPTIONS']);

      Parameters

    Use curly brackets to define parameter in the address:

    $routes->get('/countries/{country}', function ($country) {
        return "country {$country} response";
    });

    By default, [^/]+ pattern is used for parameters. The where route method is used for indicating your own criteria:

    $routes->get('/countries/{country}', function ($country) {
        return "country {$country} response";
    })->where('country', '[a-zA-Z]+');

    If parameter value can contain /, use the pattern .*:

    $routes->get('/search/{search}', function ($search) {
        return "search {$search} response";
    })->where('search', '.*'); 

    Parameters can have default values; in this case their presence is optional in the address:

    $routes->get('/countries/{country}', function ($country) {
        return "country {$country} response";
    })->default('country', 'Australia');
    
    // route will be selected when querying /countries/
    // the country parameter will have a specified value

    For convenience, you can set parameters that don’t participate in generating the address at all:

    $this->routes->get('/countries/hidden', function ($viewMode) {
        return 'countries response {$viewMode}';
    })->default('viewMode', 'custom');

    Access to route parameter values is gained via controller parameters or current route object:

    $routes->get('/countries/{country}', function ($country) {
        return "country {$country} response";
    });
    
    ...
    
    $app = BitrixMainApplication::getInstance(); 
    $country = $app->getCurrentRoute()->getParameterValue('country');    

      Names

    Assign this route a unique identifier — a name — for convenience and route lists systematization:

    $routes->get('/path/with/name', function () {
        return 'path with name';
    })->name('some_name');

    Subsequently, this will allow to query the route when generating links.

      Controllers

    Routing supports several types of controllers:

    1. Controllers BitrixMainEngineController:
      $routes->get('/countries', [SomeController::class, 'view']);
      
           // launches action SomeController::viewAction()
    2. Separate controller actions BitrixMainEngineContractRoutableAction:
      $routes->get('/countries', SomeAction::class);
    3. Closures:
      $routes->get('/countries/', function () {
               return "countries response";
           });

      Arguments can be a query object BitrixMainHttpRequest, current route object BitrixMainRoutingRoute, as well as route named parameters in any combinations:

      use BitrixMainHttpRequest;
      use BitrixMainRoutingRoute;
      
      $routes->get('/countries/{country}', function ($country, HttpRequest $request) {
          return "country {$country} response";
      });
      
      $routes->get('/countries/{country}', function (Route $route) {
          return "country {$route->getParameterValue('country')} response";
      });
    4. The class BitrixMainRoutingControllersPublicPageController is available for purposes of backward compatibility with public pages:
       $routes->get('/countries/', new PublicPageController('/countries.php'));

    Groups

      Merging in groups

    In case of similar attributes for several routes, it’s recommended to merge them into groups:

    $routes->group(function (RoutingConfigurator $routes) {
        $routes->get('/path1, function () {});
        $routes->get('/path2, function () {});
        $routes->get('/path3, function () {});
    });

    Merging itself doesn’t affect the system behaviour and is useful in case of shared attributes: parameters, prefix or name, that will be overviewed below.

      Group parameters

    When several routes have a shared parameter, it makes sense to move it up to a group level. This will allow avoid describing this parameter separately for each route:

    $routes
        ->where('serviceCode', '[a-z0-9]+')
        ->group(function (RoutingConfigurator $routes) {
            $routes->get('/{serviceCode}/info', [ServicesController::class, 'info']);
            $routes->get('/{serviceCode}/stats', [ServicesController::class, 'stats']);
    });

      Group prefix

    If the address start matches for several routes, move it as general for the group:

    $routes->prefix('about')->group(function (RoutingConfigurator $routes) {
        $routes->get('company', function () {});
        $routes->get('personal', function () {});
        $routes->get('contact', function () {});
    }); 

    The example above shows route addresses as /about/company, /about/personal and /about/contact, to avoid duplicating the general portion.

      Group name

    General portion of route name is generated in the similar manner as the prefix:

    $routes
        ->prefix('about')
        ->name('about_')
        ->group(function (RoutingConfigurator $routes) {
            $routes->name('company')->get('company', function () {});
            $routes->name('personal')->get('personal', function () {});
            $routes->name('contact')->get('contact', function () {});
        })
    ;

    The example above shows route names as about_company, about_personal and about_contact.

    Generating links

      Routes with name

    When describing a route, set a unique name for it:

    $routes->get('/countries/{country}', function () {
        return 'some output';
    })->name('country_detail');

    And use this name for generating a link:

    $router = BitrixMainApplication::getInstance()->getRouter();
    $url = $router->route('country_detail', ['country' => 'Australia']);
    
    // $url: /countries/Australia

    Names act as unique identifiers. When link format must be changed, i. e. its static portion:

    - $routes->get('/countries/{country}', function () {
    + $routes->get('/countries/{country}', function () {
        return 'some output';
    })->name('country_detail');

    In this case, you don’t have to change all links to this route, because they use specifically name for forwarding.

      Routes without name

    When unique name is not specified for the route, its permissible to indicate its address in the link. Helper BitrixMainRoutingRouter::url() may be useful for an available GET parameters:

    $country = 'Australia';
    $router = BitrixMainApplication::getInstance()->getRouter();
    $url = $router->url("/contries/{$country}", [
        'showMoreDetails' => 1
    ]);
    
    // $url: /contries/Australia?showMoreDetails=1

    Loggers

      Introducton

    Core now have available loggers, implementing the PSR-3 interface:

    • base abstract class BitrixMainDiagLogger, implementing the PSR-3 interface;
    • file logger: BitrixMainDiagFileLogger;
    • syslog logger: BitrixMainDiagSysLogger.

    Loggers are used by the log formatter BitrixMainDiagLogFormatter that replaces placeholders as per PSR-3.

    Note: Library is available starting from main version 21.900.0.

      Logger Interface

    PsrLogLoggerInterface interface is quite simple, representing a set of logging features that support levels of logging. Levels are set by constants PsrLogLogLevel::*.

    interface LoggerInterface
    {
        public function emergency($message, array $context = array());
        public function alert($message, array $context = array());
        public function critical($message, array $context = array());
        public function error($message, array $context = array());
        public function warning($message, array $context = array());
        public function notice($message, array $context = array());
        public function info($message, array $context = array());
        public function debug($message, array $context = array());
        public function log($level, $message, array $context = array());
    }

    Message can contain {placeholders}, replaced by data from the $context associative array.

    Also, a useful interface PsrLogLoggerAwareInterface is available if you want to notify that your object is ready to accept the PSR-3 logger:

    interface LoggerAwareInterface
    {
        public function setLogger(LoggerInterface $logger);
    }

      Loggers in Bitrix24

    While implementing PSR-3, loggers in Bitrix24 have expanded functionality. Now you can:

    • set a minimum logging level, below which logger doesn’t show anything,
    • set formatter.

    File logger BitrixMainDiagFileLogger can write messages into file, specified in constructor. When log size exceeds the set maximum, system performs a single-time log file rotation. Null — no rotation is needed. Default size: 1 Mb.

    $logger = new DiagFileLogger($logFile, $maxLogSize);
    $logget->setLevel(PsrLogLogLevel::ERROR);
    // prints into log
    $logger->error($message, $context);
    // Doesn't print into log
    $logger->debug($message, $context);

    Syslog logger BitrixMainDiagSysLogger is an addin to the function php syslog function. Constructor receives parameters, used by the function openlog.

    $logger = new DiagSysLogger('Bitrix WAF', LOG_ODELAY, $facility);
    $logger->warning($message);

    File logger uses the function AddMessage2Log and class BitrixMainDiagFileExceptionHandlerLog, as well as logging in the Proactive protection module (security).

      Message formatting

    Message formatter can be set into the logger. By default, uses the formatter BitrixMainDiagLogFormatter, implementing the interface BitrixMainDiagLogFormatterInterface:

    interface LogFormatterInterface
    {
    	public function format($message, array $context = []): string;
    }

    Formatter constructor receives parameters $showArguments = false, $argMaxChars = 30 (show argument values in trace, maximum argument length).

    $logger = new MainDiagFileLogger(LOG_FILENAME, 0);
    $formatter = new MainDiagLogFormatter($showArgs);
    $logger->setFormatter($formatter);

    Formatter main task is to insert values into message placeholders from context array. Formatter can process specific placeholders:

    • {date} — current time * ;
    • {host} — HTTP host * ;
    • {exception} — exception object (Throwable);
    • {trace} — backtrace array;
    • {delimiter} — message delimiter * .

    * — optional to pass in context array, substituted automatically.

    $logger->debug(
        "{date} - {host}n{trace}{delimiter}n", 
        [
            'trace' => DiagHelper::getBackTrace(6, DEBUG_BACKTRACE_IGNORE_ARGS, 3)
        ]
    );

    Formatter formats values from context array into convenient format depending on the value type. Accepts strings, arrays, objects.

    Use

    Standard object format can get a logger that supports the interface PsrLogLoggerAwareInterface. Can use a corresponding trait:

    use BitrixMainDiag;
    use PsrLog;
    
    class MyClass implements LogLoggerAwareInterface
    {
    	use LogLoggerAwareTrait;
    	
    	public function doSomething()
    	{
    	    if ($this->logger)
    	    {
    	        $this->logger->error('Error!');
    	    }
    	}
    }
    
    $object = new MyClass();
    $logger = new DiagFileLogger("/var/log/php/error.log");
    $object->setLogger($logger);
    $object->doSomething();

    However, it’s not convenient to change the code at the operational project to pass logger a desired object. Logger class provides an individual factory for this. The factory receives a logger string ID:

    use BitrixMainDiag;
    use PsrLog;
    
    class MyClass implements LogLoggerAwareInterface
    {
    	use LogLoggerAwareTrait;
    	
    	public function doSomething()
    	{
    	    if ($logger = $this->getLogger())
    	    {
    	        $logger->error('Error!');
    	    }
    	}
    
    	protected function getLogger()
    	{
    		if ($this->logger === null)
    		{
    			$logger = DiagLogger::create('myClassLogger', [$this]);
    			$this->setLogger($logger);
    		}
    
    		return $this->logger;
    	}
    }

    Configuration

    Root section for the .settings.php file indicates loggers in the loggers key. Description syntax matches with ServiceLocator settings. The difference is that service locator is a register and this file contains the factory config.

    Additional parameters can be passed to constructor closure via the DiagLogger::create('logger.id', [$this]) factory’s second parameter. Parameters allows to flexibly enable logging depending on passed parameters, including calling the methods of the object itself.

    Additionally, you can indicate minimal level of logging (level) and formatter. Formatter is searched in service locator by its ID.

    // /bitrix/.settings.php
    return [
    	//...
    	'services' => [
    		'value' => [
    			//...
    			'formatter.Arguments' => [
    				'className' => 'BitrixMainDiagLogFormatter',
    				'constructorParams' => [true],
    			],
    		],
    		'readonly' => true,
    	]
    	'loggers' => [
    		'value' => [
    			//...
    			'main.HttpClient' => [
    //				'className' => '\Bitrix\Main\Diag\FileLogger',
    //				'constructorParams' => ['/home/bitrix/www/log.txt'],
    //				'constructorParams' => function () { return ['/home/bitrix/www/log.txt']; },
    				'constructor' => function (BitrixMainWebHttpClient $http, $method, $url) { 
    					$http->setDebugLevel(BitrixMainWebHttpDebug::ALL);
    					return new BitrixMainDiagFileLogger('/home/bitrix/www/log.txt');
    				},
    				'level' => PsrLogLogLevel::DEBUG,
    				'formatter' => 'formatter.Arguments',
    			],
    		],
    		'readonly' => true,
    	],
    	//...
    ];

    Upon indicating closing constructor, its preferable to use the .settings_extra.php file, to avoid loosing code when saving settings from API.

    There is PsrLogNullLogger that can be installed to avoid writing if($logger) each time before calling logger. However, you need to consider, if extra message and context formatting work is worth it.

      Classes

    List of classes supporting logger factory:

    Class Logger ID Passed parameters
    BitrixMainWebHttpClient main.HttpClient [$this, $this->queryMethod, $this->effectiveUrl]

    Design Integration

    Note. Portal templates are system templates and cannot be customized! There is a technical possibility to copy, customize, and apply the template, but in this case the backward compatibility will be lost.

    The following aspects are reviewed in this section:

    • Site design template management;
    • Work with include areas and advertising areas;
    • Site navigation tools management: menu and navigation chain;
    • Main product localization principles;
    • Work with visual components;
    • Site optimization.

    The minimum requirements for study: knowledge of the basic techniques for website development, such as HTML, CSS, and PHP.

    Web design – it is first of all the development of the interface, as the user’s interaction environment with information, and not just a “beautiful picture”. Specifics of the web environment must be taken into account along with such things as usability and fit for purpose of creating a site. In addition, due regard must be given to the main scenarios of a user’s behavior and the particulars of the target audience.

    While working with the design of your own site you must remember that any work with site design has its own preferences. If you want to make any changes to the design, do the following:

    • First try to do it by editing the template of the site itself and CSS files;
    • If the previous step is impossible, then try to do it using site page editing means;
    • And only in the last resort start editing the component templates and files of the CSS component. In this case, templates must be copied and not edited in a system folder.

    This sequence of actions is required because during product update component templates are also updated. If you have changed (customized) a component template, it will not be updated. In this case, the functionality loss of the updated version and decrease in the security level are possible.

    Attention! If you have activated component cache, you might not notice the changes after introducing them to component templates or its CSS file. It is about caching itself, which shows you not a real view but a cached one from previous sessions. In this case, you only have to update the component cache by using the Update button on the administrative panel.

    Using Access Rights

    Quite often, when creating a site template, access to certain elements must be limited. An access right verification mechanism included in the system can be used while creating a site template for the following purposes:

    • To Control Menu Option View

      When editing the menu in an extended mode, a viewing condition may be set for each menu option. For example:

    • To control the menu template

      The level of users’ access rights may affect the menu template structure, elements, and images used, etc. An example of the verification of a user’s access right level for a menu template is provided below:

      <?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
      
      <?if (!empty($arResult)):?>
      <div class="blue-tabs-menu">
             <ul>
      <?foreach($arResult as $arItem):?>
      
             <?if ($arItem["PERMISSION"] > "D"):?>
                    <li><a href="<?=$arItem["LINK"]?>"><nobr><?=$arItem["TEXT"]?></nobr></a></li>
             <?endif?>
      
      <?endforeach?>
      
             </ul>
      </div>
      <div class="menu-clear-left"></div>
      <?endif?>

      Important! The conditions that include the verification of a value of the $PERMISSION variable are used only for the site menu.

    • To control site template

      A condition of use of a design template can be set up for each design template. The setup shall be made on the site parameter control page (Control Panel > Settings > System settings > Websites > Websites). E.g.:

      The most flexible tool to setup display conditions is the PHP Condition. Examples of php conditions to display the site template:

      $USER->IsAuthorized() Checks if a current user is authorized in the system.
      $USER->IsAdmin() Checks if a current user is the administrator.
      in_array(‘5’,$USER-> GetUserGroupArray()) Checks if a current user belongs to the specified group (in this case to a group with the ID equal to 5).
    • To control design template elements

      Control of display of site template elements, their form, color, and other parameters may also be effected based on the level of access rights of site users. For more details, please refer to the lesson Design Template Development.

    • Control of specific elements of the site

      The use of the access rights check feature permits to organize control of specific site elements (pages, sections, advertising, fora, etc.) by different users. Please see the relevant section of the course System administration.

    Site Design Template

    The information of this section gives an idea about the structure, creation technique, and use of the site design templates. In addition, this section describes the possibility to use templates for working and editing areas of a site page.

    Design template is an exterior appearance of a site determining layout of different elements on the site along with page art style and display mode. It includes programming html and php codes, graphic elements, style sheets, and additional files for contents display. It can also include component templates, ready page templates, and snippets.

    The use of templates makes it possible to perform the flexible design setup of sites, sections, and webpages. E.g., it is possible to apply a special festival design during a certain time, automatic control of the site exterior depending on visitor group, etc.

    The site may have many design templates and, vice versa, one template may be used in several sites.

    Design Template

    The site template is a group of PHP and CSS files (<file_name>.css). Templates are used for public section layout. Site template defines:

    • site design (layout, using sets of CSS and etc.);
    • menu types;
    • advertising areas;
    • include and editable areas;
    • occurrence of authorization or subscription forms in site design, etc.

    All templates are stored in directory /bitrix/templates/. Files that comprise the template are stored in subdirectory named by the template identifier (for example, /bitrix/templates/demo/ or /bitrix/templates/template1/).

    A common site design usually includes three main parts:

    • (1) Topmost section (header);
    • (2) Main section that is used to display the site presentation and information content, components or the code;
    • (3) Bottom section (footer).

    Header — is the top part of the design. As a rule, it includes the top and left part of the design with static information (logo, slogan, etc.), top horizontal menu, and left menu (if they are stipulated by design). Dynamic informational materials may be included. It is stored in a separate file …/<template_identifier>/header.php.

    Work area — is the page working area where the proper informational materials of the site are located. Work area comprises all the documents created by users that are stored in the files <document_name>.php in the relevant site folders.

    Footer — is the bottom part of the design containing static information (as a rule: contact details, information about author and owner of the site, etc.), low horizontal menu, and right menu (if they are stipulated by design). Informational materials may be included. It is stored in a separate file …/<template_identifier>/footer.php.

    Connection of Design Parts

    Assembling of typical page is implemented by uniting header and footer parts of the site design with work (main) area. In the general case a site page has the following structure:

    <?
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
    $APPLICATION->SetTitle("Bitrix Site Manager");
    ?>
    
    Main part
    
    <?
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");
    ?>

    Note: In addition to static information, the following items may be located in the template and work area:

    • Visual components;
    • Include areas;
    • Arbitrary PHP code.

    These site elements are intended for the display of dynamic information.

    Site Template File Structure

    Example of the general file and folder structure of a site template:

    • The components catalogue is intended for component templates;
    • The images catalogue is intended for template images (that do not depend on the browsable page) and copied from site layout;
    • The include_areas catalogue contains the areas of a template;
    • The lang catalogue contains language communication files;
    • The page_templates catalogue is intended for templates of pages and editable areas;
    • The snippets catalogue contains snippets – small fragments of html code intended to speed up the operation of the content manager associated with creation of frequent code blocks;
    • The themes catalogue is a template theme;
    • The header.php file is a part of the template BEFORE the contents;
    • The footer.php file is a part of the template AFTER the contents;
    • The description.php file is a name and description of the template;
    • The .styles.php files contains a description of styles for the visual page editor;
    • The template_styles.css file contains template styles (styles applied in the site design template itself);
    • The styles.css file contains styles for the contents and include areas. These styles may be used in visual editor.

    Site template developing

    The site template developing process consists of two basis stages:

    • the template prototype developing;
    • the full-functional template creation.

    The template prototype developing

    The prototype is a site template in HTML format. During the page makeup there are defined all functional areas in future site template. For example:

    • page title;
    • menu;
    • navigation chain;
    • authorization form;
    • subscription form;
    • advertising areas;
    • etc.

    The full-functional template creation

    At this stage, the HTML design elements are replaced with appropriate functional elements: source code and component calls. As a result, a PHP template of the site design is obtained.

    Note: When creating a site template, various program conditions may be used that affect the display of template elements for different site sections. For this, a certain property must be determined for a site section the value of which will be checked in the site template:

    <?if ($APPLICATION->GetProperty(“SECT_PROP”)=="Y"):?>
    

    Similar conditions may be entered for any template elements. E.g., deactivation of the display of include areas and the control of navigation chain display, etc.

    Recommendations for Template Creation

    The main aspects to be taken into account while creating a template:

    • When preparing graphic design, a design dividing line should be marked in advance for the prologue (header.php) and epilogue (footer.php).
    • The main design elements should be singled out for subsequent modification of the style sheets: fonts, fill colors, etc.
    • When developing the design of the menu of different levels, it is recommended to mark repeated elements in order to simplify menu template creation and for the further control of these menus.
    • The use of text elements instead of graphics is recommended to facilitate the support of different language versions of the site.
    • During the development of graphic design and the HTML template it is necessary to provide for the location of the main components of the site control system and to allocate menu areas, advertising areas, and areas for additional forms.
    • The template should be prepared taking into account subsequent table assembly. Layers may be used simultaneously.
    • Solid areas must be marked during preparation of graphic design. When assembling a template, these areas may be represented by table cells with solid color fill.
    • Graphic images associated with template should be located in the folder /bitrix/templates//images.
    • Site design template will be edited correctly in a visual mode, if HTML tag attributes do not contain php code and also, for example, if table lines and cells are not interrupted with php code while a table is formed. If the site design template code has such particulars, it should be edited only in the code mode.
    • Cascading style sheets used in the template should be divided into two style tables stored in two different files. Two files shall be located in the directory /bitrix/templates//. The file styles.css contains styles to represent the information contents of the page on the site. The file template_styles.css contains styles to display texts in the design template itself.

      If necessary, any number of style files may be connected to <head> in addition to styles.css and template_styles.css connected through showhead(). It should be done using regular links before closing the tag </head> additional style files can be placed in any folder. The result will be the same as if you had collected all your additional styles and written them in two files of the site template with standard names.

    The site template should be created on a local demo version of the product. A ready-made template must be exported using system means as a set of files in tar.gz format into a remote server and then deployed.

    Note: The use of complex components in the design template is strongly discouraged because in this case the rules of address rewriting become applicable to the entire site. It may affect the operation of computer numerical control of other components and page /404.php. Complex components must be located in #WORKAREA.

    Attention! Be very careful using visual editor to edit site template since unforeseen code distortions may occur.

    As a rule, a site requires several templates to be developed: for a home page, a section, a detailed page, etc. If there are very insignificant differences among the templates, the use of one template is preferable:

    • Either with an incorporated check verifying which page is currently open,
    • Or together with include areas.

    Experience has shown that this solution is by far cheaper in terms of site support.

    Site template management

    Templates for the Bitrix Site Manager can be loaded in the system in <template_name>.tar.gz. format. Also the site template can be added by copying the template folder to the system.

    The system templates management is implemented in the administrative section: Settings > System settings > Sites > Site templates

    There you can view, edit and upload the existing templates and also add new templates.

    The possibility to view templates layout directly in the template list is realized by means of template capture using. The template capture should be located in the corresponding template folder with the screen.gif name (for example, /bitrix/templates/demo/screen.gif).

    Template editing

    To view or modify a template code use the Edit item in the template context menu.

    Note: Template editing may also be accessed from the public part of the site. For this, use the option Edit template of the menu of the Site template button in the administrative panel:

    The field Site template design contains the template code.

    Click to enlarge

    Design template may be edited using a visual editor or working with a source code. The latter option is preferable.

    The possibility of the visual editing of a template is determined by Kernel module settings (option Use WYSIWYG editor for Site Templates).

    If this option is selected in the template editing form the option Use visual editor will become available.

    Attention! Site design template will be edited correctly in a visual mode, if HTML tag attributes do not contain php code and also, for example, if table lines and cells are not interrupted with php code while a table is formed. If site design template code has such particulars, it should be edited only in the code mode. In addition, template editing is not recommended in case of a complex layout.

    When editing a template in a visual editor, consolidated top and bottom parts of the site design are displayed. Components and functions written in the PHP programming language are inserted into HTML code and ensure the display of various types of information: metadata, page header, cascading style sheets, administrative panel, and subsequent control of this information using visual tools.

    Attention! The use of composite components in site design template is strongly discouraged. Please note that #WORK_AREA# separator is available in the template code which is used to indicate the boundary between the top and bottom part of the design. Here, the work area of the site page will be connected. In the visual mode of the template editing work area is indicated with . The template cannot be saved without this separator.

    Template export

    The special system interface features allow to export templates used in the system in the <file_name>.tar.gz format. The Downoadcontext menu item is used.

     

    Template import

    A ready-made template must be exported as a set of files using file manager or special interface of the system. A special Load template button of the context panel is located on the page containing the list of templates.

    If we click the button, the following form opens:

    • Specify the file containing template for upload using the Browse… button.
    • In case of upload by default the template will be unpacked and placed into a folder with a name that corresponds to the name of the uploaded file (/bitrix/templates/<template_identifier>/). For example, if the name of the uploaded file is template1.tar.gz, the template will be automatically placed into the folder .../template1/, and the identifier (ID) template1 will be assigned to the template itself.

    • In order to assign another identifier to a template and have the template itself placed in a folder with the appropriate name, the necessary template code should be indicated in the Template code field.
    • Also, an uploaded template may be linked as a default template for the selected site using the relevant option.

    Template creation

    A new site template can be created directly in the system with use of the New template form. To go to this form use the Add template button located on the page context panel.

    Creating the template via the system interface you can:

    • assign template ID (must be unique);
    • assign template name;
    • input template description for showing in template list;
    • input template source code;
    • assign used in template CSS:
      • The bookmark Site styles serves for description of cascading style sheets (CSS) used on the site pages. Style description is stored in the file styles.css in the site template folder.
      • The bookmark Template styles serves for description of cascading style sheets (CSS) used in the template. Style description is stored in the file template_styles.css in the site template folder.
    • define set of used in template pictures and include areas.

    The new template is stored in the /bitrix/templates/<template_ID> directory (this directory is created automatically).

    It is recommended to store all graphic files used in the template in the /bitrix/templates/<template_ID>/images/ directory.

    Note: It is recommended to deactivate caching during template creation.

    Page templates developing

    The Bitrix Framework allows creating and utilizing templates for work (main) and include page areas. Templates utilizing makes work with page having a complex structure (layout) easier and faster.

    All page and include area templates are stored in the /page_templates/ directory located in the corresponding template folder or in the folder .default (if these page templates are used for all site templates).

    Creating a new page in HTML editor mode you can just choose necessary page template in the list and than add page content.

    List of available page templates is created with use of .content.php file. This file is also stored in the /page_templates/ directory in the corresponding site template folder. This file contains associative array of page templates and their names intended for displaying in the list.

    <?
    if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    $TEMPLATE["standard.php"] = Array("name"=>"Standard page (Default template)", "sort"=>1);
    $TEMPLATE["page_inc.php"] = Array("name"=>"Include area for page", "sort"=>2);
    $TEMPLATE["sect_inc.php"] = Array("name"=>"Include area for section", "sort"=>3);
    ?>
    

    Using message files

    The Bitrix Framework allows to use one site template for several sites on different languages. This possibility is realized by means of message files usage:

    • in the template HTML prototype are defined areas intended for text displaying (for example, titles, ALT attributes, controls text etc.);
    • then in these areas should be placed special source code that calls language files containing necessary text messages on corresponding language.

    More detailed information on language files usage is given in the Message files section.

    Examples of work and problem solutions

    Simple examples of template application depending on various conditions

    If the phone section property is equal to Y

    $APPLICATION->GetDirProperty("phone")=="Y"

    If the current section is equal to /en/catalog/phone/

    $APPLICATION->GetCurDir()=="/en/catalog/phone/"

    If a current user is the administrator

    $USER->IsAdmin()

    If the template must be linked to a dynamic page (in the example, to a detailed page (card) of the goods)

    preg_match("#/catalog/?SECTION_ID=d+&ELEMENT_ID=d+#i",$_SERVER['REQUEST_URI']);

    Connection of Favicon.ico for Various Templates

    In order to have different symbols in different site templates a call of a symbol from the template must be added to header.php of the template:

    <link rel="icon" type="image/x-icon"
    href="<?=SITE_TEMPLATE_PATH;?>/images/favicon.ico" />
    

    Separate Template for the Home Page of the Site

    In addition to the main template for all site pages, a required template must be connected through the condition For folder or file indicating /index.php and taking into account the application sorting index of the template.

    Change of the Site Template Depending on the Time Parameter

    Task: How to implement a site template change depending on the time parameter (two templates in total, but they must be changed every hour)?

    For this, the current template must be changed according to the condition PHP expression.

    First Option

    On odd hours: ((date("H")+6)%2) == 1

    On even hours: ((date("H")+6)%2) == 0

    where +6 indicates the time zone.

    Second Option

    date("H")%2

    or

    !date("H")%2

    Application of the Template Using Both Conditions Simultaneously

    Task: How to set a template according to 2 conditions simultaneously (for a group of users and for both folder and file)?

    For this, the current template must be changed according to the condition PHP expression:

    in_array($groupID, $USER->GetUserGroupArray()) || strpos($APPLICATION->GetCurDir(), "/dir/")!==false || $APPLICATION->GetCurPage()=="/dir/file.php"
    

    Template Application Only to the Files with a Specific Extension

    Task: Which PHP expression should be used so that the template applies to all pages ended in .php but not .html?

    Solution: change of the template according to the condition PHP expression:

    substr($APPLICATION->GetCurPage(true), -4) == ".php" 
    

    Similarly for the files with the html extension:

    substr($APPLICATION->GetCurPage(true), -5) == ".html"

    Change of Site Header Design for Different Sections

    Task: The site is divided into several sections. The idea is that each section must have its own header in the design. There are no other changes in the design.

    The component Insert include area is connected to the template:

    <div id="header">
    <?$APPLICATION->IncludeComponent("bitrix:main.include", ".default", array(
       "AREA_FILE_SHOW" => "sect",
       "AREA_FILE_SUFFIX" => "headerinc",
       "AREA_FILE_RECURSIVE" => "Y",
       "EDIT_TEMPLATE" => "sect_headerinc.php"
       ),
       false
    );?>
    </div> 
    

    The header code of each section will be stored in the file sect_headerinc.php. The parameter "AREA_FILE_RECURSIVE" => "Y" means that the same header will appear in all subsections of this section, unless the parental sect_headerinc.php is specifically shut off in any underlying sections.

    Specific of working with AJAX

    Using the ajax mode has its specifics. For the navigation string to have its name at the page opened via ajax, the template must have the element with id="navigation". The div is optional: it can be span, h1, p and so on.

    Similarly, the header must have the id="pagetitle" element available.

    Using message files for localization

    Language communication is a group of phrases in different languages having the same meaning.

    The Bitrix Framework allows using the same template for several sites on different languages. This opportunity is realized by means of the message files mechanism:

    • areas intended for text displaying are defined in the template HTML prototype (for example, titles, ALT attributes, controls text etc.);
    • then, special source code should be placed in these areas that calls language files containing necessary text messages on corresponding language.

    For example, table headers, button inscriptions, component messages, etc. may be created using language communications.

    Note: language communication feature is also used to support multi-language interface of the administrative section of the system.

    Related links:

    • Localization Guide

    Implementation

    • The /lang/ folder is created in the a site template directory:

      /bitrix/templates/<>/lang/

    • The /lang/ folder has the folders with the utilized languages identifiers: /en/, /de/, /fr/, and etc. For example:
      /bitrix/templates/<>/lang/fr/

    • The languages identifiers corresponding to message files are stored within the folders. These files are characterized by the following properties:
      • the message file name is equal to the file name where this message file is called. For example, if a message file call is implemented in the template header (file header.php) then this message file must have name header.php.
      • message list in the file is stored the following way:
        <?
        $MESS ['COMPANY_NAME'] = "Company Name";
        $MESS ['MAIN_PAGE'] = "Home page";
        $MESS ['PRINT'] = "Print version";
        $MESS ['AUTH_LOGIN'] = "Authorization";
        $MESS ['RATES_HEADER'] = "Currency rates";
        $MESS ['SEARCH'] = "Site search";
        $MESS ['SUBSCR'] = "Subscription";
        ?>

    • At the beginning of file, where the message file call is implemented, the following function is added:

      <?
      IncludeTemplateLangFile(__FILE__);
      ?>

      The IncludeTemplateLangFile(__FILE__) connects a message file for the current language.

    • All text in template is replaced with the function calling the corresponding messages:

      <font class="search"><?echo GetMessage("SEARCH");?></font>


      The code (ID) of calling message is used as the GetMessage() parameter. The function verifies if the connected message file contains necessary message. If the necessary message exists then it is shown to page visitors.

    Localization

    The message files localization can be implemented the following ways:

    • manually (when the site administrator searches and translates necessary message files);
    • with use of the Localization module tools.

    The Localization module provides users with a handy interface for text messages search and translation. The Localization module allows:

    • to look through the message files distribution among the system files;
    • define the number of untranslated text messages for every file;
    • proceed to the necessary text messages translation.

    In order to view distribution of language communications by files, go to the page Interface localization (Control Panel > Settings > Localization > Browse Files) and click the button Show differences on the context panel:

    The number of untranslated phrases used in a file or directory for each language is marked in red.

    Note: For big projects, it is not recommended to search for differences in all the product files; it should be done in stages, for each folder. Otherwise, the error 504 Gateway Timeout may occur.

    In order to translate language phrases, a file for which language communications are used should be selected, and a translation of the communication to a relevant language should be entered:

    Note: This form shows translations into the languages of the same encoding.
    I.e. if the ISO-8859-1 encoding is set for the English and Spanish language settings and Windows-1251 for the Bulgarian language, the form will contain fields to introduce the translation of the message into English and Spanish. To translate the phrases into Bulgarian, the interface must be switched to Bulgarian.

    You can quickly start translating the missing messages directly from a page on which these messages are used (both in the public section and Control Panel). To activate the quick translation mode, use the Show Language File button (it is visible if the corresponding option is checked in the Localization module settings)

    or add the parameter show_lang_files=Y to the page address:

    As the result, list of localization files used on the page will be displayed at the bottom of the page:

    File names are shown as links, which can be clicked to open the language file for editing. In the end of the list you will find a search field. You can use it to find a required message (exact match).

    Information: All localized phrases can be collected with use of the special tool. Collected files can be placed in the Update system for downloading by other users. Please refer to the Localization Guide for more details.

    Change of Phrases in Components and Modules

    Sometimes during developing, it’s necessary to change some words or phrases in the system components or modules.

    The main aim of this technology is to allow the developer to modify language phrases after connecting with the language file.
    A list of changes is kept in the file: /bitrix/php_interface/user_lang/<language_code>/lang.php

    In this file, the elements of the array $MESS must be determined as $MESS['language file']['code of the phrase'] = 'new phrase'‘, for example:

    <?
    $MESS["/bitrix/components/bitrix/system.auth.form/templates/.default/lang/en/template.php"]["AUTH_PROFILE"] = "My Profile";
    $MESS["/bitrix/modules/main/lang/en/public/top_panel.php"]['top_panel_tab_view'] = "View";
    $MESS["/bitrix/modules/main/lang/en/interface/index.php"]['admin_index_sec'] = "Pro+Pro";
    ?>

    The first line changes the text of the link in the authorization form component; the second line changes the name of the tab of the public panel; and the third line changes the line for index page of the control panel.

    Theoretically, there can be problems with maintenance of this file, if the code of the phrase or arrangement of the files are changed, but in practice we try not to do that, because it will be additional work for localizers.

    Localization Archive Import/Export

    Importing and exporting into CSV file

    CSV file Import/Export feature is used to handle localization files manually.

    • Go to the required folder (for example, activity) to the Export files tab (Settings > Localization > Browse files) select entirely replace language files to export all localization strings of this folder (module), or export only new messages to export only translated strings into CSV file.
    • Click Export and the created localization CSV file window ill appear.

      By default, the name of the CSV file will consist from the folder (or module) names, included into the localization. Otherwise, the file will have the name bitrix_activities.csv

    • Edit the required strings and save all changes in the same CSV file.
    • In the similar manner, you can select the way of adding localization into the system via Browse files: import only new messages or entirely replace language files, select CSV file and click Import:

    The module has been translated.

    Collect localization strings

    After the system localization archive was created, a full localization package for each system language can be created.

    • Go to the page Collect strings (Settings > Localization > Export and Import).

    • Select the required options in the Collect strings tab:
      • Select language — indicate which language phrases are to be exported;
      • Localization version date — date format YYYYMMDD for the localization archive;
      • Convert To UTF-8 — select the option to convert into a national encoding (UTF-8) for all localization files (by default, the files will be collected in the current site encoding otherwise);
      • Pack Files (tar.gz) — option to compress files into the archive of tar.gz format;
    • To launch the collection of localization package, click the Build Localization button.

    After the localization archive is collected, a link with the archive location is provided (if the option to build archive with tar.gz format is selected) or to files of the complete archive.

    To import the localization archive into the system:

    • Go to the page Browse files (Settings > Localization > Export and Import):

    • Select the required options in the Collect Strings:
        indicate the language phrases of which language are to be imported

      • Localization archive (with tar.gz format);
      • Select Language (the required language can be already installed in the system, or can be created via the Add link);
      • Convert from UTF-8 — select this option if your project is in national encoding and the localization archive to be imported is in UTF-8;
    • To launch the localization package import into the system, click the Import Localization button.

    Related Links:

    Bitrix Framework Localization Guide.

    Editable areas

    Editable area is a specially designated area on the site page that may be edited separately from the main contents of the page.

    Include areas serve to place reference information, forms (subscription, voting, surveys, etc.), news, and any other static and dynamic information. Also the areas indicating copyrights, graphic links, contact information, company logo, etc. may be performed as an include area.

    The Bitrix Framework allows creating different types of editable areas:

    • editable area of the certain file – displayed only when viewing the current document;

    • editable area of the certain section – displayed for all documents of the given section;

    • editable area of site template – displayed in predefined areas of site design template.

    The number of editable areas can be extended. In this case you need to implement the corresponding modifications in site template. For example you may need to include php code calling the additional editable areas.

    Besides that, editable areas can be displayed according to any program conditions. For example, editable areas can be shown only for exact user groups or only for authorized users, and etc.

    You can turn on the special mode that enables to view the include areas by clicking the Edit Mode button in the administrative toolbar. As the result all editable areas will be spotlighted as separate blocks. Links allowing to proceed to area editing will be displayed in top left conner of each block.

    Managing editable areas

    The contents of include areas are stored in separate PHP or HTML files. Areas for pages or sections are saved with a suffix. E.g., in the product files supplied the suffix _inc is used as a designation of the include area for a page, and the include area for a site section is stored in the sect file with a suffix added (e.g., sect_inc.php).

    Important! The file with include area must be saved in the same directory with the page for which it was created. Include area for a section must be saved in the folder of this section.

    Areas in the site design template should be connected using the component Insert include area or using the function of IncludeFile().

    The suffix used for the designation of include areas is determined by the option having the same name in the settings of the component Insert include area. The component may be located not only in the design template but also on the site pages, provided that file suffix must be set as different from that used in the template.

    Note: The type of the include area is determined by the parameter Show include area of the component Insert include area.

    If the component is located in the site design template, the information from file will be displayed on the entire site. Parameter setup is available only for users with the operation Edit PHP code (edit_php) at the kernel module access level.

    Include Area Layout

    In order to place the include area proceed as follows:

    • Open a site template or a page in a visual editor for editing.
    • Add the component Insert include area (bitrix:main.include) to the site template (or page body) and set up its parameters.

    Using the editable areas when integrating into the site design has limitations. They are related with the size, assigned for a slot in which the component is allocated. If a text, an image or any other object has the size larger than the slot, assigned for the the component, the site design will become misaligned.

    Using the editable areas allows to manage not only the text. An image instead of a text can be placed in such an area (or a Random photo component) and receive an custom appearance for each section. With this, the design variation will be «dynamic» and can be modified.

    Creating and Editing the Include Area

    Include areas may be created:

    • From the administrative section in Site Explorer (Control Panel > Content > Site Explorer > Files and Folders) by creating a file with the relevant name;
    • From the public section of the site in the edit mode. In the places where include areas are supposed to be displayed, the icons will be shown for quick access to creation of these areas.

      Note: A file of the include area will be created and named in accordance with the suffix indicated in the component settings – for the option for section, or a file name – for the option from file.

      After the command of Add Area is selected, visual editor will be launched to create the contents of the include area. If the command of Edit Area as PHP is selected, it will become possible to add an area in the PHP code mode.

    Likewise, it is possible to switch to editing of include areas as follows:

    • Directly from the public section of the site in the edit mode

    • Or from the administrative section by opening relevant file in Site Explorer for editing.

    Attention! If the option from file is used as include area, you have to check that the file is connected from the system and not accessed directly. It should be done using the following line:
    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>.

    Example of contents of included file:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
      <div class="bx-main-title">World Book</div>
      <span class="bx-main-subtitle">Books</span>
    

    Templates of Include Areas

    Editable areas are created on base of templates stored in folders with the names /page_templates/:

    • /bitrix/templates/.default/page_templates/ — in case if this editable area template is utilized for all site templates;
    • /bitrix/templates/<template_ID>/page_templates/ — in case if for this site template are used individual editable areas templates.

    If you want the editable areas to be added to the list of available templates of WYSIWYG editor, add the editable area templates manes in the file .content.php.

    The file .content.php is stored in the folder /page_templates/ located in the corresponding template directory.

    Example of file contents:

    <?
    if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    IncludeTemplateLangFile(__FILE__);
    
    $TEMPLATE["standard.php"] = Array("name"=>GetMessage("standart"), "sort"=>1);
    $TEMPLATE["page_inc.php"] = Array("name"=>GetMessage("page_inc"), "sort"=>2);
    $TEMPLATE["sect_inc.php"] = Array("name"=>GetMessage("sect_inc"), "sort"=>3);
    ?>
    

    Also the name of utilized template can be defined with use of special parameter while assigning editable area to the site. (see the code spotlighted with blue color in the example below).

    Assigning editable areas to site templates is implemented with use of the function IncludeFile(), places in the necessary areas of site template:

    <?
    $APPLICATION->IncludeFile(substr($APPLICATION->GetCurPage(),
    0, strlen($APPLICATION->GetCurPage())-4)."_inc.php", Array(),
    Array("MODE"=>"html", "NAME"=>GetMessage("PAGE_INC"), "TEMPLATE"=>"page_inc.php"));
    ?>
    <?
    $APPLICATION->
    IncludeFile($APPLICATION->GetCurDir()."sect_inc.php", Array(), Array("MODE"=>"html",
    "NAME"=>GetMessage("SECT_INC"), "TEMPLATE"=>"sect_inc.php"));
    ?>

    Note: If no variable is indicated, the system will use a default template, which is the page template.

    Deleting Include Areas from the Demo Template

    The line responsible for connecting an include area must be commented out or deleted in the template code. Normally these are files ending with _inc.php.

    The line may look like the following:

    <?
    $APPLICATION->IncludeFile(
    substr($APPLICATION->GetCurPage(), 0, strlen($APPLICATION->GetCurPage())-4)."_inc.php",
    Array(),
    Array("MODE"=>"html", "NAME"=>GetMessage("PAGE_INC"), "TEMPLATE"=>"page_inc.php"));
    ?>
    

    Example of Using Include Areas

    Task: The site is divided into several sections. The idea is that each section must have its own header in the design. The design has no other changes. What is the best way to implement the change of section headers?

    Solution: The component of the «Include Area (for section)» is connected to the template:

    <div id="header">
    <?$APPLICATION->IncludeComponent("bitrix:main.include", ".default", array(
       "AREA_FILE_SHOW" => "sect",
       "AREA_FILE_SUFFIX" => "headerinc",
       "AREA_FILE_RECURSIVE" => "Y",
       "EDIT_TEMPLATE" => "sect_headerinc.php"
       ),
       false
    );?>
    </div>

    Header code for each section will be stored in the file sect_headerinc.php. The parameter of «AREA_FILE_RECURSIVE» => «Y» means that the same «header» will appear on all subsections of this section, if the parental sect_headerinc.php is not specifically shut off at a lower section level.

    Navigation tools

    Navigation tools shall be included in the site template. The use of special components is recommended for their implementation.

    Site Map

    Site map is one of the navigation elements on the site that is a standard element of the modern site. As a rule, the page headers contained in the list constitute links to these pages.

    Creation of a Site Map

    The component of Site map (bitrix:main.map) is used to create the site map. The component is located in the folder Content > Site map of the panel Components 2.0 of the visual editor.

    The map is built up based on the menus used in the system. The menu types are specified in the settings of the Kernel module in the Site Map section (Control Panel > Settings > System settings > Module settings).

    Note: Several types separated by a comma may be used simultaneously as a basis for building up the reference level and branches of the site map.

    • Create a separate page and place the component of the Site map.
    • Setup the component parameters:
      • Maximum number of nested levels — is the depth of the section nesting that must be reflected in the menu. If the number in this field is more than 4, the site structure should be thought over again.
      • Number of columns – indicate how many columns will be used to create the site map image. This number largely depends on the site design, nesting level used, and site structure. Allocation of map points in the columns is based on the first level menu options. I.e., if one of the menu options has a slightly deeper structure compared to other options, then in case a deep nesting level is chosen on the site map you can obtain a long column for this point and short columns for other points.
      • Show descriptions – descriptions of sections (physical folders) will be displayed. A description of information block sections is not displayed.

    Note: If a site folder is specified incorrectly (e.g., there is no closing slash) in the site settings (Control Panel > Settings > System settings > Websites > Websites > _ site _), the component will not be able to create the site map.

    Examples of Work

    How to include site sections that are not specified in the menu into the site map?

    Normally, the customization of a component template is required to do so, but there is also another solution. The simplest one is to prepare their own menu type for these sections and add it, separated by a comma, in the settings of the main module in the menu types option of the site map menu.

    Menu

    Menu is a design element that constitutes the main site navigation tool.

    This section contains description of the main principles of creating and managing site menu, including description of file with menu data and menu template structure. Also the possibility of dynamic menu usage is mentioned.

    Menu Types

    Menu types are the menu organization principle. By default, two menu types are used in the installation package – Top and Left.

    Several menu types are possible, depending on the site function: top, left, and bottom, etc. In each menu component, two menu types may apply: one as the main menu, and another as an additional menu, provided that multi-level templates are used.

    In the most general case, the site has one “main” menu corresponding with the top most hierarchy level and is reflected in all the site sections. In addition, the system often uses a “secondary” menu (or a second-level menu) that includes links to the subsections and documents of the current section.

    The menu in the system is hereditary. This means that if a certain menu type is selected for a menu component in the template, this menu will be translated downwards to all site sections and pages with this template, unless a proper menu was created for these sections and pages. This mechanism is convenient for the site main menu; normally it is assigned the Top type.

    Note: If a higher menu must not be reflected in the lower level section, just create a menu in the relevant section without menu options.

    As a rule, a section menu shall be created for each section and translated to all the pages of the section. If necessary, a proper menu can be created for each subsection and a proper type applied thereto.

    Types of used on a site menu are defined in the administrative section on the Site Explorer module page.

    For example, let us assume that the system uses two menu types:

    • Left menu – “left” type;
    • Top (main) menu – “top” type.

    A menu type (defined on this page) will be utilized as prefix both for file with the menu template (e.g., top.menu_template.php) and for file with section menu items (e.g., .top.menu.php). Besides the menu type is used calling necessary menu in site template.

    Note: Menu types can be defined for every site individually.

    The menu types are assigned arbitrarily. But to make work with menu more convenient it is recommended to use meaningful designations for menu types. For example, top, left, bottom.

    Menu Building and Display

    Generally, the task of menu creation consists of the following:

    • marking HTML elements to build up the menu;
    • creating menu template (creation of the Menu component template);
    • including a menu display function (Menu component call) in the general template (“prologue” and “epilogue”);
    • completing menu in accordance with the site structure.

    Menu Structure

    Any menu on the site consists of two parts:

    • $aMenuLinks data array determining menu composition which gives names and links for all menu options. Data array is managed through an administrative interface;
    • template of an external display of the menu. The menu template is a PHP code determining the external aspects of the menu (Menu component template). The menu template processes the data array giving an HTML code in the output.

    Menu Data Array

    Data for each menu type are stored in a separate file. Its name has the following format: .<menu type>.menu.php. For example, to store the data of the left type menu, the file .left.menu.php will be used, and to store the menu data of the top type – the .top.menu.php file.

    The menu is inherited hierarchically. The menu files are located in the folders of the site sections where the display of the relevant menu types is required. If no relevant menu file is created for this section, the system searches for the file in the catalog of the upper level.

    For example, since the main menu (it is a top type menu in the product demo version) must be displayed in all the sections, the file of this menu shall be located only in the root catalog of the site.

    Accordingly, the second level menu (it is a left menu in the product demo version) is displayed separately for each section of the site. That is why a proper file for this menu type must be created in the folder of each section.

    Another example: a visitor is in the section /en/company/about/. In order to display a left type menu, the menu file will be searched in the following order:

    1. /en/company/about/.left.menu.php
    2. /en/company/.left.menu.php
    3. /en/left.menu.php
    4. /.left.menu.php

    If the menu is found in one of the catalogs, the search will be stopped and the next catalogs will not be searched.

    The Bitrix Framework system also permits you to create a dynamic type menu. I.e. the data array of such a menu is generated automatically based on certain data obtained using the source code. This code must be stored in the folder of a relevant site section in a file named .<menu type>.menu_ext.php..

    The principal task of these files is to manipulate a $aMenuLinks array. These files cannot be edited visually in the Site Explorer module, which is why they will not be edited accidentally during the visual editing of the menu. Use the Menu items (bitrix:menu.sections) component to create this file.

    Note: The paragraph above describes only the adding names of information block sections to the created menu. This variant is not suitable, for example, for adding forum names to the menu.

    Attention! If catalog sections without computer numerical control are used as menu options, it is necessary to indicate variables in significant variables of a query.

    A drop-down menu of the Products section offered in the demo version of the product may be a good example of such a menu. Here, upper menu options are created normally, and the remaining options (Sofas & Loveseats, Office Desks and Chairs etc.) are formed dynamically.

    In this case, the names of the groups of the Products catalog created based on the information blocks are used as menu options.

    The source code used to generate the menu is stored in the file .left.menu_ext.php in the folder /products/.

    The following standard variables may be used in the files .<menu type>.menu.php:

    • $sMenuTemplate — is an absolute path to the menu template (this variable is used very rarely);
    • $aMenuLinks is an array; each element of this array describes the next menu option.

      This array structure:

      Array
      (
          [0] => menu option 1
              Array
                  (
                      [0] => menu option header
                      [1] => menu option link
                      [2] => array of additional links for a menu option selection:
                          Array
                              (
                                  [0] => link 1
                                  [1] => link 2
                                  ...
                               )
                      [3] => array of additional variables transmitted to the menu template:
                          Array
                              (
                                  [name of variable 1] => value of variable 1
                                  [name of variable 2] => value of variable 2
                                  ...
                               )
                      [4] => condition for the menu option to appear
                             is a PHP expression which must return “true”
                  )
          [1] => menu option 2
          [2] => menu option 3
          ...
      )
      

    Examples of menu files

    <?
    // file example .left.menu.php
    
    $aMenuLinks = Array(
    	Array(
    		"Furniture Company", 
    		"company/", 
    		Array(), 
    		Array(), 
    		"" 
    	),
    	Array(
    		"News", 
    		"news/", 
    		Array(), 
    		Array(), 
    		"" 
    	),
    	Array(
    		"Products", 
    		"products/", 
    		Array(), 
    		Array(), 
    		"" 
    	)
    );
    
    ?>
    <?
    // file example .left.menu_ext.php
    
    if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    global $APPLICATION;
    
    $aMenuLinksExt = $APPLICATION->IncludeComponent(
    	"bitrix:menu.sections",
    	"",
    	Array(
    		"ID" => $_REQUEST["ELEMENT_ID"], 
    		"IBLOCK_TYPE" => "books", 
    		"IBLOCK_ID" => "30", 
    		"SECTION_URL" => "/e-store/books/index.php?SECTION_ID=#ID#", 
    		"CACHE_TIME" => "3600" 
    	)
    );
    
    $aMenuLinks = array_merge($aMenuLinks, $aMenuLinksExt);
    
    ?>

    Organizing the Menu Display

    In order to display the menu on the site pages, the component Menu (bitrix:menu) is used. For example, the top menu on the demo site is invoked as follows:

    <?$APPLICATION->IncludeComponent(
    "bitrix:menu",
    "horizontal_multilevel",
    Array(
    "ROOT_MENU_TYPE" => "top",
    "MAX_LEVEL" => "3",
    "CHILD_MENU_TYPE" => "left",
    "USE_EXT" => "Y"
    )
    );?>

    This code is located in the areas of the site template designated for the menu display.

    Building the Site Menu

    Menu for display is built as follows:

    • menu display call is included in the general template;
    • when loaded, the component checks for the availability of a file containing array of values for the menu in the current site section;
    • after that, the component invokes a building template for this type of the menu and displays an HTML menu on the screen.

    Menu Templates

    Data of the Menu component are displayed using templates. The templates may be created by users independently.

    System Templates

    Six templates are included in the Bitrix Site Manager installation package by default:

    • Default (vertical menu by default) is a template for the vertical menu. It is the simplest template. If a nesting depth higher than 1 is selected in the component parameters, you will see the list of site pages in the general hierarchy. I.e. the index page of the site (section) will be located on the same level with a nested page (section). It makes the understanding of the site structure by a visitor more difficult. That is why the template is recommended for simple menu types and the main menu of the top level. The template is simple enough for customization under a specific design.

    • Tree (Tree menu) is a template for vertical menu. It implements the menu as a tree-like structure similarly to Windows Explorer. Nested pages are shown as pages in the folder (section), which makes it significantly easier for a user to understand the site structure. This menu is not always convenient for a branched structure of the Internet project because it significantly extends the column where it is located in a vertical direction.

    • Vertical_multilevel (Vertical multilevel dropdown menu) is a template for the vertical menu. It implements the menu with dropdown options of a lower level menu that maintains the visitor-friendly site structure properly for the tree-like menu, but at the same time does not extend the design the way the branched structure does.

    • Grey_tabs (Gray menu in the form of bookmarks) and Blue_tabs (Blue menu in the form of bookmarks) are templates for the horizontal menu. They differ only in appearance. These are the simplest templates similar to the default template for the vertical menu.

    • Horizontal_multilevel (Horizontal multilevel dropdown menu) is a template for the horizontal menu. It is similar to Vertical_multilevel (vertical multilevel dropdown menu) and implements the menu with dropdown options of the lower level menu.

    Creating Menu Templates

    Selecting HTML Elements to Create Menu

    The creation of menu templates is started from selecting the necessary HTML areas in the site template:

    • Unchanged top and bottom part of the template;
    • repeated elements. For example, table cells for horizontal menu and lines for the vertical menu.

    Creating a Menu Template

    All menu templates have a similar structure:

    • menu template prologue area;
    • area describing the replacements for the different conditions of template processing;
    • menu template body area;
    • menu template epilogue area.

    The array of $arItem, which is a copy of menu option array, is used in the php template to display the menu. In this array, each option represents, in its turn, an array using the following parameters:

    • TEXT — menu option header;
    • LINK — menu option link;
    • SELECTED — whether a menu option is selected at the time; the following values are possible:
      • true — menu option selected;
      • false — menu option is not selected.
    • PERMISSION — the right of access to the page indicated in LINK for the current user. The following values are possible:
      • D — access denied;
      • R — read (the right to view file contents);
      • U — document flow (the right to edit file in a document flow mode);
      • W — write (the right to direct editing);
      • X — full access (the right to direct editing of the file and the right to change access rights to this file).
    • ADDITIONAL_LINKS is an array of additional links for menu selection;
    • ITEM_TYPE is a flag indicating the link type specified in LINK, the following values are possible:
      • D — directory (LINK is ended with “/”);
      • P — page;
      • U — page with parameters.
    • ITEM_INDEX — sequential number of a menu option;
    • PARAMS — associated array of menu option parameters. The parameters are set in the extended menu editing mode.

    Let us consider the creation of a menu template using the example of the Left menu provided in the demo version of the product (template .default of the Menu component bitrix:menu):

    <?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
       <?if (!empty($arResult)):?>
          <ul class="left-menu"> 
          <?foreach($arResult as $arItem):?>
              <?if($arItem["SELECTED"]):?>
                   <li><a href="<?=$arItem["LINK"]?>" class="selected">
                   <?=$arItem["TEXT"]?></a></li>
              <?else:?>
                   <li><a href="<?=$arItem["LINK"]?>">
                   <?=$arItem["TEXT"]?></a></li>
              <?endif?>
          <?endforeach?>
          </ul>
       <?endif?>

    The repeated part of the menu marked in the previous step is allocated in the body of the menu template.

    When creating the menu template, additional styles will be required in the style sheet (CSS). E.g., for text menu: the color of a menu option and the color of a current (active) menu option.

    Section headers may require a separate description in the template (e.g., the name of the current section during subsection view). In addition, the use of graphics or text denominations may be added; e.g. that this option refers to subsections or a document of the current section, etc.

    Note: All menu templates are stored in the component folder: /bitrix/components/bitrix/menu/templates/.

    Quick access to template editing of each menu type may be effectuated in the Edit Mode using the option Edit component template of the command menu of the component control button.

    Note: The menu template, if it is a system template, must be copied into the current site template before making any changes.

    When editing such a template from the public part of the site, the system will automatically offer the possibility of copying.

    Menu Control

    Menu is controlled using both tools of the administrative and public sections.

    Creating Menu

    The command Add menu of the Add button located on the context panel of the File Manager makes it possible to start creating the section menu from the administrative section:

    The menu will be created for a section in which the folder is currently opened in the File Manager.

    Note: As a result of these actions, a file with menu data is created with a name .<menu_type>.menu.php. However, the name of the data file in the file manager is automatically represented as a link of the “<menu_type>Menu type.

    Menu Editing

    Note: When editing the file .<menu_type>.menu.php is changed (e.g., .top.menu.php). However, work with this file is carried out through a special interface of the system. It makes the work directly with source code unnecessary and gives the possibility to edit the menu options in visual mode.

    In order to edit the menu from the administrative section go to File Manager and open the file of a relevant menu for editing.

    The system provides for two modes of menu editing (use the button located in the context panel of the menu editing page to switch between them):

    • Simple mode of editing;

      The mode permits to determine the menu type and indicate a name of menu option, navigation link, and a sorting index value.

    • Advanced mode of editing.

      The following data are available for control in this mode:

      • type of the menu being edited;
      • template which serves as a basis to generate the menu (the field is used if the new menu must be generated based on a template which is different from the default template);
      • name of the menu option;
      • navigation link;
      • sorting index;
      • a set of additional links that correspond to the same menu option. A set of links to pages that, when accessed, will also highlight this menu option that is established in this field. For example, in order to have the Book catalog menu option highlighted when any Book catalog section page is viewed, this field must contain a link to the folder with all the pages of the section (or necessary pages): /e-store/books/;
      • viewing conditions. For example, it permits you to introduce viewing restrictions that are applicable to this menu option for users with certain access rights;
      • Additional parameters is a set of arbitrary parameters that may be processed in the menu display template and are represented accordingly. For example, if a menu option is a section header, it may be specified in the option parameters as follows: parameter name – SEPARATOR, value – Y. When creating a template, this parameter may be verified and this option may be marked with a separator.

        Parameters are stored in an associated array $PARAMS as name => value pairs. When creating a menu according to a template, the template itself may include parameter verification, for example:

        if ($PARAMS["MY_PARAM"]=="Y")
        

        Note: The number of additional parameters in the form may be increased using the option Number of additional menu parameters in the settings of the Site Explorer module, Website Parameters section.

    Problems with Menu Caching

    Oversized Folder Containing Menu Cache

    Situation. A big size of the folder bitrix/managed_cache/MYSQL/menu: 1.9 Gb is revealed. The site has 4 types of cut-through menus and many pages. What is the problem and what can be done about it?

    Cause. One cache file is created per page for each type of the menu used. In addition to that, if caching is set for various groups, multiply this number by the number of such groups. I.e., for each page you will have 4 files of the menu cache (if by groups, multiply by the number of groups). That explains the size of the folder.

    Such a number of files by itself is not a problem, provided that there is enough space on the disk. The problem is that the accelerator (in our case, APC) saves these files into cache overfilling it.

    Solution: Make sure that template.php and result_modifier.php contains no requests and heavy computing and exclude a file cache from the accelerator. Requests must be cached in menu_ext files.

    apc.filters="-/bitrix/cache,-/bitrix/managed_cache/,-/upload/"

    Note: If a certain menu type is not redefined in subfolders of the site, the following parameter may be specified upon connecting to the menu:

    "CACHE_SELECTED_ITEMS" => "N",

    As a result, the URL will not appear in the key during creation of the menu cache file. The selected level will be calculated after data from the cache are retrieved.

    Examples of Resolving Specific Tasks in the Menu

  • Highlighting of Links
  • How to Open a Menu Option in a New Window?
  • Displaying a Menu Option for Certain User Groups
  • Displaying a Menu Option to Unauthorized Users
  • Displaying a Menu Option when Visiting a Certain Site Section
  • Displaying Certain Options Only on the Home Page and in an Internal Section
  • Displaying a Menu Option Referring to a Specific Product Connected with the Product Opened on This Page
  • Pop Up Tips for Menu Options
  • How to Put Images Close to Menu Options?
  • Different Menu Option Images for Different Languages
  • Displaying of Customized Menu Different from Template Menu for Specific Site Sections
  • How to Keep Tree Menu Open at All Times?
  • Having Opened a Page through a Tree Menu, How to Prevent the Menu from Rolling Up and Make It Show on Which Page You Are?
  • Dropdown Menu with Inactive Options of the First Level
  • Hiding of a Side Menu According to the Page Properties
  • Menu and Cache
  • Kernel module version 9.0.5 in the bitrix:menu component has the parameter Delay building the menu template that permits adding menu options to the components.

     $GLOBALS['BX_MENU_CUSTOM']->AddItem('left', array('TEXT' => 'Mob.version', 'LINK' => $APPLICATION->GetCurPage(false) . '?mobile'))

    First parameter is the menu type.

    Second parameter is an array describing the menu option.

    Highlighting of Links

    The field Additional links to highlight in an extended editing form that permits you to include the highlighting of certain menu options when going to the page. It may be necessary when a user must not forget where they came from or in order to draw attention to a certain page.

    When visiting a page indicated in the field Additional links to highlight, the appropriate menu option will be highlighted. The path to pages is set starting from the site root.

    How to Open a Menu Option in a New Window?

    The following additional parameters must be indicated for the appropriate options in the extended editing mode:

    Name: target 
    Value: target="_blank"
    

    The code must be replaced in the menu template after displaying the menu element:

    <a href="<?=$arItem["LINK"]?>"><?=$arItem["TEXT"]?></a>
    

    with:

    <a href="<?=$arItem["LINK"]?>" <?=$arItem["PARAMS"]["target"]?>><?=$arItem["TEXT"]?></a>
    

    Displaying a Menu Option for Certain User Groups

    Choose the Condition type equal to For user group for the targeted option in the extended edit mode and select groups that will see this option in the Condition itself.

    Condition type: For user groups
    Condition: required_user_groups
    

    Displaying a Menu Option to Unauthorized Users

    Set the following condition in the extended edit mode:

    Condition type: PHP expression
    Condition: !$USER->IsAuthorized()
    

    Displaying a Menu Option when Visiting a Certain Site Section

    The system permits you to display a menu option only when visiting a specified site section or its pages. For this, select the option For file or folder in the field Condition type in the extended edit mode and specify the path in the Condition field.

    Note: The condition For file or folder should apply for static pages. It will not work on dynamic pages because it verifies the address and the dynamic pages always include selected values. For a dynamic URL, the use of the URL parameters condition is recommended.

    Displaying Certain Options Only on the Home Page and in an Internal Section

    Introduce the following php expression in the Condition field for the required options in extended mode:

    CSite::InDir('/about/') or CSite::InDir('/index.php')

    Displaying a Menu Option Referring to a Specific Product Connected with the Product Opened on This Page

    The URL parameters condition type may be used for this. The option will be displayed on pages with a defined parameter in a URL. The parameter works with a URL containing the symbol «?». I.e., with dynamic pages.

    The pages created on the basis of infoblocks have a URL of the following type: http://site/section/index.php?SECTION_ID=***. Let us assume that on the page with SECTION_ID=123 a menu option leading to the page SECTION_ID=456 must be displayed.

    We create a menu option leading to the page http://site/section/index.php?SECTION_ID=456. Let us select the URL parameter in the field Condition type and specify SECTION_ID in the first field and 123 in the second field.

    Pop Up Tips for Menu Options

    In the extended edit mode, add the additional parameter of A_TITLE and write the contents of the pop up tip there.

    Name: A_TITLE
    Value: pop_up_tip
    

    In the menu template:

    <?if($arItem["SELECTED"]):?>
    		<li><a href="<?=$arItem["LINK"]?>" class="selected"><?=$arItem["TEXT"]?></a></li>
    	<?else:?>
    		<li><a href="<?=$arItem["LINK"]?>"><?=$arItem["TEXT"]?></a></li>
    <?endif?>
    

    the first link in the code must be replaced with the line:

    <a href="<?=$arItem["LINK"]?>" class="selected" title="<?=$arItem["PARAMS"]["A_TITLE"]?>"><?=$arItem["TEXT"]?></a>
    

    and the second with:

    <a href="<?=$arItem["LINK"]?>" title="<?=$arItem["PARAMS"]["A_TITLE"]?>"><?=$arItem["TEXT"]?></a>
    

    How to Put Images Close to Menu Options?

    Add an additional parameter into the menu (menu editing in the extended mode), e.g., IMG, and write the address of the image that you want to display close to this option.

    Name: IMG
    Value: path_to_image
    

    In the following display of a menu element after the line (depending on the template):

    <a href="<?=$arItem["LINK"]?>">
    

    the following must be added in the menu template:

    <img src="<?=$arItem["PARAMS"]["IMG"]?>" border="0" />
    

    Different Menu Option Images for Different Languages

    The menu consists of image options set by CSS classes. The site is bilingual. The menu structure itself is already separated and connected; the external appearance must be separated.

    Solution:

    Specify the following in the menu template:

    <body class="lang-<?=LANG?>"> 
    

    in CSS:

    .menu li.item1 { 
    background-image:url(title-en.gif); 
    } 
    .lang-ru .menu li.item1 { 
    background-image:url(title-ru.gif); 
    }
    

    Displaying of Customized Menu Different from Template Menu for Specific Site Sections

    Such menu may be organized using a site template change (Control Panel > Settings > System settings > Websites > Websites, site edit, Template section, condition For file or folder). In addition, if the template is not complicated, verification may be set directly in the template code, and display the menus depending on a specific case:

    if($APPLICATION->GetCurPage() == "/index.php") {
    display of the menu for home page
    }
    else {
    display of the second menu
    }
    

    How to Keep Tree Menu Open at All Times?

    Solution:

    Take the standard template for tree menu and copy it into its template. After that, replace line 14 in the file template.php:

    <li class="close">
    

    with:

    <li>
    

    In this case, clicking the image will cause the unfolded options to hide.

    Having Opened a Page through a Tree Menu, How to Prevent the Menu from Rolling Up and Make It Show on Which Page You Are?

    Solution:

    Take the standard template for tree menu and copy it into its template. After that, replace line 14 in the file template.php:

    <li class="close">
    

    with:

    <li <?if (!$arItem["SELECTED"]):?>class="close"<?endif?>>
    

    Note: This solution works only up to the 2nd nesting level of the menu.

    The following code that must be introduced into the menu template file permits the menu from rolling up if the nesting level is higher than 2:

    ...
    <?if (!empty($arResult)):?>
    
    <?
    //analysis of open nodes of the tree
    $lastLevel = 0;
    $selected = false;
    
    foreach(array_reverse($arResult) as $arItem){
        if ($arItem["SELECTED"]) {
            $lastLevel = $arItem["DEPTH_LEVEL"];
            $selected = true;
        }
        if ($selected and $arItem["DEPTH_LEVEL"] < $lastLevel){
            $arResult[ $arItem["ITEM_INDEX"] ]["SELECTED"] = true;
            $lastLevel--;
        }
    }
    ?>
    
    <div class="menu-sitemap-tree">
    <ul>
    <?$previousLevel = 0;foreach($arResult as $arItem):?>
    ...
    

    Dropdown Menu with Inactive Options of the First Level

    The task is to ensure that after clicking an option of the main menu that has a dropdown menu of the second level, instead of going to that main menu option (this option must not be a link) the user goes to any of the second level options of the dropdown menu.

    If the main menu option has no second level menu, the user should go directly to that main menu option.

    To make it possible, all links must be eliminated from the menu display template code of the type:

    <?if ($arItem["DEPTH_LEVEL"] == 1):?>
             <a href="<?=$arItem["LINK"]?>" class="<?if ($arItem["SELECTED"]):?>root-item-selected<?else:?>root-item<?endif?>"><?=$arItem["TEXT"]?></a>
    

    The result is as follows:

    <?if ($arItem["DEPTH_LEVEL"] == 1):?>
             <li><?=$arItem["TEXT"]?></a>
    

    Hiding of a Side Menu According to the Page Properties

    Site template provides for the display of a side menu. This side menu must be hidden only on the pages with a specific property that cancels the display of a side menu.

    Solution:

    If the menu is located in the header, the function GetProperty may not be used because page properties are set after connection of the header. That is why the menu display may be “deferred” as follows:

    • Add the following code in the header where the menu is required:
    $APPLICATION->ShowProperty('menu');
    
    • In the page properties if the menu must be blocked:
    $APPLICATION->SetPageProperty('hide_menu', 'Y');
    
    • In the footer:
    if( 'Y' != $APPLICATION->GetPageProperty('hide_menu') ){
        ob_start();
        echo '<b>verification of the deferred menu!</b>';
        // ....here, the menu is displayed using a component or otherwise.... //
        $APPLICATION->SetPageProperty('menu', ob_get_clean() );
    }
    

    The menu itself is “displayed” in the footer if the hide_menu property value is not set to Y. It will not be actually displayed in the footer. Rather, it will go to the menu property in which the display can be “deferred” “higher” up the code using ShowProperty. If the menu is blocked, the property value of the menu will be empty, and the template will not display anything. If the menu is not blocked, the phrase “verification of the deferred menu!” will be displayed for this example (where $APPLICATION->ShowProperty('menu') is specified).

    Menu and cache

    Quote: I have ran out of the disk space completely. Took a look and it turned out, that the bitrix/managed_cache/MYSQL/menu folder’s size is 16Gb! There are 2 menu at the site: horizontal — pagewise navigation and vertical — surfing via the catalog sections.

    Menu cache depends on the page URL. If the are many pages, the cache can be quite large as well. In this case, the more effective solution would be to disable cache in the component menu.

    Navigation chain

    Navigation chain (breadcrumbs) is a sequential list of links to site sections and pages that shows the level of “immersion” of the current page into the site structure.

    A navigation chain helps to display the nesting level of the current page, site section or goods catalog, starting from the home page to the current document. Values indicated in the navigation chain can be specified for each section or document individually.

    Navigation chain provides visitors with tools for easy orientation on site. It allows going to the main site page or going up on one or more levels in site hierarchy.

    Click to enlarge

    This section contains description of template structure and data used for navigation chain building and showing. Also this section includes description of ways for navigation chain showing management.

    Managing Navigation Chain via the System Interface

    By default, the navigation chain point names are managed by the system through the section properties.

    Site section header is set in the service file.section.php located in the relevant section. The following variables may be used in this file:

    • $sSectionName — section header;
    • $sChainTemplate — absolute path to navigation chain template (this variable is used very rarely).

    Example of the file .section.php:

    <?
    $sSectionName = "About us";
    $sChainTemplate = $_SERVER["DOCUMENT_ROOT"]."/en/about/chain_template.php";
    ?>

    The name of the link to the site section in the navigation chain is specified using the field Section Name in the setup form of the section properties.

    The section properties setup form may be accessed as follows:

    • from public section using the «Folder properties» button, located on the administrative panel. This button opens Folder properties form for current site section;

    • from administrative section using the «Folder properties» button, located on the context panel of Site Explorer. This button opens Folder properties form for current folder.

    To modify an item of navigation chain edit the value in the Section Name field and save changes.

    Information: You can exclude a link to any site section from the navigation chain. To do it, delete this section title from the Section Name field.

    Managing navigation chain via the source code

    The AddChainItem() function allows adding additional items to the navigation chain. Both static and dynamic values can be used as the navigation chain item.

    <?
    //--- The first parameter of the function AddChainItem() is the name 
    //--- to be shown in the navigation chain; 
    //--- the second parameter is the link URL.
    //--- Parameter values can be both static and dynamic.
    //--- In this example, section name is a static value, while
    //--- the link is generated dynamically.
    $APPLICATION->AddChainItem("Product details", "catalog.php?BID=".$arIBlock["ID"]."&ID=".$arSection["ID"]);
    
    //--- The next example shows how to generate both parameters dynamically. 
    //--- Current name of the catalog section is used as the name.
    $APPLICATION->AddChainItem($arSection["NAME"], "catalog.php?BID=".$arIBlock["ID"]."&ID=".$arSection["ID"]);
    ?>

    To display the title of a current page in the navigation chain, call the function AddChainItem() in file footer.php, that is included after the main content is generated.

    <?$APPLICATION->AddChainItem($APPLICATION->GetTitle());?>

    You can set some of the navigation chain elements to be displayed with no link, as a common text (for example, display the current page title without link):

    This elements are creating by adding to the navigation chain template (file chain_template.php) the following code:

    if (strlen($LINK)>0)
     $sChainBody .= "<a href="".$LINK."" class='".$strclass."'>".$TITLE."</a>";
    else
     $sChainBody .= "<font class='".$strclass."'>".$TITLE."</font>";

    Some visual components are able to add to navigation chain the current page or news title, or, for example, catalog item name.

    For example, the «Catalog» sequentially adds a catalog sections names according to the catalog structure.

     

    Forum and forum themes names are added to the navigation chain the same way.

    In this case the navigation chain element name for the current page is defined directly in the document with use of the AddChainItem() function.

    Navigation chain show

    Navigation chain is displayed using special code in the site template that uses a deferred function template:

    <?
    $APPLICATION->ShowNavChain();
    ?>

    Function for navigation chain call can be used not only in site template, but in a page or any visual component code placed on a page. For example, this function is utilized in the Search component.

    Note: Trial version contains the additional navigation chain template for use with the Search component. It can be found in the default component template folder /bitrix/components/bitrix/search.page/templates/.default/chain_template.php.

    Navigation chain display may be turned off on certain pages or in a certain site section. Navigation chain display may also be managed using page (section) properties. Do the following:

    • On the setting page of the Site Explorer module, Website Parameters section, create a property for the pages Do not show navigation chain with the code not_show_nav_chain. This property is preinstalled in the system.

    • If the navigation chain must not be displayed on a certain page(s) of a section, set the value for this property as Y.

    • in page source code with use of the function SetPageProperty().

      <?
        require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
        $APPLICATION->SetPageProperty("keywords", "Bitrix, content management, web-solution, site");
        $APPLICATION->SetPageProperty("description", " Bitrix Site Manager – is a powerful Content Management Solution");
        $APPLICATION->SetPageProperty("NOT_SHOW_NAV_CHAIN", "Y");
        $APPLICATION->SetTitle("Bitrix Site Manager");  
      ?>
       

    Managing Navigation Chain Template

    Navigation chain is connected in the site design template using the component Breadcrumb (bitrix:breadcrumb). Any number of templates, i.e. designs, may be set for these components. All of them are stored in the component folder /bitrix/components/bitrix/breadcrumb/templates/<template name>/. All the new templates will be displayed in the component settings. Thus, a navigation chain design template may be set for each site template. The structure of the navigation chain display template is similar to the menu display template.

    Navigation chain and its design template are managed in the same way as when working with other 2.0 components. Using control button in the site edit mode you can easily access the component parameter change form or copy the component template and then edit it.

    Building Navigation Chain and Developing Its External Appearance:

    1. Navigation chain points are gathered starting from site root and ending with the current section. The file .section.php should be connected for each section. If this file has the variable of $sChainTemplate initiated, its value will be used as a path to the navigation chain template. While going through sections, each subsequent value of this variable overrides the previous one. Thus, the “deeper” the section is in the site section hierarchy, the more “important” its variable $sChainTemplate becomes.
    2. If the path to the template is not determined after points of the navigation chain have been collected, the existence of the file is checked:

      /bitrix/templates/current site template ID/chain_template.php

      If such a file exists, the path to it is adopted as the path to the navigation chain template. Otherwise, the default value is used:

      /bitrix/templates/.default/chain_template.php

    If navigation chain is displayed, the navigation chain template will be connected each time at the next point of the chain. That is why its main task is to provide for the external appearance of only one point of the chain.

    The main variables used in the template are as follows:

    • $sChainProlog — is the HTML code displayed before the navigation chain;
    • $sChainBody — is the HTML code which determines external appearance of one point of the navigation chain;
    • $sChainEpilog — is the HTML code displayed after the navigation chain;
    • $strChain — is the HTML code of the entire navigation chain collected by the time of template connection.

    All variables shown will store HTML code determining the external appearance of the navigation chain.

    In addition, the following additional variables will be available in the template:

    • $TITLE is a header of a point of the navigation chain;
    • $LINK is a link on a point of the navigation chain;
    • $arCHAIN a copy of array of elements of the navigation chain;
    • $arCHAIN_LINK a link to array of elements of the navigation chain;
    • $ITEM_COUNT the number of array elements of the navigation chain;
    • $ITEM_INDEX a sequential number of a point of the navigation chain.

    Example of the navigation chain component template:

    <?
    if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    if(empty($arResult))
      return "";
    
    $strReturn = '<ul class="breadcrumb-navigation">';
    for($index = 0, $itemSize = count($arResult); $index < $itemSize; $index++)
    {
       if($index > 0)
              $strReturn .= '<li><span>&nbsp;&gt;&nbsp;</span></li>';
       $title = htmlspecialcharsex($arResult[$index]["TITLE"]);
       if($arResult[$index]["LINK"] <> "")
              $strReturn .= '<li><a href="'.$arResult[$index]["LINK"].'" title="'.$title.'">'.$title.'</a></li>';
       else
              $strReturn .= '<li>'.$title.'</li>';
    }
    $strReturn .= '</ul>';
    return $strReturn;
    ?>

    Note: When connecting the navigation chain using the function ShowNavChain(), its template may be additionally set for a separate site section.

    To do so, the variable $sChainTemplate must be determined directly in the file .section.php where the complete path to the navigation chain display template is set. Example:

    $sChainTemplate = "/bitrix/templates/demo/chain_template.php"

    The navigation chain template may also be set when using the function ShowNavChain() as one of the parameters of the function.

    $APPLICATION->ShowNavChain("/bitrix/templates/.default/chain_template_bottom.php")

    Examples

    How to Add Own Point to the Navigation Chain?

    Use the method AddChainItem() :

    <?
    $APPLICATION->AddChainItem("About us", "/about/index.php");
    ?>
    

    Only infoblock names are shown in the navigation chain. In addition, page headers (e.g., contacts) and “Home” header are not displayed. What is the problem?

    The value N must be set in the page properties in the field NOT_SHOW_NAV_CHAIN of the page properties.

    For the remaining pages, it is necessary to check if the headers are set in the section properties. It is section headers that are used to create points of the navigation chain.

    The home page can also fail to display due to the incorrectly set option Ordinal of item from which to build the chain in the parameters of the Breadcrumb component: 0 (default value) means that the navigation chain will be built starting from the site root. If the field Path for which the navigation chain is to be built is completed, the point number is included in the indicated path.

    The navigation chain has the catalog name repeated twice: Home/Catalog/Dresses/Dresses.

    The first link to the catalog looks as follows: /catalog/dresses/

    and the second:
    /catalog/dresses/section.php?SECTION_ID=0

    The first name is taken from the properties of the dresses directory (file .section.php) and the second is determined by the component located on the page (in this case, the address of the page section.php).

    Note: For example, the parameters of the News (bitrix:news) component contain the relevant options: Include information block into navigation chain and Add Section name to breadcrumb navigation.

    The element repetition in the navigation chain can also be caused by the presence of several components on the pages that are set to add their points to the chain.

    How to ensure that the navigation chain has only a physical section present?

    The complex component News is used to display a section from the infoblock. Apart from the physical section, an unclickable name of the section of the infoblock proper appears in the navigation chain. Neither section nor infoblock is included in the navigation chain settings.

    Add the following line in the template:

    "ADD_SECTIONS_CHAIN" => $arParams["ADD_SECTIONS_CHAIN"],

    Advertisement

    Several different types of advertising may be shown on the site. It may be both standard banner advertising or written advertising areas. Advertising may be permanent or shown with a certain probability set by the administrator. An advertising show may be regulated by specific site sections etc. Advertising is created and managed using tools of the Advertising module.

    Advertising is shown in specifically allocated areas of the site design template – advertising areas.

    A banner may be connected to the advertising area using the Banner component (bitrix:advertising.banner). The component displays the banner of a set type.

    Note: This component does not take into account the targeting by keywords. If the targeting must be taken into account, the function $APPLICATION->ShowBanner() should be used. The code used to connect a banner in the advertising area using PHP function of ShowBanner() is as follows:

    <?
    //--- Example of placing an advetising area in the left part of the design. 
    //--- Any other type can be selected similarly:
    //--- TOP, BOTTOM, COUNTER,… (first parameter of the function)
    //--- Both predefined and user-defined types can be used. 
    //--- Other two optional parameters can be used to specify the HTML code 
    //--- that is to wrap the advertising area.
    
    $APPLICATION->ShowBanner("LEFT", '<div align="center">', '<br></div><br>');
    ?>

    The type of advertising available for display in this area must be determined for each advertising area. Advertising banners of the type LEFT1, LEFT2, and TOP are used on the image shown above.

    Banner types

    Advertisement type is a conventional name for a group of advertising banners that share a common feature. For example:

    • Show place – all banners must be displayed in a specific place on the site page.
    • Subject matter (for example, the same products are advertised).
    • Advertiser (advertising for a specific company).
    • etc.

    The name of the advertising block type is set at the discretion of the administrator. For example, the TOP, LEFT, BOTTOM, etc. type may be set for the advertising set at the top of the page.

    Important! Banners from the same group should have the same size. It will permit you to prevent page deformation during display of the advertising.

    Advertisement types are managed through the administrative interface of the Advertising module (Control Panel > Services > Advertising > Banner types):

    An advertising banner or a list of banners of a selected advertising area can be managed directly from the public part of the site. For this, switch to the Edit mode and use one of the advertising area control buttons:

    Controlling advertising shows using keywords

    One of the methods that can be applied to control the banner shows and to target the advertising precisely is using keywords. The distinctive advantage of this method is that it allows to drive the advertising campaign aimed to reach the well-defined target group among your visitors.

    Using keywords, it is possible to:

    • Organize the display of advertising aimed at a specific group of site users. I.e. display advertising on the pages visited primarily by these users or pages that the subject matter may be of interest for this group of users.
    • Restrict the advertising shown on a site page, for example, to the extent the advertising content is connected to the information shown on the page.

    Control Mechanism

    Advertising on the site pages is controlled using desired and mandatory keywords of the site page and a set of keywords of the advertising banner. Two types of special keywords are used to manage the advertising shown on pages:

    • banner keywords;
    • page keywords:
      • desired: if a site page is assigned the desired keywords, all the banners that have at least one matching keyword in their keyword sets can be shown on that page.

        If no banners with the matching keywords can be found, then the page will show banners that are not assigned any keywords. In this situation, the system uses own standard algorithm to select banners to be displayed.

      • required: if a site page is assigned the required keywords, all the banners that have all keywords in their keyword sets can be shown on that page.

        If no such banners can be found, then the page will show banners that are not assigned any keywords. In this situation, the system uses own standard algorithm to select banners to be displayed.

    If the system fails to find banners satisfying any of the keywords, the page will show banners for which no keywords are set. These banners are selected and displayed based on a standard system algorithm (permitted/forbidden pages of contracts and banners, user groups, banner types, etc.).

    The general procedure for banner display on specific pages is as follows:

    • Determine the advertising available for display on specific site pages.
    • According to the tasks at hand, sets of specific keywords are determined for site pages.
    • A required set of keywords is set in the settings of advertising banners.

    Banner keywords are set in the Keywords field on the page of banner creation/editing, Targeting bookmark (Control Panel > Services > Advertising > Banners).

    Desired keywords of the page are managed using a special property adv_desired_target_keywords. Desired keywords of the page may be set using the function of SetDesiredKeywords.

    Please note! By default, if page code does not provide for any keywords for advertising, the function of SetDesiredKeywords is deemed using properties of the page adv_desired_target_keywords as a value parameter. If it is not set, the value of the keywords property is used as a function parameter.

    The SetDesiredKeywords method is called automatically at the time of page assembly; it must not be additionally called in the file header.php if there is no need to redefine the keywords for the advertising shown.

    The required keywords of the page are set using a preset function of SetRequiredKeywords of the system.

    Managing Template Service Data

    Editing Service Areas

    If a visual editor is used for creating (editing) a template, the service areas can be managed in a special form. This form can be accessed using the button Edit template areas located in the editor’s panel.

    Area editing form consists of two bookmarks: Top Area and Bottom Area.

    The top part of the template down to the <body> tag is edited in the first bookmark. You can set the contents of the area with default values. To do so, press the button to insert a set of standard functions into the form, such as page encoding, header and page metadata display, connection of style files, etc. Similarly, the bottom part of the template is edited in the Low part bookmark in which the contents can also be set using the default values.

    Attention! The use of the functions of ShowMeta(), ShowTitle(), ShowCSS() etc. permits you to initialize separate elements directly from a page script or from a component. E.g., page header may be added after displaying script output. Thus, if in earlier version’s page header initialization was required prior to connecting the main design, now the page header can be set directly from code in the page working area.

    Managing Page Encoding

    Support of any number of languages is one of important features of Bitrix Framework. The system permits:

    • Using a multi-language interface in the administrative section.
    • Creating any number of sites (depending on the license in different languages within one system.

    Note: The number of languages used in the system does not depend on the number of sites.

    This section contains information about the use of encoding for the correct display of information on the site pages. Having studied the section you will get an idea about the main principles of use and also about the ways to setup and connect different encodings.

    Use of Encodings

    In order to display national characters correctly, the appropriate encodings are used. When showing a page, the browser recognizes the encoding used and displays the characters accordingly.

    The list of code tables used to display the characters of the English, and German languages is provided below:

    Language Encoding
    English (en) windows-1252, iso-8859-1, latin1
    German (de) windows-1252, iso-8859-1, latin1

    The complete list of encodings used for different languages is provided in the product documentation.

    Note: Starting from version 7.0 the product supports a universal UTF-8 encoding. Using this encoding, the site contents may be simultaneously displayed in different languages.

    If UTF-8 is not used but the system requires a combination of various languages, a code table must be determined for each language that will be used to display text messages.

    Attention! Page encoding and database table encoding must be the same.

    Encoding shall be set separately for the administrative and public sections:

    • The encoding used in the public section shall be set up for each site (Control Panel > Settings > System settings > Websites > Websites):

      The encoding is set based on the language used on the site. In addition, when setting the language parameters, the time and data format can be set, which will permit you to display these data in the public section correctly (for example, during display of news, catalog of goods, etc.).

    • The encoding for the administrative section of the site is set up using the form of language parameters used in the system (Control Panel > Settings > System settings > Language Parameters > Regional Options and Culture).

      In addition, the time and data format may be determined when setting the language parameters.

      The indicated format will be used for the display of the date and time in the administrative section of the site.

    Determining the Current Encoding

    The current encoding used in the public section of the site is determined using the php constant LANG_CHARSET substituted to the site template header area.

    When applying a template to the site, the value of encoding parameter set in the site settings is requested. The constant LANG_CHARSET is given a value equal to the value of the encoding parameter.

    Example of the code used to establish page encoding is provided below:

    < head >
    …
    < meta http-equiv="Content-Type" content="text/html; charset=< ?echo LANG_CHARSET? >" >
    …
    < head > 

    Managing Document Header

    The use of header helps to draw users’ attention to a site page and also to give a general idea about its contents and purpose. Additional page headers should be set to reflect as a header of the browser window so that the user can accurately determine the window where the page they need is open.

    Page header and web browser window may be created and changed both using the tools of the administrative and public section.

    Note: Additional header for web browser window can be set using the title property reserved in the product.

    For convenient use of the property its name must be set (for example, Additional header (web browser window header)) in the Site Explorer module setting.

    Please refer to the page Work examples for more information about setting up several headers.

    Note: Some components may independently set a header and it should be taken into account.

    Managing Header in the Code

    Setting up and Displaying Page Header

    Page header may be set up as follows.

    • When editing the document with the help of the incorporated editor (in the Text, PHP, or HTML mode). In this case, the page header is set by way of substituting the following function in the code:

      <?
      $APPLICATION->SetTitle("About Company");
      ?>

    • Document header can be set dynamically by one of the components located on the page. In the example below, the name of the current information block is used as a page header:

      <?
      $arIBlock = GetIBlock($ID, "news")
      $APPLICATION->SetTitle($arIBlock["NAME"]);
      …
      ?>

    The document header is displayed by placing the function of ShowTitle() at the place of page header display:

    <H1><?$APPLICATION->ShowTitle()?></H1>

    If the function of ShowTitle() uses the parameter false for page header display, it means that there is no need to check the value of the title property to set the page header (e.g., Additional header (web browser window header)).

    <H1><?$APPLICATION->ShowTitle(false)?></H1>
    

    I.e. the header set by the SetTitle() function will be used as a page header.

    Note: For more information about the ShowTitle(false) function, please refer to the page Work examples.

    Set Up and Display of Web Browser Window Header

    Web browser window header is displayed using the ShowTitle() code located in the <head> area of the site design template.

    <head><title><?$APPLICATION->ShowTitle()?></title></head>

    There are various ways to set web browser window header. By default, the header is set in the title property of the page (e.g., Additional header (web browser window header)). If the value of this property is not indicated the browser window header will be set as equal to the current page header.

    Note: Browser window header can also be set using the function of SetPageProperty() or in the public part of the site. For more details, please refer to the page Work examples.

    Attention! If several identical header setting functions or components are located on the page, the header set in the last (the bottommost on the page) function/component will be used.

    Work Examples

    In this lesson we will review, stage by stage, the procedure for creating a page with different headers of the browser window and the page itself. Site template may require changes during this work.

    • Set a page header from the interface or using the function of $APPLICATION->SetTitle(“Page title”):
      <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
      $APPLICATION->SetTitle("Page title"); ?>
      ....
      <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php"); ?>
      

    • Add additional header through interface or using the function of $APPLICATION->SetPageProperty('title','Browser title'):
      <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
      $APPLICATION->SetTitle("Page title"); 
      $APPLICATION->SetPageProperty('title','Browser title'); ?>
      ...
      <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php"); ?>
      

      The function of $APPLICATION->SetPageProperty() in the system has a priority over the function of $APPLICATION->SetTitle(). That is why the browser and page headers will display the contents of the function exactly:

    • If different headers are to be used for page and web browser window, check the parameters of the CMain::ShowTitle() method displaying page header in the site template.

      Replace:

      $APPLICATION->ShowTitle()

      with:

      $APPLICATION->ShowTitle(false)

      In this case, the value of page property SetPageProperty('title','Browser title') will be ignored, and the header set by the function of SetTitle() will be used as a page header instead.

      Let us review the difference in operation of the function $APPLICATION->ShowTitle() with the false parameter using the following example, without changing the template code:

      <? require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
      $APPLICATION->SetTitle("Page title"); 
      $APPLICATION->SetPageProperty('title','Browser title'); 
      $APPLICATION->ShowTitle(); 
      <br>
      $APPLICATION->ShowTitle(false); ?>
      ....
      <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>
      

    Managing Metadata Values

    As a rule, the main purpose of using metadata is the optimization of site for search systems. Search systems use metadata for indexation of site documents.

    Keyword mechanism and description of site pages and sections are good examples of product metadata management. By default, the installation package of the product includes the management of exactly these two types of metadata (similarly, the list of possible options may be extended).

    Managing Metadata Values through Visual Interface

    In order to have a possibility to manage metadata values, the relevant properties must be created in the settings of the Site Explorer module (Control Panel > Settings > System settings > Module settings):

    Important! The names of property types used for managing page metadata must coincide with the names of meta tags in HTML. E.g., keywords and description property types must be the same as names (name) of the relevant meta tags: keywords and description.

    Note: A set of properties can be set separately for each site that works under control of the system.

    Attention! The values of properties set for a folder will be used by default for all pages and subsections of the relevant site section (if no proper values of these properties are set for them).

    More detailed information about properties management is available in the Properties of pages and folders section.

    Managing Metadata in a Code

    In order to display the metadata values in the page code the function of ShowMeta() located in the prologue of the site design template must be used:

    < head >
    …
    < ?$APPLICATION->ShowMeta("keywords")?>
    < ?$APPLICATION->ShowMeta("description")?>
    …
    </head>

    Let us assume that the following values are set for keywords and description properties of a page:

    The function of SetPageProperty() will apply the values of these properties to a page:

    <?
    $APPLICATION->SetPageProperty("keywords", "sofas, chairs, office furniture, kitchen furniture, nursery furniture");
    $APPLICATION->SetPageProperty("description", "We use only the high quality equipment to manufacture our furniture to achieve the superior quality.");
    ?>

    Note: Section properties can be set up using the function of SetDirProperty() (e.g., in the code of the file .section.php):

    < ?
    …
    $APPLICATION->SetDirProperty("keywords", "design, web, site");
    
    $APPLICATION->SetDirProperty("description", "Site Manager System"); … ?>

    In this case, the following HTML code will be introduced into the page code as a result of the function ShowMeta().

    <meta name="keywords" content="design, web, site">
    <meta name="description" content="Site Manager System">

    If no property value is set for the page itself, the value of the property of a higher section (recursively to the site root) will be selected. If the property value is not determined, the value of the relevant meta tag will remain undetermined. Page properties may be set dynamically from the code of the components located on the page. For example, for pages showing catalog or news information, the page properties may be set in accordance with determined properties of infoblock elements:

     $APPLICATION->SetPageProperty("description",$arIBlockElement["PROPERTIES"][$META_DESCRIPTION]["VALUE"]);

    In this case, the value of the property of an information block element with meta_description code will be used as a value for the description page property. Thus, the properties of keywords and description may be created for catalog elements and dynamically substituted to the page code.

    CSS management

    Generally CSS is a group of rules defining layout of some page elements. The CSS technology allows to store all information about page layout, utilized fonts and colors, menu layout styles and etc. in one or several exact files.

    Using the CSS simplifies the page designing. Moreover, if you change a site design you do not need to modify each site page. It may be enough to make necessary modifications in corresponding CSS files.

    For example, using the CSS you can modify a forum layout (in this example forum CSS files are stored separately from the site template CSS).


    .forumborder {background-color:#B8B8B8;}
    .forumhead {background-color:#DDDDDD;}
    .forumbody {background-color:#F5F5F5;}
    .forumbodytext {
    	font-family: Tahoma, Arial, Verdana, sans-serif;
    	font-size: 80%;
    	color:#000000;
    }
    .forumheadtext {
    	font-family: Tahoma, Arial, Verdana, sans-serif;
    	font-size: 80%;
    	color:#011B46;
    …


    .forumborder {background-color:#96C0FA;}
    .forumhead {background-color:#A9CAF7;}
    .forumbody {background-color:#D7E6FB;}
    .forumbodytext {
    	font-family: Tahoma, Arial, Verdana, sans-serif;
    	font-size: 80%;
    	color:#042A69;
    }
    .forumheadtext {
    	font-family: Tahoma, Arial, Verdana, sans-serif;
    	font-size: 80%;
    	color:#011B46;
    …

    Stylesheets are uniquely customized for each site template in the system; each stylesheet set is stored in the folder of the corresponding template, as well as other files comprising the interface of the template. Stylesheets used in site templates are stored in the styles.css files. Additional stylesheets used for exact site elements (for example, for forum layout) a stored in files with such names as forum.css.

    Separate CSS files storage

    Bitrix software products features mechanism that allows to perform a separate storage of CCS files:

    • Styles, used in the site template are stored separately in the template_styles.css. file. It is the main CSS file of a template.
    • Styles, used when customizing the page content (site styles) are stored in the styles.css file. Styles that are stored in this file are displayed in the styles drop-down list when editing pages in the visual editor.

    Designation of these files can be divided arbitrarily, depending on the context. Specifically, the styles that are responsible for content design not only for a template, but for the visual editor, can be stored in the styles.css file.

    For example: corresponding display styles are defined for all headers on the site, i. e. they are used both to define page content and design of information block content, that is located outside the #WORK_AREA#. As a result, these header styles can be placed in both the template_styles.css and styles.css file. But because both of these files are connected to the template, all the header design styles must be placed only into the styles.css file, because it is connected to the page as well.

    If headers must be modified when editing a page in the visual editor, then correspondingly the styles that are responsible for the site template design must be placed in the template_styles.css file, and for the visual editor — into the styles.css file.

    Splitting styles between those two files must be done carefully. For example, the site background must be done in grey color, and the background color in the visual editor must be red — the background-color:#ccc; must be specified for the body tag inside the template_styles.css file; and for the same tag — background-color:#ff0000; must be defined in the styles.css file.

    Files stored on the page site are connected as follows:

    1. styles.css
    2. template_styles.css

    As a result, the body background will become grey at the site, because the style in the last connected template_styles.css file will supersede the style, defined in the styles.css file. The background in the visual editor will become red, because the visual editor content is the iframe that connects styles only from the styles.css file, and they are inserted directly into the head area via the <style> tag.

    If to add the increase of priority via !important to the styles.css file, the style from this file will supersede the style, defined in the template_styles.css; the site background will become red as well, despite the fact that the file of template styles is connected last.

    Site styles

    The site stylesheet (the styles.css file) is customized at the design template edit page (Control Panel > Settings > System settings > Websites > Site templates) at the Site Styles tab. The important element when creating the stylesheet is the style name. The names must be created only for those styles that are planned to be used when editing pages in the visual HTML editor ((Format) section).

    The styles will be available in the visual editor from the drop-down list under the names, defined in this form. The name that are specified here, will be stored in the /.styles.php file (file with style names).

    Creation of design template stylesheet (the template_styles.css file) is performed in the Template styles tab of the site template edit form

    Managing CCS styles in the visual HTML editor

    Style names are important elements when customizing a stylesheet. Style names can be added directly into the .styles.php file. This file located in the site template folder and has the following format:

    <?
    $arStyles = Array(
      "information-block-head" => "Information block header",
     "content-block-head" => "Header with padding",
     "information-block-body" => "Information block element text",
     "tablehead" => "Table - header",
     "tablebody" => "Table - body",
    );
    return $arStyles;
    ?>

    Additionally, the style names can be added in the template edit form at the Site styles tab. In this case, the .styles.php file is created automatically.

    Style names will be displayed in the drop-down list in the visual editor.

    If the Permit the display of styles without names in the HTML visual editor option is selected the Visual editor tab in the module Structure configuration settings form, then all available styles for the page will be presented in the list, including those styles, for which the names are not specified. The name, defined when creating the style: information-block, content-block and etc. will be used to designate the style with unspecified name.

    Also, the styles, applicable to specific html-elements of the page (for examples, table borders, cells, images and etc.) can be created in the stylesheet. When selecting such elements, the list will show the styles available for them.

    Example of Site Template Stylesheet

    Styles for left menu:

    .leftmenu, .leftmenuact {font-family: Arial, Helvetica, sans-serif;
     font-size:12px; font-weight: bold; text-decoration: none;}
    .leftmenu {color: #3F3F3F;}
    .leftmenuact  {color:#FF5235;}

    Styles for top menu:

    .topmenu  {font-family: Arial, Helvetica, sans-serif; font-size:12px;
     font-weight: bold; color: #FFFFFF; text-decoration: none;}
    .topmenuact  {font-family: Arial, Helvetica, sans-serif; font-size:12px; 
     font-weight: bold; color: #FAC535; text-decoration: none;}

    Styles for table:

    .tableborder  {background-color:#9C9A9C;}
    .tablehead  {background-color:#D8D9DA;}
    .tablebody  {background-color:#F8F8F8;}
    .tablebodytext, .tableheadtext {font-family: Arial, Helvetica, sans-serif;
     font-size:12px;}
    .tablebodytext {color:#000000;}
    .tableheadtext {color:#000066;}

    Connecting CSS

    Stylesheets are connected to the site template in the prolog area via the ShowCSS() function.

    <?
    $APPLICATION->ShowCSS(); 
    ?>

    The ShowCSS() function connects CSS file from the current site template, as well as all additional styles, defined for the this page by the function SetAdditionalCSS().

    <?	
    $APPLICATION->SetAdditionalCSS("/bitrix/templates/demo/additional.css");
    ?>

    Additional styles can be used, for example, to customize design elements of forum, web forms, tables, certain menu types, etc.

    When using the ShowCSS() function without parameters, styles will be connected as the link to the CSS file:

    <LINK href="/bitrix/templates/demo/styles.css" type="text/css" rel="STYLESHEET">

    The styles connected via the SetAdditionalCSS(), will be included into the page code using the PHP function require() (i. e. such styles will be fully included into the final page code).

    When using the ShowCSS() function with the parameter false, the CCS file for the current design will be included into the page code via the require():

    <?
    $APPLICATION->ShowCSS(false);
    ?>

    Setup of External Appearance of Additional Elements of the Site Design

    Setup of Error Messages

    It is often necessary to setup error messages to have an error message clearly shown in the site design.

    In order to set up the design of a database connection error message, the file /bitrix/php_interface/dbconn_error.php should be edited.

    In order to set up the design of a database query error message, the file /bitrix/php_interface/dbquery_error.php should be edited.

    Setup of a File to be Connected when Closing the Site

    In order to set up the design of a file to be connected when closing the public part of the site the file /bitrix/modules/main/include/site_closed.php should be copied and placed into /bitrix/php_interface/<language>/ or into /bitrix/php_interface/include/.

    Setup of a Page by Page Navigation Design

    A page by page information display is organized using the PHP function of NavPrint(), a function for displaying links for a page by page navigation. The following parameters may be used to manage a page by page navigation design: NavPrint($title, $show_allways=false, $StyleText="text", $template_path) where:

    $title – is the name of displayed elements;

    $show_allways – if the value of the parameter is false, the function will not display navigation links should all entries fit on one page. If true, the links for a page by page navigation will always be displayed;

    $StyleText – CSS class of a font to display navigation links;

    $template_path – a path to the template for showing navigation links.

    Information Blocks

    Information Blocks — is a module that permits making catalogs and managing different types (blocks) of homogeneous information. Different types of transient content may be posted using information blocks: goods catalogs, news blocks, reference books, etc.

    Information blocks is a key point of Bitrix Framework. Almost everything that is being done in the system is more or less connected with this module, even if it is not displayed directly.

    Information blocks constitute the next level of abstraction over DBMS regular tables; it is a kind of «database within a database»; That is why all of the rules used during database design partially apply to information blocks.

    Infoblocks is an entity creating 4 tables in the database’s physical structure. These 4 tables remain unchanged during changes of data structure – object types, object instances, object properties, and object property values.

    This approach has the following advantages:

    • Convenient control over the structure data from the data application,
    • Versatility of methods,
    • Common data structure for any project,
    • Possibility to change data types for fields many times without eliminating data themselves.

    Disadvantages:

    • Higher performance requirements,
    • Nontransparent direct data access.

    Specifics of the Arrangement of Elements by Sections

    Arrangement of the elements of infoblocks by sections may greatly facilitate the navigation around an infoblock in the administrative interface. Faceted arrangement makes navigation even more convenient.

    In addition, an infoblock with sections already contains a description of how it should be displayed to the user. I.e. it is sufficient to bypass it using the very common tree-avoidance algorithm in order to display it in the most convenient form.

    The work specifics consist in a certain inconvenience when it comes to working precisely with sections, one by one. Thus, for example, if in addition to the Articles infoblock there is a Books infoblock, it is highly possible that its elements will also require classification by publication date and by subjects. In this case, you will have to create the same section structure once again. Furthermore, it will not be very easy to show, for example, the list of all of the materials (articles and books) on the same subjects by arranging them by publishing date. Moreover, it will be difficult to display the common index of subjects in the website menu.

    In this case, a separate infoblock Subjects should be formed adding a reference type property Subject to the Books and Articles infoblocks, and a Publication Date property of the Date type. Then, it will be easier to navigate in the administrative interface using these property filters.

    Working with Infoblocks Using Standard Means

    Work Order

    When creating a web section using information blocks, a certain work order should be followed. This order may differ depending on the degree of project readiness and a complexity of a specific assignment. Almost in every case you will have to:

    • Think through the entire structure of infoblocks carefully.
    • Create the necessary infoblock types and set up their parameters.
    • Create infoblocks and set up their parameters.
    • Create structure inside an infoblock.
    • Create infoblock elements.
    • Create a physical page (in case a complex component is used) or pages (if simple components are used), locate a component (components) on it, and then set up its parameters.
    • Adjust the component operation according to the assignment and website requirements (component template customization, use of the result_modifier.php or component_epilog, customization of the component itself).

    Standard Capabilities

    Built-in tools of the Information Blocks module are extensive enough. There is no limit either to infoblock types, infoblock number, number of properties of each infoblock, or number of sections or elements.

    Infoblock Properties

    Elements of each infoblock have a set of system properties that may be extended with user properties. The infoblock properties differ according to their types:

    • String — the property value is set as a text line;
    • Number — the property value is set as a number;
    • List — the property value is selected from the list;
    • File — a file is used as the property value;
    • Link to section — using this property it is possible to link an element of this infoblock and sections of another information block;
    • Link to elements — setting links among information block elements one by one;
    • HTML/text — the property value is set as a text with HTML tags;
    • Link to elements by XML_ID — the link is stored as a string, and the value is XML_ID of the linked element;
    • Bind to Google Maps — setting a link between an infoblock element and a Google Map component;
    • Bind to Yandex Maps — setting a link between an infoblock element and a Yandex.Map component;
    • Counter — is a substitute to autoincrement for databases. The value will increase by one each time an infoblock element is added. The starting value is set at random. This feature can be used for incoming document registers etc. where the continuous numbering of documents is needed.
    • Link to user — a link between an element of this infoblock and system users can be set using this property
    • Date/Time — the property value is set as a date/time;
    • Video — setting a link between a list element and a media file;
    • Link to elements (drop-down list) — setting a link between elements using a list;
    • Link to forum topic — using this property, a link can be set between an element of this infoblock and forum subjects;
    • Link to file (on server) — using this property a link can be set between an infoblock element and a file on a remote server;
    • Link to elements autocomplete text box — setting a link to autocomplete elements;
    • Link to product items (SKU) — setting a link to products (SKU).

    Each type of properties has its own set of parameters established in relevant forms. Properties may be multiple and of mandatory completion.

    Note. It is recommended that only the characteristics that are to be used as filters should be converted into separate properties. The remaining characteristics should be incorporated in product description as text.

    Properties of Infoblock Sections

    It is possible to set the user properties for infoblock sections. The code of user fields must always contain the prefix UF_. The list of field types is somewhat smaller than that for the infoblock proper:

    • Video
    • Text
    • Integer
    • Number
    • Date/Time
    • True/False
    • File
    • List
    • Link to information block sections
    • Bind To Information Block Elements
    • Template
    • Poll

    Similarly to the properties of the infoblock itself, section properties may be multiple and mandatory. In addition, it is possible to establish whether the property will participate in search and sorting, whether the user can edit the property value, and whether the property will be displayed in the general list of properties.

    Export-Import

    Adding a big number of infoblock elements manually is a very laborious task
    Data import/export can be applied using different file formats in order to make the information adding process easier. The following formats are supported:

    • RSS
    • CSV
    • XML

    Export and import in RSS format are arranged using the special components RSS news (export) (bitrix:rss.out) and RSS news (import) (bitrix:rss.show), accordingly.

    Data from the infoblock are exported to CSV file using the form Information block export (Control Panel > Content > Information blocks > Export > CSV). Data stored in a separate CSV file, are imported to the information block using the form of Information block import (Control Panel > Content > Information blocks > Import > CSV).

    Note: Starting from module version 14.0.5, the section nesting depth for CSV export/import is determined by the settings of the Information Blocks module.

    In earlier versions, export to CSV was limited to three nesting levels.

    Note: If an infoblock is to be exported as a product catalog, the following path should be used: Control Panel > e-Store > Settings > Data export. In addition, import from a CSV file is possible: as a product catalog. In this case, it is necessary to use the following path: Control Panel > e-Store > Settings > Data import.

    The functional capacity of the infoblock import/export feature permits moving to and from XML not only the contents of the infoblocks but also their properties and images. Export can be made at the page Export to XML (Control Panel > Content > Information blocks > Export > XML). Import can be made at the page XML Import (Control Panel > Content > Information blocks > Import > XML).

    Setting Up Fields of the Element Adding Form

    The task of creating a large number of elements of an information block can be made easy by using the presettings of the fields of the element adding form. The settings are made in the Administrative section using the tab Fields of the infoblock editing form (Control Panel > Content > Information blocks > Information block types).

    Note: The tab Fields permits you to establish preset data for the infoblock element fields.

    This form has three columns:

    • Element field – a field to which the setting will apply
    • Active – a checkbox means that this field is mandatory. The element will not be created if the relevant checkbox is not checked.

      Note: There are fields that are mandatory according to the system requirements. These fields cannot be checked or unchecked.

    • Default value – defines a default value to be acquired by this field. If any line in this column is empty, it means that this field cannot be given a default value.

    Let us take a closer look at certain fields:

    The form type for this field that will open by default shall be set in the fields Description type type (for a preview text and a detailed description): a text field or a visual editor. You have to decide what type of field suits you better.
    For a preview text, the – text description type is recommended; and for a detailed description – html. It permits using text with html tags when importing data from a CSV file. In this case, the description will be imported with an already set format.

    Fields in the Default value can be used as a prompt for the content manager about the data that should be introduced in the field. For example, a text can be entered in the fields Preview text and Detailed description to help the content manager to complete forms. Let us suppose that the comment for the preview text will be: «Introduce a summary of the piece of news», and for the detailed description: «Introduce full text of the piece of news».

    Note: Similarly, the tab Section Fields can be set up.

    System Processing of Photographs

    As a rule, several pictures are used on websites for an infoblock element: a small preview and a big picture. If multiple elements must be created, normally it takes a lot of time to create small pictures from big ones. However, it is actually not necessary. Just upload one big picture, and indicate in the Fields tab the way you would like it to be processed.

    Let us set up the infoblock operation in such a way that when we upload one big picture we obtain one small and one big picture, both of a preset size and with a preset quality.

    • Check the box Auto-create preview image from detail image. Now, the preview picture will be created from the big picture.
    • Check the box Auto-resize large images. The picture will be scaled down to the size set in the fields below.
    • Enter the picture size in pixels in the fields Maximum width and Maximum height. Picture size will be change proportionally. Setting the size in these fields guarantees that a preview picture will not accidentally damage the page layout.
    • Check the box Ignore scaling error, to display the picture “as is” in case image processing fails.

      Note: The preset image resizing mechanism works with traditional graphic formats: jpg, gif and png. If you upload a picture in a different format, for example bmp, it will not be processed. The system will either display the picture in its original size, or show a message that the picture cannot be processed. It depends on the checkbox Ignore scaling error as to which of these two options will occur.

    In order to set up processing quality, you can also use the options of the Fields tab. Please note that free server resources are required to use these fields.

    • Check the box Preserve quality when scaling.
    • Set an acceptable quality level in percentage in the field Quality.

    Enter similar settings in the Detailed image fields in order to set up a picture shown in detailed view.

    Setting Up the Element Adding Form Display

    The form for editing/adding an infoblock element can be changed and set up according to the requirements of the website content manager. The changed form is displayed and works both for adding/editing an element to the administrative part and for adding/editing an element to the public part.

    Note: The form can be set up only from the administrative part of the website.

    Follow the steps below in order to set up the form:

    • Go to the page containing the list of the infoblock elements for which a form should be set up.
    • Open for editing any element of the infoblock and click Settings .

    • The window Customize form appearance will open up:

    This form permits you to rename and change the sequence order both for fields and tabs of the element editing form. You can also move fields not only within one tab but also from one tab to another, thus forming a visual appearance of the element adding/editing form which is convenient to you.

    Set up tabs and fields as necessary:

    • In the list of Tabs (Selected fields) check the tabs (fields) you do not need by using the mouse and a Ctrl button and click Delete.
    • Using the buttons Up/Down, change the order of the chosen tabs or fields.
    • Edit the names of the chosen tabs/fields using the button Rename.
    • In order to add a new tab (or field separator) to the form, click Add. In the window that opens up, enter the name of the tab (or separator) and click OK:

      New tab (or separator) will appear in the list of Tabs (or Selected fields).

    • In order to move fields from one tab to another, first select a tab in the list of Tabs to which fields are to be added. Then, select a tab in the list Available tabs and the necessary fields in the list Available fields, that will be moved. After that, click «>«:

    The option Set these settings for all users by default permits you to establish form settings for all users as default settings.

    Save the changes made. Now, you have a changed infoblock element editing form with no unnecessary fields where the remaining fields are grouped up in a necessary order.

    Once set up is completed, the form looks like this:

    This form will also be used to create/edit elements in the public section.

    Note: The settings of the external appearance of the element creating/editing form can be reverted in the administrative part of the website using the command Disable custom form settings from the menu of the Settings button:

    Setting Up Prompts

    It is rather unusual to find a highly qualified employee working as a content manager in website support. Moreover, in case of a large number of created properties of an information block, even a highly qualified employee may experience difficulties when it comes to deciding which value should be entered in any other property field. A website administrator can create prompts to make it easier for a content manager to complete forms. The prompts look as follows in the form itself:

    Just use the field Info caption for ‘?’ sign in the properties form of the information block.

    Storage Types for Infoblocks

    When creating information blocks, infoblock properties should be stored in a separate table, and all of the property values of each element shall be stored in the same string. This technique is called Information Blocks 2.0 and permits you to significantly speed up system operation and also to lift a number of restrictions of the previous version of infoblocks. For example, now there is no need to make the additional request CIBlockElement::GetProperty when selecting property values with the function CIBlockElement::GetList.

    Infoblock 2.0 performance options:

    • When selecting elements, property values can be obtained at once, because the number of attached tables in the request is not increased with each property and is always equal to 1.
    • Sorting by property values is processed similarly to infoblocks 1.0 (except for multiple properties).
    • Selection of multiple property values does not lead to a Cartesian product of a query result; property values are transferred as an array.
    • For combined filters by non-multiple (single) properties, now there is an option to create a composite database index manually to speed up sampling operations.
    • “Straight through” element sampling is not possible for infoblocks 2.0 when an infoblock type and a symbol code of a property is indicated in the filter. Instead, IBLOCK_ID must be specified in the filter.

    Full compatibility with API is important. I.e. the technique for using infoblocks, properties, elements, and their values is the same for both versions of infoblocks.

    Connection between Infoblocks

    Bitrix Framework permits creating interconnections between information blocks using properties of the type Link to elements, Link to section, Link to elements (drop-down list), Link to elementы autocomplete text box and Link to product items (SKU).

    Infoblocks 2.0

    When creating information blocks, infoblock properties should be stored in a separate table, and all property values of each element are stored in the same string. This technique is called Information Blocks 2.0 and permits you to significantly speed up system operation and also to lift a number of restrictions of the previous version of infoblocks. For example, now there is no need to make the additional request CIBlockElement::GetProperty when selecting property values with the function CIBlockElement::GetList.

    Note. Documentation to Bitrix Framework, forum messages on the company’s website, and other places may contain the former name of the technique: infoblocks +.

    Infoblock 2.0 performance options:

    • When selecting elements, property values can be obtained at once, because the number of attached tables in the request is not increased with each property and is always equal to 1.
    • Sorting by property values is processed similarly to infoblocks 1.0 (except for multiple properties).
    • Selection of multiple property values does not lead to a Cartesian product of a query result; property values are transferred as an array.
    • For combined filters by non-multiple (single) properties, now there is an option to create a composite database index manually to speed up sampling operations.
    • “Straight through” element sampling is not possible for infoblocks 2.0 when an infoblock type and a symbol code of a property is indicated in the filter. Instead, IBLOCK_ID must be specified in the filter.

    Full compatibility with API is important. I.e. the technique for using infoblocks, properties, elements, and their values is the same for both versions of infoblocks.

    Storing properties in one common table and managing them using metadata from IBLOCK_PROPERTY_ID. is a very convenient feature for a developer because any metadata can be adjusted at any time without affecting any other information. This drawback is common for regular infoblocks.

    If information is stored in infoblocks 2.0 and a property changes its type, e.g. from Number to Line,the storage type in the database itself will also change.

    From the point of view of performance, infoblocks 2.0 scores better for small reference tables with a small number (20-30) of rarely changed properties. It makes no sense to shift a newsfeed to this type of infoblocks. You will gain in terms of the number of queries, but lose in terms of the query performance time.

    Infoblock 2.0 databases have a physical limit to the number of infoblock properties. At this time, it is not controlled in the system because it depends on a number of unpredictable factors: property types, MySQL configuration, and others. When this physical limit is exceeded, you will obtain a MySQL error that is unusual for Bitrix Framework. However, no data will be lost in this case.

    A big advantage of infoblocks 2.0 is the possibility to use composite indexes. However, the situation when sorting is made by = and by several fields simultaneously is quite unusual.

    Information Block Level

    Information block has a VERSION, attribute that determines whether an information block property value will be stored in a common or an allocated storage when creating a new infoblock. If an allocated storage for a specific infoblock is selected, the database creates two additional tables wherein the names will contain the infoblock indicator. One of the tables will store multiple properties and another one single and cached values of multiple properties.

    There is a link to a “converter” between storage types at the editing stage of an infoblock. It should be used with utmost care because the duration of the conversion process depends on the total volume of infoblock property values. Throughout the entire conversion, the infoblock is in an inconsistent state (only a part of values is transferred). In a test configuration for a MySQL version the conversion speed is approximately 1,500 elements per a 20 second step.

    The Fetch method is redefined in the CIBlockResult class.The method is responsible for caching values of multiple properties of an element that participate in sampling. For the same properties of the list type, the pairs ID=>VALUE are selected from the reference table.

    API provides for a VERSION parameter in the field array $arFields of the method CIBlock::Add. Its values are: 1 – for general storage and 2 – for allocated (new) storage.

    Information Block Level of Properties

    During the editing of properties (change of a multiplicity attribute or a property type), additional table management operations are performed for the properties stored in the allocated storage, such as delete/add columns, insert/update, or delete a big number of entries. It is best to avoid doing this unless it is absolutely necessary. It is recommended to change the type or multiplicity of one property at a time. Moreover, for single properties, it is recommended to convert them first to multiple properties and then change the type, and the other way around for multiple properties – first comes the type, and then conversion to a single property.

    Level of Elements of an Information Block

    When adding an element, a relevant entry is made into the table that stores the property values of the element.

    Level of Property Values of Information Block Elements

    The values of single properties of an infoblock with the allocated ID storage are composite and consist of an element ID and a property ID separated by a colon. When updating multiple properties, the cache of these values resets.

    Property values are stored in 2 tables (description of tables and their structure is provided for reference only and is subject to change in later versions):

    • b_iblock_element_prop_mNN — for multiple. It has the same structure as b_iblock_element_property;
    • b_iblock_element_prop_sNN — for single. It has the field IBLOCK_ELEMENT_ID — infoblock element ID to which the properties:
      • PROPERTY_XX — stores values of a single property XX or cache of values for a multiple property;
      • DESCRIPTION_XX — stores description for a single property.

    How to achieve the best performance using Infoblocks 2.0?

    In order to take advantage of the data storage structure used in new infoblocks, component behavior must be modified to a certain extent.

    For example: if the code template was more or less like this:

    <?
    //Determine the array of necessary fields of an element
    $arSelect = array(
        "ID",
        "IBLOCK_ID",
        "IBLOCK_SECTION_ID",
        "NAME",
        "PREVIEW_PICTURE",
        "DETAIL_PICTURE",
        "DETAIL_PAGE_URL",
    );
    //Obtain the list of elements. (+1 query)
    if($rsElements = GetIBlockElementListEx($IBLOCK_TYPE, false, false, array($ELEMENT_SORT_FIELD => $ELEMENT_SORT_ORDER), 
    $ELEMENT_COUNT, $arFilter, $arSelect))
    {
        //Initialization of a page by page display.
        $rsElements->NavStart($ELEMENT_COUNT);
        $count = intval($rsElements->SelectedRowsCount());
        if ($count>0)
        {
            //For each element:
            while ($obElement = $rsElements->GetNextElement())
            {
                $arElement = $obElement->GetFields();
                //Obtain its properties. (+1 query)
                $arProperty = $obElement->GetProperties();
                //Below property values can be used.
                //For example:
                echo $arProperty["SOURCE"],"
    "; //etc. } } } ?>

    Now, after conversion to a new storage type, it is possible to avoid queries in the cycle:

    <?
    //Determine the array of necessary fields of an element
    $arSelect = array(
        "ID",
        "IBLOCK_ID",
        "IBLOCK_SECTION_ID",
        "NAME",
        "PREVIEW_PICTURE",
        "DETAIL_PICTURE",
        "DETAIL_PAGE_URL",
        "PROPERTY_SOURCE", //Select a property we need
        // And all other which may be needed
        //directly in the list
    );
    //Obtain the list of elements. (+1 query)
    if($rsElements = GetIBlockElementListEx($IBLOCK_TYPE, false, false, array($ELEMENT_SORT_FIELD => $ELEMENT_SORT_ORDER), 
    Array("nPageSize"=>$ELEMENT_COUNT), $arFilter, $arSelect))
    {
        //Initialization of a page by page display.
        $rsElements->NavStart($ELEMENT_COUNT);
        if($obElement = $rsElements->GetNextElement())
        {
            //For each element:
            do
            {
                $arElement = $obElement->GetFields();
                //Below property values can be used.
                //For example:
                echo $arElement["PROPERTY_SOURCE"],"
    "; //etc. } while ($obElement = $rsElements->GetNextElement()) } } ?>

    Action Plan in Case of Problems

    Should any problems occur in project operation, we recommend that the following algorithm be used in order to eliminate the problems:

    • Establish a specific goal. The optimization and improvement has no limits. For example: each page with a catalog of your goods must open within a set amount of time, say, 0.2 second. Only establishing these specific goals and achieving them can be efficient, unlike the vague and formalized request “to make it work better”.
    • Find and remove irrelevant queries using the tool Performance monitor (Settings > Performance > SQL Queries).

      An irrelevant query is:

      • A query in a cycle (should be removed from the cycle to a variable).
      • Queries that collect additional data in a cycle. (It is better to collect data in a filter, then display data in a single query, and after that introduce an additional cycle – breaking the data down; in this case, queries have no linear dependence on the number of elements).
    • Remove unused data from queries ($arSelect). Specify the fields to be used. It drastically improves performance because such a specific indication of data to be used means a lesser volume of sorting for the database. The database performs such sorting not in the hard drive but in the RAM.
    • Assess the possibility of using PROPERTY_* in combination with infoblocks 2.0. Infoblocks 2.0 store their properties in a separate table. This table is attached at the selection stage when PROPERTY_* is mentioned. And no properties are attached until values of these properties are selected.

      When can we not use it? For example, in case of a small selection from a big number of elements (10 pieces of news out of several thousands of entries). It is not a straightforward aspect, and it depends very little on the developer. The reason is that sampling from infoblocks and infoblocks 2.0 leads to a different result. In simple infoblock entries start reproducing when selected. And infoblocks 2.0 return the array of property values. If a template code does not provide for this situation, the change of regular infoblocks to infoblocks 2.0 will destruct the template.

    • Review the execution plan of the most heavy queries and add/delete indexes.

    Sorting

    Sometimes caching is bad. If we choose to cache a catalog with a multivariate filter in the conditions of dense website traffic, cache generation may exceed 200 MB per minute. Any disc quota limit will be filled fast enough. Problems with cleaning this cache may also occur. Deactivating such cache will reduce the number of writing operations.

    Do not be afraid of creating indexes. It is impossible to say which indexes must be created in each particular case by default; each specific situation must be examined without fail. The tool Analyze indexes helps you do that (Settings > Performance > Database indexes > Analyze indexes).

    One of the indexes most frequently used is the index by the list-based property. This index is intended for simple infoblocks. b_iblock_element_property — in this table property values are stored. So we index it: property value, its ID, and an element ID. When the list-based property filter is in place, creating such an index actually makes the query of MySQL to said table unnecessary because all of the query fields are available in the index.

    Indexes are needed for specific sampling in specific projects. Depending on project architecture and logics, slow queries receive their own indexes, and they need such own indexes; often such indexes are composite.

    Sorting Types

    In the majority of functions and methods of the module of information blocks with list selection, filters admit different sorting types. Sorting type symbols shall be indicated directly before the name of the field to be sorted.

    Types:

    • (empty) – for string fields means mask search (in mask: «%» — an arbitrary number of any symbols, «_» — one arbitrary symbol); for non-string fields the search is «equal».
    <?
    // find elements in which the name begins with "#"
    $res = CIBlockElement::GetList(Array(), Array("NAME"=>"#%"));
    
    // find elements with identifier 100
    $res = CIBlockElement::GetList(Array(), Array("ID"=>"100"));
    ?>
    • «!» — for strings – an expression that does not fall within a mask, or unequal (for other types of fields).
    <?
    // find elements in which the name does not begin with "#"
    $res = CIBlockElement::GetList(Array(), Array("!NAME"=>"#%"));
    ?>
    • «?» — using the logics, works only for string properties.
    <?
    // find elements in which the name contains "One" or "Two"
    $res = CIBlockElement::GetList(Array(), Array("?NAME"=>"One | Two"));
    ?>
    • «<» — less;
    • «<=» — less or equal;
    • «>» — more;
    • «>=» — more or equal.
    <?
    // find elements in which the name begins with "A"
    $res = CIBlockElement::GetList(Array(), Array(">=NAME"=>"A", "<NAME"=>"B"));
    
    // find elements with an identifier of more than 100
    $res = CIBlockElement::GetList(Array(), Array(">ID"=>"100"));
    ?>
    • «=» — equal;
    • «!=» — non-equal.
    <?
    // find elements in which the name is equal to "ELEMENT%1"
    $res = CIBlockElement::GetList(Array(), Array("=NAME"=>"ELEMENT%1"));
    ?>
    • «%» — substring;
    • «!%» — not a substring.
    <?
    // find elements in which the name contains the percent symbol "%"
    $res = CIBlockElement::GetList(Array(), Array("%NAME"=>"%"));
    ?>
    • «><» — between;
    • «!><» — not between.

    As an argument, these types of filters admit an array («value FROM», «value TO»)

    <?
    // ind elements in which the name begins between "A" and "B" or between "D" and "E"
    $res = CIBlockElement::GetList(Array(), Array("><NAME"=>Array(Array("A", "B"), Array("D", "E"))));
    
    // find elements in which the activity start date is outside the year 2003
    $res = CIBlockElement::GetList(Array(),
                                   Array("!><DATE_ACTIVE_FROM"=>
                                         Array(date($DB->DateFormatToPHP(CLang::GetDateFormat("FULL")), 
                                                    mktime(0,0,0,1,1,2003)),
                                               date($DB->DateFormatToPHP(CLang::GetDateFormat("FULL")), 
                                                    mktime(0,0,0,1,1,2004)))));
    ?>

    Some Specific Cases of Sorting

    $arFilter = array("PROPERTY_CML2_SCAN_CODE"=>false ) - is used to select all elements with an empty property; 
    $arFilter = array("PROPERTY_CML2_SCAN_CODE"=>"" ) - is used to select all elements;  
    $arFilter = array("PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - during element sorting, the exact match of a property with the preset string is checked; 
    $arFilter = array("?PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - during element sorting, the availability of preset substring in a property is checked;
    $arFilter = array("!PROPERTY_CML2_SCAN_CODE"=>false ) - is used to select only elements with a property filled in; 
    $arFilter = array("!PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - during element sorting, the absence of the exact match with the preset string is checked; 
    $arFilter = array("!?PROPERTY_CML2_SCAN_CODE"=>"qwe" ) - during element sorting, the absence of the preset substring in a property is checked. 
    

    Sorting Set Up to Display Related Elements

    Task: Let us assume that there are 2 infoblocks that are interrelated by one of the properties. How should we set up the sorting so that among the infoblock elements only related elements can be displayed?

    Solution:

    <? 
      $arrFilter = array(); 
      $arrFilter['!PROPERTY_<property_code>'] = false; 
    ?>

    Sorting Set Up Using a «Date/Time» Type of Property

    A «Date/time» type of property is stored as string in the format YYYY-MM-DD HH:MI:SS, That is why the value for sorting is formed as follows:

    $cat_filter[">"."PROPERTY_available"] = date("Y-m-d");

    Handling iblocks via API

    The needs of website owners are very diverse. The standard functionality of Bitrix Framework cannot resolve all tasks that may be set for the developer when it comes to creating Internet project. API must be used to implement non-standard tasks. API of infoblocks should be studied with special attention. They are most widely used in programming.

    Attention! Direct database queries are strongly discouraged. In this case, the operation of system basic functions is not guaranteed. In addition, it may lead to data disintegration.

    API of a module consists of several high-level functions to select data in the public section of a website and a set of classes with low level methods for a more specialized work.

    Before using the module, please make sure it is installed and activate it using the following structure:

    <?
    if(CModule::IncludeModule("iblock"))
    {
       //here module functions and classes can be used
    }
    ?>

    Functions with simple parameters and preset filters may be used to obtain data during display in the public section of the website. These functions select by default the values that are suitable for the sorting location, namely only those active, related to the current website, suitable in terms of access rights, etc.

    All work with dates through API (insert, selection, filters, etc.) is done in the format of the current website or, if in the administrative part, in the format of the current language.

    Some API functions are available at all times, i.e. described in the main module, and some functions depend on the module used and can be present or absence in different versions of the product.

    For most classes of Bitrix Framework the following functions are available:

    • Data selection (GetList).
    • Adding a new element (Add).
    • Updating and deleting an element (Update).
    • Deleting an element (Delete).
    • And other functions.

    For most modules, a specialized class structure, event mechanism, and additional functions are available. In particular, for the module of Information blocks the description is provided for:

    • All tables used in the database, including table fields.
    • Classes for work with infoblock types, infoblocks, elements, sections, and fields.
    • Events occurring when adding, changing, and deleting module objects.
    • Functions that extend kernel possibilities.
    • Means to create the user’s forms for editing and the user’s data types.
    • Other information.

    The lessons of this chapter will address some examples of using the API of information blocks.

    Work with the User Properties of Infoblocks

    Examples of tasks that may occur while working with elements, sections, and infoblock properties.

  • Obtain values of all of the properties of an element knowing its ID
  • Obtain properties of the elements using the method CIBlockElement::GetList
  • Add a property of the type TEXT/html for an element
  • Complete a multiple property of the File type
  • Complete a multiple property of the List type with a display as checkboxes
  • Obtain a user property of a section
  • Task 1:
    Obtain values of all of the properties of an element knowing its ID.

    1	<? $db_props = CIBlockElement::GetProperty(IBLOCK_ID, ELEMENT_ID, "sort", "asc", array());
    2	$PROPS = array();
    3	while($ar_props = $db_props->Fetch())
    4	$PROPS[$ar_props['CODE']] = $ar_props['VALUE'];?>

    Now the symbol code of the property is the key of the associative array $PROPS, I.e. if you need a value of the property with the code price, it will be stored in $PROPS[‘price’].

    Task 2:
    Obtain properties of the elements using the method CIBlockElement::GetList

    1	<? $arSelect = array("ID", "NAME", "PROPERTY_prop_code_1", "PROPERTY_prop_code_2");
    2	$res = CIBlockElement::GetList(array(), array(), false, array(), $arSelect);?>

    Then, you have to use the cycle and obtain the properties with symbol codes prop_code_1 and prop_code_2.

    Task 3:

    Add a property of the type TEXT/html for an element.

    If the property is not a multiple property:

    01	<? $element = new CIBlockElement;
    02	$PROP = array();
    03	$PROP['property symbol code']['VALUE']['TYPE'] = 'text'; // or html
    04	$PROP['property symbol code']['VALUE']['TEXT'] = 'property value';
    05	$arLoadArray = array(
    06	  "IBLOCK_ID"      => IBLOCK_ID,
    07	  "PROPERTY_VALUES"=> $PROP,
    08	  "NAME"           => "Name of the element"
    09	  );
    10	$element->Add($arLoadArray);?>

    If the property is a multiple property:

    01	<? // In $ITEMS multiple property values are stored
    02	foreach($ITEMS as $item)
    03	{
    04	    $VALUES[]['VALUE'] = array(
                      'TYPE' => 'text', // or html
                      'TEXT' => $item,
                );
    05	    $VALUES[]['VALUE']['TEXT']= $item;
    06	}
    07	$element = new CIBlockElement;
    08	$PROPS = array();
    09	$PROPS['property symbol code'] = $VALUES;
    10	$arLoadArray = array(
    11	  "IBLOCK_ID"      => IBLOCK_ID,
    12	  "PROPERTY_VALUES"=> $PROPS,
    13	  "NAME"           => "Name of the element"
    14	  );
    15	$element->Add($arLoadArray);?>

    Task 4:
    Complete a multiple property of the File type. Quite often when adding an element to an infoblock several files may need to be attached to it. It can be conveniently done by creating a multiple property of the File type for the infoblock and store files there. Here is an example of the property completed:

    01	<?
    02	$arFiles = array();
    03	for($i = 1; $i < 10; $i++)
    04	{
    05	    if(file_exists($_SERVER['DOCUMENT_ROOT'].'/images/image_'.$i.'.jpg'))
    06	    {
    07	        $arFiles[] = array('VALUE' => CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"].'/images/image_'.$i.'.jpg'), 'DESCRIPTION' => '');
    08	    }
    09	}
    10	?>

    After that, the array $arFiles is transferred as a property value when an element is added.

    Task 5:

    Complete a multiple property of the List type with a display as checkboxes. In this case, each element of the list has its own ID. It can be looked up by going to detailed editing of the property. The property shall be completed as follows:

    1	<?
    2	if($first_condition == true) $values[] = array('VALUE' => 1);
    3	if($second_condition == true) $values[] = array('VALUE' => 2);
    4	CIBlockElement::SetPropertyValuesEx($ELEMENT_ID, $IBLOCK_ID, array('property_code' => $values));
    5	?>

    In this case, when performing the first and second condition, we should check the list elements from ID =1 and ID=2 accordingly. $ELEMENT_ID, $IBLOCK_ID and property_code shall be replaced with necessary values.

    Task 6:

    Obtain a user property of a section

    1	<? $section_props = CIBlockSection::GetList(array(), array('IBLOCK_ID' => IBLOCK_ID, 'ID' => SECTION_ID),  true, array("UF_ADDITIONAL_PRICE"));
    2	$props_array = $section_props->GetNext(); ?>

    Now, the value of the property $props_array[‘UF_ADDITIONAL_PRICE’] of an infoblock section is located in UF_ADDITIONAL_PRICE.

    Infoblock Copy

    Infoblock copy is not provided for in Bitrix Framework as a standard option, although sometimes it may become necessary, and it can be done. Automation of this process will be a good example of using infoblock API.

    The Use of XML Import

    Infoblocks can be copied using the XML import/export function:

    • Download the necessary infoblock by exporting in XML.
    • Open the XML file for editing and carefully adjust the infoblock ID where necessary. In the beginning of the XML, the ID node may be replaced with the Title node:
      <?xml version="1.0" encoding="UTF-8"?>
      <CommerceInformation SchemaVersion="2.021" CreationDate="2010-03-20T09:55:13">
         <Metadata>
            <Id>2
            <Title>Notebooks
            <Properties>
               <Property>
                  <Id>CML2_CODE
                  <Title>Symbol code

      Find the following code after the description of the infoblock and its properties:

      <Catalog>
            <Id>2
            <MetadataId>2
            <Title>Notebooks

      Establish data in accordance with the amendments made above in the nodes ID, MetadataId, and Title.

    Copy Automation

    Use the script provided below to import metadata from the information block created earlier when generating a new one:

    Metadata copy setting is made using three fields:

    • Copy IB. This field is mandatory and is always preset. The IB from which metadata are to be imported shall be indicated in this section (except for property description).
    • Copy properties of an IB to a new IB. This field is not mandatory. It may be used to import only metadata of the properties of any infoblock to a new information block. If the field is not completed, property metadata will be taken from the infoblock indicated in the field Copy IB.
    • Copy of IB to type. This field is not mandatory. It may be completed if a new information block must be generated in any type of IB. If no setting is indicated, the type of infoblock indicated in the field Copy IB will be used.

      After copy, the new infoblock will have the name of the old infoblock with the suffix _new.

    Script code:

    CModule::IncludeModule("iblock");
        if(intval($_REQUEST["IBLOCK_ID_FIELDS"])>0){
            $bError = false;
            $IBLOCK_ID = intval($_REQUEST["IBLOCK_ID_FIELDS"]);
            $ib = new CIBlock;
            $arFields = CIBlock::GetArrayByID($IBLOCK_ID);
            $arFields["GROUP_ID"] = CIBlock::GetGroupPermissions($IBLOCK_ID);
            $arFields["NAME"] = $arFields["NAME"]."_new";
            unset($arFields["ID"]);
            if($_REQUEST["IBLOCK_TYPE_ID"]!="empty")
                $arFields["IBLOCK_TYPE_ID"]=$_REQUEST["IBLOCK_TYPE_ID"];
            $ID = $ib->Add($arFields);
                if(intval($ID)<=0)
                    $bError = true;        
            if($_REQUEST["IBLOCK_ID_PROPS"]!="empty")
                $iblock_prop=intval($_REQUEST["IBLOCK_ID_PROPS"]);
            else
                $iblock_prop=$IBLOCK_ID;
    
     $iblock_prop_new = $ID;
            $ibp = new CIBlockProperty;
            $properties = CIBlockProperty::GetList(Array("sort"=>"asc", "name"=>"asc"), Array("ACTIVE"=>"Y", "IBLOCK_ID"=>$iblock_prop));
            while ($prop_fields = $properties->GetNext()){
                if($prop_fields["PROPERTY_TYPE"] == "L"){
                    $property_enums = CIBlockPropertyEnum::GetList(Array("DEF"=>"DESC", "SORT"=>"ASC"),
                                                                   Array("IBLOCK_ID"=>$iblock_prop, "CODE"=>$prop_fields["CODE"]));
                    while($enum_fields = $property_enums->GetNext()){
                        $prop_fields["VALUES"][] = Array(
                          "VALUE" => $enum_fields["VALUE"],
                          "DEF" => $enum_fields["DEF"],
                          "SORT" => $enum_fields["SORT"]
                        );
                    }
                }
                $prop_fields["IBLOCK_ID"]=$iblock_prop_new;
                unset($prop_fields["ID"]);
                foreach($prop_fields as $k=>$v){
                    if(!is_array($v))$prop_fields[$k]=trim($v);
                    if($k{0}=='~') unset($prop_fields[$k]);
                }
                $PropID = $ibp->Add($prop_fields);
                if(intval($PropID)<=0)
                    $bError = true;
            }
            if(!$bError && $IBLOCK_ID>0)
                LocalRedirect($APPLICATION->GetCurPageParam("success=Y",array("success","IBLOCK_ID_FIELDS")));
            else 
                LocalRedirect($APPLICATION->GetCurPageParam("error=Y",array("success","IBLOCK_ID_FIELDS")));
        }
       $str .='<form action='.$APPLICATION->GetCurPageParam().' method="post">[table]';    
        if($_REQUEST["success"]=="Y") $str .='[tr][td]<font color="green">IB is copied successfully</font>[b][/td][/tr]';
        elseif($_REQUEST["error"]=="Y") $str .='[tr][td]<font color="red">Error</font><br/>[/td][/tr]';
        $str .='[tr][td]Copy of IB metadata to a new IB:[/b]<br/>[/td][/tr]';
        $res = CIBlock::GetList(Array(),Array(),true);
            while($ar_res = $res->Fetch())
                $arRes[]=$ar_res;
        $str .='[tr][td]Copy IB:<br><select name="IBLOCK_ID_FIELDS">';
        foreach($arRes as $vRes)    
            $str .= '<option value='.$vRes['ID'].'>'.$vRes['NAME'].' ['.$vRes["ID"].']</option>';
        $str .='</select>[/td]';
        $str .='[td]Copy to new IB properties of another IB: *<br><select name="IBLOCK_ID_PROPS">';
        $str .='<option value="empty">';
        foreach($arRes as $vRes)    
            $str .= '<option value='.$vRes['ID'].'>'.$vRes['NAME'].' ['.$vRes["ID"].']</option>';
        $str .='</select>[/td][/tr]';
        $str .='[tr][td]Copy IB to type:<br><select name="IBLOCK_TYPE_ID">';
        $str .='<option value="empty">';
        $db_iblock_type = CIBlockType::GetList();
        while($ar_iblock_type = $db_iblock_type->Fetch()){
           if($arIBType = CIBlockType::GetByIDLang($ar_iblock_type["ID"], LANG))
              $str .= '<option value='.$ar_iblock_type["ID"].'>'.htmlspecialcharsex($arIBType["NAME"])."</option>";
        }
        $str .='</select>[/td][/tr]';
        $str .='[tr][td]<br/>if the value is not specified, IB metadata of the "Properties" section will be collected from the IB of the 1st field[/td][/tr]';
        $str .='[tr][td]<input type="submit" value="copy">[/td][/tr]';
        $str .='[/table]</form>';    
        echo $str;

    The script can prove to be of invaluable help, for example, when copying IB without using XML export/import mechanisms of information blocks.

    This tool is recommended for infoblocks where there are many list-based properties or generally a big number of properties that require a detailed setup.

    The script must be located in the website root.

    Infoblocks in Document Flow

    When working in document flow mode 2, infoblock elements are created: one “final” with an empty WF_PARENT_ELEMENT_ID (which we can see in the administrative part), and another “temporary” with WF_PARENT_ELEMENT_ID equal to the ID of the just created final element. The “temporary” element turns into “final” when it reaches a final status Published in the document flow or any other status flaged as IS_FINAL. This flag cannot be set using the API methods of Bitrix Framework; it only can be set by editing the database. Accordingly, entries will be made to the infoblock up to this moment; however, for standard API methods with default parameters these entries will not be available.

    The principal difference between the “temporary” elements and “final” is the field WF_PARENT_ELEMENT_ID, which is empty for the “final” elements and contains an identifier of the “final” element for temporary elements. Should a new element be created in the document flow (provided that the starting status of the document flow is not final), an element will be created, and its own identifier will be written in the field WF_PARENT_ELEMENT_ID. Upon the subsequent promotion of the element to any other document flow status, including the final one, new elements with the field WF_PARENT_ELEMENT_ID will be created in the infoblock.

    Upon the promotion of an element to a final status, the initial element will be updated in such a manner so that the field WF_PARENT_ELEMENT_ID becomes empty, and the field WF_STATUS_ID becomes equal to the value of the final status (1 is the most frequent value). After the subsequent promotion of the element to any temporary status the cycle will be repeated with a slight difference that the current published element will be used as a starting point.

    By default, API only permits working with published elements. If a published element is promoted to any other status, API will return the element version that corresponds to the published one.

    In order to obtain the list of all of the elements (including unpublished ones), the method CIBlockElement::GetList must have the following parameter in the filter properties: "SHOW_HISTORY" => "Y".

    In addition, it is possible to obtain the latest version of an element in the document flow by its ID using the function CIBlockElement::WF_GetLast, nd vice versa to obtain the original ID of an element knowing the latest version of such an element through the function CIBlockElement::GetRealElement.

    Event handlers also deserve some attention, among them OnAfterIBlockElementUpdate. Since in case of working with document flow a direct Update only occurs in case of the promotion of an element to the final status, handlers of this event should not be expected when an element is promoted to “temporary” statuses. Instead, add a handler to the event OnAfterIBlockElementAdd and watch the fields "WF" = "Y" (a sign that the element participates in the document flow) and WF_PARENT_ELEMENT_ID (identifier of the “final” element).

    All change history of an element can be displayed using the function CIBlockElement::WF_GetHistoryList. In order to obtain detailed information about the temporary elements obtained using this function, the functions CIBlockElement::GetByID and CIBlockElement::GetProperty should be used.

    SEO in Infoblocks: Calculated Properties

    Starting from version 14.0.0, the tab SEO is available in the editing form of the infoblock, its sections, and elements. This functionality is based on the following techniques:

    • Storage – meaning a mechanism of inherited properties (property values apply top to bottom over the infoblock hierarchy: from infoblock through sections down to an element);
    • Template engine is a template builder that uses substitutions and functions.

    Let us consider each of these techniques in more detail.

    Storage

    All templates to be inherited by the calculated inherited properties are stored in the table b_iblock_iproperty. It is a single table that stores templates for three entities: elements, sections, and infoblocks.

    Templates are linked to an infoblock, section, or element using two fields: ENTITY_TYPE and ENTITY_ID. In order to determine which templates should be used for each entity, an internal search by existing infoblock tables is performed. Calculated values are stored in 3 different tables for elements, sections, and infoblocks separately.

    When handling the data of the table b_iblock_iproperty (when we change, delete, or make additions to the template) no calculations are made, only the reset of values calculated previously. The calculation operation is postponed until the values are called for (read). At this time, templates are searched from bottom to top over the infoblock hierarchy (for an element, these will be templates of the element proper, its sections (up to the root), and infoblock templates). After that, the templates will be calculated and the obtained values will be stored in cache tables to be retrieved during subsequent read operations.

    Classes of inherited properties use all of the capacity of object-oriented programming and belong to a new D7 core. They are in the name space of BitrixIblockInheritedProperty and are pretty easy to use:

    use BitrixIblockInheritedProperty; 
    
    //OOP  ElementTemplates or SectionTemplates or IblockTemplates )) 
    $ipropTemplates = new InheritedPropertyElementTemplates($IBLOCK_ID, $ELEMENT_ID);
    //Install template for an element
    $ipropTemplates->set(array(
             "MY_PROP_CODE" => "{=this.Name}",
             “SOME_CODE" => "", //Delete template
    ));
    //Obtain templates for "editing" 
    $templates = $ipropTemplates->findTemplates();
    //Delete all own element templates
    $ipropTemplates->delete();
    
    //OOP  ElementValues or SectionValues or IblockValues )) 
    $ipropValues = new InheritedPropertyElementValues($IBLOCK_ID, $ELEMENT_ID);
    //Obtain values 
    $values = $ipropValues->getValues();
    echo $values [" MY_PROP_CODE "]; 
    //Reset cache
    $ipropValues->clearValues(); 
    
    • Create a class instance depending on the entity type (for elements it will be ElementTemplates, for sections — SectionTemplates and for infoblock — IblockTemplates).
    • Use the set method for template handling.

      Note: The set method is currently used in the methods Add and Update of the classes CIBlock, CIBlockSection and CIBlockElement (the field IPROPERTY_TEMPLATESis processed).

    • Use the method getValues (it can be found in infoblock components) in order to display data calculated during selection according to set templates.
    • The method clearValues permits you to reset the cached values and recalculate.

    Templates

    Templates are built irrespective of the storage mechanism, and thus dynamic forms may be used. The following components are used to build a template:

    • First, it is just a text to be calculated into the same simple text.
    • Second, the substitutions which start inside curly brackets with an equal sign (e.g., {=this.Name}). This pseudoobjective syntaxis permits you to implement an economic model with pending data queries. The template may use the following areas: this, parent, sections, iblock, property or catalog. Fields can be very different: name, code, previewtext, detailtext, property_CODE, etc. (See files with classes in the folder /bitrix/modules/iblock/lib). The number of DB queries directly depends on the number of areas used in the template.
    • Third, the functions (e.g., {=concat » » «!» iblock.name sections.name this.name}). There is a set of built-in functions (upper, lower, translit, concat, limit, contrast, min, max и distinct) and the event OnTemplateGetFunctionClass, which permits you to write an own function.

    Templates can have modifiers: lower casing (/l) and transliteration (/t-). They are displayed in separate checkboxes in the SEO tab interface.

    Furthermore, all templates support nesting. For example:

    //For an element, a preview and detailed descriptions of its section are selected, then
    //first 50 words are selected. After that, they joined with the first 50 words of the element preview.
    //Out of them, 20 of the most contrasted words are selected, and all of them are converted to lower case.
    
    
    {=lower {=contrast 20 " .,?!" {=limit 50 " .,?!" this.previewtext} {=limit 50 " .,?!" parent.previewtext parent.detailtext}}}
    

    Let us have a look at the template code:

    use BitrixIblockTemplate;
    //Connect the infoblock module.
    if (BitrixMainLoader::includeModule('iblock'))
    {
          //Set a template.
          $template = "Name: {=this.Name}. Code:{=this.code}";
          //We will take the source data from the element.
          $entity = new TemplateEntityElement($ELEMENT_ID);
          //Do not forget about safety.
          echo BitrixMainTextString::htmlEncode(
                  //Calculate value according to the template.
                  TemplateEngine::process($entity, $template) 
         );
    }
    

    The entity object must be created. Parsing and template calculation are covered with the process, static method to which entity and template are submitted. The method process can also be used in the cycle with one entity and different template, and in this case the data will be «reused», i.e. there will be no odd queries. In addition, pay attention to the method htmlEncode, that is used to form safe html.

    Highload blocks

    The Information block module is considered as «heavily resource-demanding» for creating «lightweight» directories or storage of large data volumes (when module can behave sub-optimally). Now an option to create analog to iblocks (Highload blocks module), but a lot simpler and «lightweight» is available. New module is available from product version 14.0 and is written on the new core D7. Data structure for this module provides for use of high-load projects.

    Note: Highload blocks and traditional information blocks are conceptually two different things. That’s why there is no option to convert traditional iblocks into highload blocks. Technically, a mirroring structure can be created and data moved, however it makes sense only for a specific project, if required.

    Highload blocks

    Highload blocks — are fast listing directories, without hierarchy support, with limited property support. They could query the database, including via HandlerSocket and handle large volumes of data. Highload blocks store elements in their tables and use their own indexes.

    Note: with exception to module Highload blocks, core D7 can query database via HandlerSocket for all ORM entities.

    Previously, data was stored using the module that closed tables via Table Module design pattern. Highload blocks have additional layer called Table Data Gateway that supports all techniques for handling tables, with business logic located in expanding class.

    Highload blocks is a logic for handling data, nothing more. For specific data use, developer must envisage implementation of business logic by the application itself. Accordingly, Highloadblock class must be expanded to implement this logic:

    use BitrixHighloadblock as HL;
    $hlblock   = HLHighloadBlockTable::getById( # )->fetch();
    $entity   = HLHighloadBlockTable::compileEntity( $hlblock ); //class generation 
    $entityClass = $entity->getDataClass();
    
    class MyDomainObjectTable extends #entityClass# {
    …//our project business logic, overview the contents of $entityClass and write it into #entityClass#
    } 
    

    Highload blocks advantages

    • Low overhead cost (PHP, less SQL queries).
    • Low risk of blockages in database: because data is are stored in respective tables, there is no unified, global table that can be blocked upon high loads (data retrieval and simultaneous import).
    • Thousands, millions entities, directories.
    • Reduce load on the database, hosting.

    About performance and resources

    Highloadblock is a middleware between user and ORM. OOP (object-oriented programming) leads to an increased use of memory and CPU, offering a more convenient and effective development process. Highloadblocks are intended for high-loads experienced by the system. Their advantages are in the area of architecture, allowing easier scalability, management and risk assessment.

    Database handling is the specific advantage database handling. Developer receives:

    • separate tables for entities (can be noticeable when handling large volumes),
    • possibility to easy index assignment for necessary selections,
    • possibility to transparently use handlersocket that significantly reduces load to DBMS.

    Module architecture

    Description

    In contrast to Information blocks module, Highload blocks module can store each entity in their own individual tables, without associating with any module. Entity fields are [ds]core’s user properties. [/ds][di]It is necessary to differentiate User fields in system modules and properties used within information blocks, although system forms (forms for creating/editing an iblock section and others) use a «user property» term[/di]

    Entities are generated in admin interface, without additional programming.

    Access to data is provided based on ORM.

    Data allocation can be performed both to a single database and to different databases in general. This way a project’s scaling out becomes possible, when some objects are located at one server, and some data — on another.

    The Highload blocks module supports NoSQL concept based on MySQL database. Database connection is provided with both handlersocket extension for MySQL, as well as without it. Handlersocket use requires additional settings.

    For example, when project supports NoSQL, with all required installed drivers for MySQL and core/kernel configuration had been established connection via these drivers. As result, Highload blocks module allows creating entities and building project in a manner that data retrieval from database was performed via the driver by primary key.

    The following actions must be performed to use ORM entity for Highload blocks:

    • Initialize entity in the system:
      //first, select entity data from the database
      $hldata = BitrixHighloadblockHighloadBlockTable::getById($ID)->fetch();
      
      //then initialize entity class
      $hlentity = BitrixHighloadblockHighloadBlockTable::compileEntity($hldata);
      
    • Next, creates queries with this entity:
      $query = new EntityQuery($hlentity);
      

      or use via DataManager interface:

      $hlDataClass = $hldata['NAME'].'Table';
      $hlDataClass::getList();
      

    All further handling of Highload blocks is governed by the ORM rules, because entity, created in the Highload blocks is an ORM entity.

    Module contains two components: list of entity records (highloadblock.list) and entity detail view (highloadblock.view).

    List association

    Association of directory to Highload block is performed via the USER_TYPE_SETTINGS properties. They contain table name.

    Association of property values uses UF_XML_ID (completing the field is required), otherwise element’s «list» property values are not saved.

    Highloadblock and handlersocket

    Traditional ACID Databases generally complicate project implementation for specific tasks. Such frameworks as NoSQL and HandlerSocket were designed for such tasks (in a form of plugin to standard MySQL).

    HandlerSocket allows customer application to connect directly to MySQL data engine to reduce excessive load, specific to traditional queries via the SQL interface and unacceptable to highly loaded databases.

    Example comparison for number of queries, permitted by different database handling methods:

    Query type Number of queries per second CPU load
    MySQL via SQL client 105 000 User processes: 60%
    System processes: 28%
    memcached 420 000 User processes: 8%
    System processes: 88%
    MySQL via client’s HandlerSocket 750 000 User processes: 45%
    System processes: 53%

    The difference between querying via MySQL client and via HandlerSocket is that in the second case, parsing, table openings, execution schedule optimization are skipped. It means that querying is performed directly and the load to MySQL decreases significantly. Queries are not executed quicker, but reduces server load.

    Connecting HandlerSocket is specified in the file Core parameter settings.

    You can install the plugin either by directly downloading MySQL source files and assembling the plugin, or by installing PerconaServer or MariaDB, with plugin enabled by default.

    HS API is called when querying the Highloadblock ($obj = $entityClass::getById( $arData["ID"] )->fetch();) via HandlerSocket (query open_index and find in MySQL) with call result processing by the application.

    HandlerSocket launches a thread pool inside the database, operating in asynchronous mode. (NGINX operates in a similar manner) In standard cases MySQL operates with a single thread for each client connection (thread) and works inside it. However, in case of HandlerSocket, it uses a pool of threads with multiplex system pool/select calls. That’s why, for example, 5 threads can process hundreds of thousands queries.

    Highload blocks handling examples

    <?
    //Preparation:
    if (CModule::IncludeModule('highloadblock')) {
       $arHLBlock = BitrixHighloadblockHighloadBlockTable::getById(1)->fetch();
       $obEntity = BitrixHighloadblockHighloadBlockTable::compileEntity($arHLBlock);
       $strEntityDataClass = $obEntity->getDataClass();
    }
    
    //Adding:
    if (CModule::IncludeModule('highloadblock')) {
       $arElementFields = array(
          'UF_NAME' => $arPost['name'],
          'UF_MESSAGE' => $arPost['message'],
          'UF_DATETIME' => new BitrixMainTypeDateTime
       );
       $obResult = $strEntityDataClass::add($arElementFields);
       $ID = $obResult->getID();
       $bSuccess = $obResult->isSuccess();
    }
    
    //Retrieving the list:
    if (CModule::IncludeModule('highloadblock')) {
       $rsData = $strEntityDataClass::getList(array(
          'select' => array('ID','UF_NAME','UF_MESSAGE','UF_DATETIME'),
          'order' => array('ID' => 'ASC'),
          'limit' => '50',
       ));
       while ($arItem = $rsData->Fetch()) {
          $arItems[] = $arItem;
       }
    }
    ?>

    Arbitrary value selection:

    $q = new EntityQuery($entity);
           $q->setSelect(array('*'));
           $q->setFilter($arFilter);
           $q->setLimit(1);
           $q->registerRuntimeField(
               'RAND', array('data_type' => 'float', 'expression' => array('RAND()'))
           );
           $q->addOrder("RAND", "ASC");
           $result = $q->exec();
    

    User Forms for Element Editing

    The form of adding/changing elements of information blocks is one of those most frequently used, and this form is definitely one of the most popular in the administrative section of online stores or information editions. Although the form’s interface and fields change depending on the information block settings, the interface of the element editing form can be set up using standard tools of the system; however, these tools may prove insufficient for certain specific tasks.

    In this case, one or two (depending on the task) additional files should be created in /bitrix/php_interface/include/:

    • With the element editing form;
    • Responsible for processing the element fields before saving the element.

    After that, the paths to these files shall be set:

    File with the Element Editing Form

    Let us create, for example, a file my_iblock_element_edit.php in the folder /bitrix/php_interface/include/, and copy a code from the file /bitrix/modules/iblock/admin/iblock_element_edit.php into it from the string:

    	//////////////////////////
    	//START of the custom form
    	//////////////////////////
    

    up to the string:

    	//////////////////////////
    	//END of the custom form
    	//////////////////////////
    

    Now we can start editing the file, i.e. changing the external appearance of the editing form of the infoblock element according to our needs:

    • You can remove the infoblock fields that you do not need. The following structures are used in order to display form fields:
      <?
      $tabControl->BeginCustomField("ACTIVE_TO", GetMessage("IBLOCK_FIELD_ACTIVE_PERIOD_TO"), $arIBlock["FIELDS"]["ACTIVE_TO"]["IS_REQUIRED"] === "Y");
      ?>
      	<tr id="tr_ACTIVE_TO">
      		<td><?echo $tabControl->GetCustomLabelHTML()?>:</td>
      		<td><?echo CAdminCalendar::CalendarDate("ACTIVE_TO", $str_ACTIVE_TO, 19, true)?></td>
      	</tr>
      <?
      $tabControl->EndCustomField("ACTIVE_TO", '<input type="hidden" id="ACTIVE_TO" name="ACTIVE_TO" value="'.$str_ACTIVE_TO.'">');
      ?>
      
    • The function _ShowPropertyField() is used to display infoblock elements in the property form:
      <?
      $prop_code = "AUTHOR";
      $prop_fields = $PROP[$prop_code];
      $prop_values = $prop_fields["VALUE"];  
      
      $tabControl->BeginCustomField("PROPERTY_".$prop_fields["ID"],
                                    $prop_fields["NAME"],
                                    $prop_fields["IS_REQUIRED"]==="Y"); 
      ?>
      
      <tr id="tr_PROPERTY_<?echo $prop_fields["ID"];?>">
        <td class="adm-detail-valign-top" width="40%"><?if($prop_fields["HINT"]!=""):
      	?><span id="hint_<?echo $prop_fields["ID"];?>"></span>
                <script>BX.hint_replace(BX('hint_<?echo $prop_fields["ID"];?>'), '<?echo CUtil::JSEscape($prop_fields["HINT"])?>');</script>
         <?endif;?><?echo $tabControl->GetCustomLabelHTML();?>:</td>
        <td width="60%"><?_ShowPropertyField('PROP['.$prop_fields["ID"].']',
                                             $prop_fields,
                                             $prop_fields["VALUE"],
                                             (($historyId <= 0) && (!$bVarsFromForm) && ($ID<=0)),
                                             $bVarsFromForm, 50000,
                                             $tabControl->GetFormName(),
                                             $bCopy);?></td>
      </tr>
      
      <?  
      $tabControl->EndCustomField("PROPERTY_".$prop_fields["ID"], $hidden);  
      ?>
      

    When an own form on the element editing page is used, the Settings button disappears. This button permits you to sort and set up the display of the form fields of an element.

    The following code shall be added to the file in order to avoid adding a field sorting mechanism to my_iblock_element_edit.php and to maintain the standard function:

    <?
    // "Settings" button
    $aMenu = array();
    if (false == ((true == defined('BT_UT_AUTOCOMPLETE')) && (1 == BT_UT_AUTOCOMPLETE)))
    {
       $link = DeleteParam(array("mode"));
       $link = $GLOBALS["APPLICATION"]->GetCurPage()."?mode=settings".($link <> ""? "&".$link:"");
       $aMenu[] = array(
          "TEXT"=>GetMessage("IBEL_E_SETTINGS"),
          "TITLE"=>GetMessage("IBEL_E_SETTINGS_TITLE"),
          "LINK"=>"javascript:".$tabControl->GetName().".ShowSettings('".urlencode($link)."')",
          "ICON"=>"btn_settings",
       );
       
       $context = new CAdminContextMenu($aMenu);
       $context->Show();
    }
    ?>
    

    Important! Do not forget to indicate the path to this file in the infoblock settings.

    The file responsible for processing the element fields before saving the element

    In order to change the fields that are to be saved, homonymous fields in the arrays $_POST and $_FILES, must be modified, and the values of all of the properties must be modified in the array $PROP.

    For example, let us create a file before_iblock_element_edit.php in /bitrix/php_interface/include/.

    Let us use the following condition to make sure that the detailed description of the element is entered:

    if (strlen($_POST['DETAIL_TEXT'])<=0)
       $error = new _CIBlockError(2, 'DESCRIPTION_REQUIRED', 'Add article text');

    The _CIBlockError object constructor admits 3 parameters: error severity level, arbitrary identifier, and the error message. If the value of this object is set in the variable $error on the editing page, the changes made will not be saved. To prevent a loss of the values that come from the form, you have to initialize the variable $bVarsFromForm=true after initializing the variable $error. It is the variable $bVarsFromForm that indicates that the values to be displayed in the fields must be those that came from the form.

    Let us use the function BXIBlockAfterSave to automatically create a small picture based on the larger one. If we define it before saving the element, it will be retrieved automatically once the element is saved successfully. Let us define it in the beginning of the file /bitrix/php_interface/include/before_iblock_element_edit.php:

    <?
    function BXIBlockAfterSave($arFields)
    {
            $dbr = CIBlockElement::GetByID($arFields['ID']);
            if(($ar = $dbr->Fetch()) && $ar['DETAIL_PICTURE']>0)
            {
                $img_path = $_SERVER['DOCUMENT_ROOT'].CFile::GetPath($ar['DETAIL_PICTURE']);
                $width = 200;
                $height = 200;
                list($width_orig, $height_orig) = getimagesize($img_path);
                if($width && ($width_orig < $height_orig))
                   $width = ($height / $height_orig) * $width_orig;
                else
                   $height = ($width / $width_orig) * $height_orig;
                $image_p = imagecreatetruecolor($width, $height);
                $image = imagecreatefromjpeg($img_path);
                imagecopyresized($image_p, $image, 0, 0, 0, 0, $width, $height, $width_orig, $height_orig);
                $new_img_path = tempnam("/tmp", "FOO").".jpg";
                imagejpeg($image_p, $new_img_path);
                $be = new CIBlockElement();
                $be->Update($arFields['ID'], Array('PREVIEW_PICTURE' => CFile::MakeFileArray($new_img_path)), false);
                @unlink($new_img_path);
            }
    }
    ?>

    Note: In the aforementioned script, a preview picture will be created based on the Detailed picture, and thus the created picture will be substituted in the field Preview picture. The example works only with JPG images.

    Let us look at the complete code of the page /bitrix/php_interface/include/before_block_element_edit.php:

    <?
    if($REQUEST_METHOD=="POST" && strlen($Update)>0 && $view!="Y" && (!$error) && empty($dontsave) && strlen($_POST['DETAIL_TEXT'])<=0)
       $error = new _CIBlockError(2, "DESCRIPTION_REQUIRED", "Add article text");
    
    function BXIBlockAfterSave($arFields)
    {
            $dbr = CIBlockElement::GetByID($arFields['ID']);
            if(($ar = $dbr->Fetch()) && $ar['DETAIL_PICTURE']>0)
            {
                $img_path = $_SERVER['DOCUMENT_ROOT'].CFile::GetPath($ar['DETAIL_PICTURE']);
                $width = 200;
                $height = 200;
                list($width_orig, $height_orig) = getimagesize($img_path);
                if($width && ($width_orig < $height_orig))
                   $width = ($height / $height_orig) * $width_orig;
                else
                   $height = ($width / $width_orig) * $height_orig;
                $image_p = imagecreatetruecolor($width, $height);
                $image = imagecreatefromjpeg($img_path);
                imagecopyresized($image_p, $image, 0, 0, 0, 0, $width, $height, $width_orig, $height_orig);
                $new_img_path = tempnam("/tmp", "FOO").".jpg";
                imagejpeg($image_p, $new_img_path);
                $be = new CIBlockElement();
                $be->Update($arFields['ID'], Array('PREVIEW_PICTURE' => CFile::MakeFileArray($new_img_path)), false);
                @unlink($new_img_path);
            }
    }
    ?>

    Important! Do not forget to specify the path to this file in the infoblock settings.

    Sometimes completely different changes must be made, for example the form of entry and change of several pictures simultaneously. In this case, we just have to create a new page and add it to the administrative menu.

    Errors When Working with Infoblocks

    Error of the Type:

    Fatal error: Class 'CIBlockElement' not found in /hosting/site.com/www/index.php on line XX

    The script uses the method of the Information Blocks module, but the module itself is not connected. The module should be connected:

    CModule::IncludeModule("iblock");

    Error of the Type:

    Fatal error: Call to a member function GetNextElement() on a non-object in /hosting/site.com/www/index.php on line XX

    You are likely to have submitted wrong parameters to a method. For example:

    $res = CIBlockElement::GetList(array(), $arFilter, array(), array(), $arSelect);

    While the third parameter must be true/false, and not array.

    Examples

    This chapter provides for examples on using the Information Blocks module.

    Sorting of infoblock elements without using the filter component

    If simple components are used to publish an information block, elements can be sorted without using the Filter component and without customizing the component used to display the list of elements. Such sorting is based on the use of the parameter Name of the array with values used to filter elements and is available in the following components: bitrix:catalog.section, bitrix:catalog.sections.top and bitrix:news.list.

    Filter array can be defined directly on the page before connecting a list component. However, in this case, you will have to create several pages placing a component and determining the filter array on each page. There is a simpler way: variables of the filter array may be submitted in a link.

    In our example, variables for the filter will be submitted in a link using the GETmethod, and the filter $arrFilter will be determined from the array $_GET. Infoblock element publishing will be performed using the list component Section elements Section elements (bitrix:catalog.section).

    Let us assume that we have the Products infoblock, and we will sort its elements using the property Manufacturer (MANUFACTURER):

    Let us create a start page with a set of links (in our case, it will be a list of manufacturer countries):

    The page code will be as follows::

    <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");?>
    <p>Filter by manufacturer:</p>
    
    <ul>
    <li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Germany">Germany</a></li>
    <li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Italy">Italy</a></li>
    <li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Holland">Holland</a></li>
    <li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Ukraine">Ukraine</a></li>
    <li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Austria">Austria</a></li>
    <li><a href="/catalog/filter.php?SECTION_ID=2&MANUFACTURER=Sweden">Sweden</a></li>
    </ul>
    <?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>
    

    Now let us create the page filter.php and place the component (bitrix:catalog.section) there. Then, we should set a necessary infoblock in the component settings and fill in the field Name of the array with values used to filter elements with the value arrFilter.

    Before connecting the component, let us add the following code:

    $manufacturer = $_GET["MANUFACTURER"]; 
    $arrFilter=array("PROPERTY"=>array("MANUFACTURER"=>"$manufacturer")); 
    

    As a result, when passing from the home page (e.g., following the link Italy), the list of goods of the section with identifier 2 manufactured in Italy will open:

    Examples of work with multiple properties

    Task 1: Delete one of the values of a multiple property of the infoblock element.

    Solution:

    $el = new CIBlockElement;
    $PROP = array();
    $PROP[property_id][id] = "4";  
    $PROP[property_id][id] = "5";
    $PROP[property_id][id] = "6";
    
    $arLoadProductArray = Array(
      "IBLOCK_ID" => $B_ID,
      "PROPERTY_VALUES" => $PROP,
      "NAME" => "Element",
      );
    
    $PRODUCT_ID = $E_ID;
    $res = $el->Update($PRODUCT_ID, $arLoadProductArray);
    

    In this case, in order to delete the value, it is sufficient to exclude the pair: key and value of the property to be deleted from the array $PROP. This solution is appropriate when the property value id must be left unchanged.

    $PROP[property_id ][id ]

    Alternatively, the method SetPropertyValues may be used as a solution:

    CIBlockElement::SetPropertyValues($ELEMENT_ID, $IBLOCK_ID, $PROPERTY_VALUE, $PROPERTY_CODE);

    False shall be submitted to the fourth parameter of the function, and the «property code»=>»value» array – to the third parameter.

    In this case, all of the values will be deleted except for those indicated in the array submitted to the third parameter.

    Task 2: Adding a specific value for a multiple property of the file type:

    Solution:

    //68 – property id;
    //FILES – symbol code of a multiple property of the file type;
    
    $ELEMENT_ID = 392;
    $arFile = CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/help.gif");
    $arFile["MODULE_ID"] = "iblock";
    
    $PROPERTY_VALUE["68"][n0] = $arFile;  
    CIBlockElement::SetPropertyValueCode($ELEMENT_ID, "FILES", Array("VALUE"=>$arFile) ) );
    

    Task 3: Adding several values for a multiple property of the file type:

    Solution:

    $arFile = array(
    0 => array("VALUE" => CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/01.gif"),"DESCRIPTION"=>""),
    1 => array("VALUE" => CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/help.gif"),"DESCRIPTION"=>"")
    );
    CIBlockElement::SetPropertyValuesEx($ELEMENT_ID, $IBLOCK_ID, array($PROPERTY_CODE => $arFile));
    

    Task 4: Deleting a specific value of a multiple property of the file type:

    Solution:

    //68 – property id;
    //FILES – symbol code of a multiple property of the file type;
    //2033 – property value id;
    
    $ELEMENT_ID = 392;
    $arFile["MODULE_ID"] = "iblock";
    $arFile["del"] = "Y";
    
    $PROPERTY_VALUE["68"]["2033"] = $arFile; 
    CIBlockElement::SetPropertyValueCode($ELEMENT_ID, "FILES", Array ("2033" => Array("VALUE"=>$arFile) ) );
    

    Task 5: Updating a specific value of a multiple property of the file type файл:

    Solution:

    //68 – property id;
    //FILES – symbol code of a multiple property of the file type;
    //2033 – property value id;
    
    $ELEMENT_ID = 392;
    $arFile = CFile::MakeFileArray($_SERVER["DOCUMENT_ROOT"]."/images/help.gif");
    $arFile["MODULE_ID"] = "iblock";
    $arFile["del"] = "Y";
    
    $PROPERTY_VALUE["68"]["2033"] = $arFile;  
    CIBlockElement::SetPropertyValueCode($ELEMENT_ID, "FILES", Array ("2033" => Array("VALUE"=>$arFile) ) );
    

    Task 6: Setting a multiple property of the string type with a value description field:

    Solution using SetPropertyValueCode:

    $arValues = array(
      0 => array("VALUE"=>"value","DESCRIPTION"=>"value description"),
      1 => array("VALUE"=>"value2","DESCRIPTION"=>"value description2") 
    );  
    CIBlockElement::SetPropertyValueCode($IBLOCK_ID, $PROP_CODE, $arValues);  
    

    Solution using SetPropertyValuesEx:

    $PROPERTY_VALUE = array(
      0 => array("VALUE"=>"value","DESCRIPTION"=>"value description"),
      1 => array("VALUE"=>"value2","DESCRIPTION"=>"value description2") 
    );
    CIBlockElement::SetPropertyValuesEx($ELEMENT_ID, $IBLOCK_ID, array($PROPERTY_CODE => $PROPERTY_VALUE));
    

    Task 7: Updating a multiple property of the text type without changing the DESCRIPTION:

    Solution:

    CIBlockElement::SetPropertyValues($nProductID, $nIblockID, array(
                 array(
                     "VALUE" => array(
                         "TEXT"=>time(),
                         "TYPE"=>"HTML"
                      ),
                     "DESCRIPTION"=>"111"),
                 array(
                     "VALUE" => array(
                         "TEXT"=>time(),
                         "TYPE"=>"HTML"
                      ),
                     "DESCRIPTION"=>"222"),
                ), $prop['ID']);
    

    Copy element field values to properties

    Let us consider an example of the function which copies the field values of infoblock elements ($_FROM_FIELD_NAMES) to the properties of the infoblock elements ($TO_PROPERTY_NAMES).

    The fields Date (DATE_ACTIVE_FROM) and Activity End (DATE_ACTIVE_TO) will be copied to the properties DATE_BEGIN and DATE_END of infoblock elements with ID = 22:

     function copy_from_fields_to_propertys_values( $IBLOCK_ID, $_FROM_FIELD_NAMES, $TO_PROPERTY_NAMES ){
        /* *
         * $_FROM_FIELD_NAMES = array(DATE_ACTIVE_FROM, DATE_ACTIVE_TO);
         * $TO_PROPERTY_NAMES = array(DATE_BEGIN, DATE_END);
         * copy_from_fields_to_propertys_values(22, array("DATE_ACTIVE_FROM","DATE_ACTIVE_TO"), array("DATE_BEGIN","DATE_END"));
         * */
            if ( CModule::IncludeModule ( "iblock" ) ){
                $arOrder = array(
                    "sort" => "ASC",
                );
    
                $arFilter = array(
                    "IBLOCK_ID" => $IBLOCK_ID,
                );
    
                foreach ( $TO_PROPERTY_NAMES as $property_name ) {
                    $TO_PROPERTY_NAMES_with_prop[] = 'PROPERTY_' . $property_name;
                }
    
                $arSelect = array(
                    "NAME",
                    "ID"
                );
    
                $arSelect = array_merge ( $arSelect, $_FROM_FIELD_NAMES, $TO_PROPERTY_NAMES_with_prop );
    
                $db_elemens = CIBlockElement::GetList ( $arOrder, $arFilter, false, false, $arSelect );
    
                while ( $arElement = $db_elemens->Fetch () ) {
                    $PRODUCT_ID = $arElement["ID"];
    
                    foreach ( $TO_PROPERTY_NAMES as $key => $property_name ) {
                        CIBlockElement::SetPropertyValues ( $PRODUCT_ID, $IBLOCK_ID, $arElement[$_FROM_FIELD_NAMES[$key]], $property_name );
                    }
                }
    
            } else {
                die( "The iblock module is not set" );
            }
        }
    

    Additional Information

    • Documentation to the method CIBlockElement::SetPropertyValues

    Obtaining the Sum of Field Values of Related Infoblocks

    Let us assume that we have related infoblocks and we face the following task: to obtain a sum of field values of related elements in a specific field of an infoblock element.

    Solution is possible both with using the module e-Store or without it. If the e-Store module is used, all of the infoblocks must have properties of a trade catalog with an indication of the price in the appropriate fields. If the version without the e-Store module is used, all of the fields of the source infoblocks must have the number type and (as a part of this solution) have the PRICE code.

    Let us assume that the resulting infoblock is called COST. It must have the number type. The following expression must be entered in the field “Default values” of the COST parameter:

    • {PROP_1_PRICE}+{PROP_2_PRICE}+… — for versions without e-Store.
    • {PROP_1} + {PROP_2}+… — for versions with e-Store.

    The code below is provided to obtain results in the component catalog.section. Code modification is required in order to display results in another component. The code provided shall be entered in the file result_modifer.php of the indicated component:

    <?
    //This string can be uncommented and see the contents arResult
    //of the component for which this modifier is to be adapted
    
    //echo "<pre>",htmlspecialchars(print_r($arResult, 1)),"</pre>";
    
    
    //The symbol code of a property with a default value containing expressions
    //the calculation result will be displayed by the component template
    //The expression itself represents a PHP code executable by eval
    //in which specific values will be substituted to the templates of the type {}
    //These properties must be selected in the component settings and available through arResult
    //otherwise, the function CIBlockElement::GetProperty must be used to access the database
    //These properties must NOT be multiple
    //Example of the expression: "({PROP_1_PRICE} + {PROP_2_PRICE}) * {PROP_COUNTER}"
    //Please pay attention to the _PRICE – it is an indication that the price of a related element must be selected! 
    //The property itself must have a symbol code PROP_1 and PROP_2, accordingly
    
    
    $CALCULATOR_CODE="COST";
    //ID of the price that will be retrieved for calculations
    $PRICE_ID = 1;
    //Infoblock identifier (for different components different fields of arResult must be chosen)
    $IBLOCK_ID = $arResult["IBLOCK_ID"];
    //We obtain metadata of the “Calculator” property (COST)
    $arProperty = CIBlockProperty::GetPropertyArray($CALCULATOR_CODE, $IBLOCK_ID);
    //If there is such property and the expression for calculation is set:
    if($arProperty && strlen($arProperty["DEFAULT_VALUE"]) > 0)
    {
       //The cycle for all the elements of the catalog
       foreach($arResult["ITEMS"] as $i => $arElement)
       {
          //We take the “Calculator”’s expression
          $EQUATION = $arProperty["DEFAULT_VALUE"];
          //Check if template substitution is necessary
          if(preg_match_all("/(\{.*?\})/", $EQUATION, $arMatch))
          {
             //Cycle for all of the properties used in the expression
             $arPropCodes = array();
             foreach($arMatch[0] as $equation_code)
             {
                //This is the "price"
                $bPrice = substr($equation_code, -7)=="_PRICE}";
                //Symbol code of the property which value will be substituted in the expression
                $property_code = ($bPrice? substr($equation_code, 1, -7): substr($equation_code, 1, -1));
    
                if($bPrice)
                {
                   //Let us find a related element
                   $rsLinkedElement = CIBlockElement::GetList(
                      array(),
                      array(
                         "=ID" => $arElement["PROPERTIES"][$property_code]["~VALUE"],
                         "IBLOCK_ID" => $arElement["PROPERTIES"][$property_code]["~LINK_IBLOCK_ID"]
                      ),
                      false, false,
                      array(
                         "ID", "IBLOCK_ID", "CATALOG_GROUP_".$PRICE_ID, "PROPERTY_PRICE"               )
                   );
                   $arLinkedElement = $rsLinkedElement->Fetch();
                   //We borrow its price
                   if($arLinkedElement["CATALOG_PRICE_".$PRICE_ID])
                      $value = doubleval($arLinkedElement["CATALOG_PRICE_".$PRICE_ID]);
                   else
                      $value = doubleval($arLinkedElement["PROPERTY_PRICE_VALUE"]);
                }
                else
                {
                   //If we require not only numbers but also strings
                   //get rid of doubleval and add screening of string symbols
                   $value = doubleval($arElement["PROPERTIES"][$property_code]["~VALUE"]);
                }
                //Value substitution
                $EQUATION = str_replace($equation_code, $value, $EQUATION);
             }
    
          }
          //Calculation proper
          $VALUE = @eval("return ".$EQUATION.";");
          //and saving its result to be displayed in the template
          $arResult["ITEMS"][$i]["DISPLAY_PROPERTIES"][$CALCULATOR_CODE] = $arResult["ITEMS"][$i]["PROPERTIES"][$CALCULATOR_CODE];
          $arResult["ITEMS"][$i]["DISPLAY_PROPERTIES"][$CALCULATOR_CODE]["DISPLAY_VALUE"] = htmlspecialchars($VALUE);
       }
    }
    ?>
    

    ORM

    ORM (Object-relational mapping) is a programming technique that connects databases with the concepts of object-oriented programming languages creating a «virtual object database» (Wikipedia).

    Introduction

    In the old core, its own GetList, Update, Add, and Delete are programmed for each entity.

    Deficiencies of such method:

    • Different set of parameters;
    • Different syntax of the filter fields;
    • Events may be existent or non-existent;
    • Sometimes different code for different databases (Add).

    New Core D7:

    To make database selecting and saving operations uniform with the same parameters and filters. If possible, entity tables must be serviced with a minimum of a new code. Standard events of adding/changing/deleting must be available automatically.

    The following notions were introduced to implement these purposes:

    1. Entities (BitrixMainEntityBase).
    2. Entity fields (BitrixMainEntityField and its successors).
    3. Data manager (BitrixMainEntityDataManager).

    An entity describes a table in the database, and also contains entity fields. The DataManager performs selection and entity change operations. In practice, the work is mostly done at the level of the DataManager.

    Attention! Before starting development, ensure that the module you selected has classes methods from core D7. It can be checked by available description in the documentation D7.

    Note: This chapter provides abstract examples for purposes of clarification. Using the provided examples via copypaste may not render the desired result for your specific project. Examples must be adapted for your specific code and database structure.

    Entity concept and description

    When creating Internet-based projects using the Bitrix Framework platform, vast functional capabilities are available out of the box for use through API calls for relevant modules. In this case, each module in the framework concept is a separate working unit which ensures a solution for a certain range of tasks.

    As a rule, the API of each module is designed based on specific tasks, and quite often call formats are different for each module. The basic functionality existing in almost every module is standardized in order to minimize these differences. These are CRUD operations: Create, Read, Update, and Delete (creating, reading, updating, and deleting data).

  • Entity concept
  • Field typing
  • Primary & autoincrement & required
  • Column name mapping
  • ExpressionField expressions
  • User fields
  • Example
  • Concept of Entities

    An entity is an aggregate of a set of objects with their intrinsic basic (low level) business logic. An entity has a set of properties which values are subject to specific processing rules.

    For example, the entity of the User means numerous users with a set of fields:

    • ID
    • First name
    • Last name
    • Password
    • Login
    • etc.

    At the same time, the database issues an ID automatically, First Name and Last Name are limited to 50 characters, Login must contain only Latin letters, numbers, and underscore sign, etc.

    Instead of programming each entity, we would rather describe it in a specific format. Such a description would be processed by the system core and would represent some sort of configuration for it:

    Book
      ID int [autoincrement, primary]
      ISBN str [match: /[0-9-]+/]
      TITLE str [max_length: 50]
      PUBLISH_DATE date

    For example, a similar description can be used for a book catalog where the system itself will monitor the correctness and integrity of the data checking of the format and the correct range of acceptable values.

    Field Typing

    Tag tools (xml, yml, and similar) are not used for the configuration of entities, php is used instead. This option provides for better development opportunities and flexibility.

    The determination of data types from the example above is as follows:

    namespace SomePartnerMyBooksCatalog;
    
    use BitrixMainEntity;
    
    class BookTable extends EntityDataManager
    {
    	public static function getTableName()
    	{
    		return 'my_book';
    	}
    	
    	public static function getMap()
    	{
    		return array(
    			new EntityIntegerField('ID'),
    			new EntityStringField('ISBN'),
    			new EntityStringField('TITLE'),
    			new EntityDateField('PUBLISH_DATE')
    		);
    	}
    }

    Attention! Notwithstanding the fact that the example means a Book as an entity, the class name is followed by a postfix: BookTable. It is intended, because the name of a descriptive class of an entity must always be completed with the word Table. The main name Book in the same namespace is considered reserved, and in the future the main name is expected to be used (in this case, the Book class) to represent entity elements as objects (at present, these entities are represented by arrays as in the old getList methods).

    The method getMap() is responsible for the description of the entity structure. It returns an array to field instances.

    Each type of the field is represented as an inheritor class EntityScalarField. These fields operate simple scalar values which are saved in the database ‘as is’. By default, 8 of such types are available:

    • Integral number
    • Number
    • Line
    • Text
    • Date
    • Date/Time
    • Yes/No
    • Value from the list

    For the sake of consistency with code writing standards, field names should be uppercase. The names must be unique within the same entity.

    As a rule, the first parameter in the field designer is the field name, and the second parameter contains additional settings. General settings will be dealt with later on in the same chapter, but there is a specific setting for BooleanField and EnumField:

    new EntityBooleanField('NAME', array(
    	'values' => array('N', 'Y')
    ))
    
    new EntityEnumField('NAME', array(
    	'values' => array('VALUE1', 'VALUE2', 'VALUE3')
    ))

    Since true and false cannot be stored as such in the database, a mapping of values is created in the form of an array for BooleanField, where the first element replaces false and the second replaces true at the time of saving.

    Note: when describing the entity the table name can be set in the method getTableName. In our example, it is my_book. If this method is not defined, the table name will be generated automatically from the namespace and class name, and for this entity it will result in b_somepartner_mybookscatalog_book.

    Primary & autoincrement & required

    In the majority of cases, the entity has a primary key for one field. The same key is, as a rule, autoincremental. To inform an entity about it, we have to use parameters in the field designer:

    new EntityIntegerField('ID', array(
    	'primary' => true
    ))

    Note: a composite primary key is also possible. For example, in the relations of two entities, the IDs of both entities will form a composite key. To learn more about this and see an example, please refer to the section N:M relations.

    This is the way to announce the ownership of a field to the primary key. Thanks to this option, the entity will control data entering and will not permit adding an entry without specifying the value for the primary key. During the update and deletion of entries it will be possible to identify them using the primary key only.

    The ID value is often not indicated but rather obtained from the database after successfully adding an entry. In this case, the entity must be informed about that:

    new EntityIntegerField('ID', array(
    	'primary' => true,
    	'autocomplete' => true
    ))

    The flag autocomplete for an entity means that when adding a new entry the developer is not required to set values for this field. By default, this requirement is applicable only to the primary key fields, but it is possible to ask the system to also insist on setting any other field:

    new EntityStringField('ISBN', array(
    	'required' => true
    ))

    Now, it will not be possible to add a new book without specifying its ISBN code.

    Column Name Mapping

    When describing entities for an already existing table you may wish to rename the column. For example, initially the ISBN field in the table my_book is named as ISBNCODE, and the old code uses this column name in SQL-query. If you wish to optimize the name, making it a more readable ISBN in the new API, you may use the parameter column_name:

    new EntityStringField('ISBN', array(
    	'required' => true,
    	'column_name' => 'ISBNCODE'
    ))

    There are other instances when the same physical column of the table stores values that differ in meaning. In this case, we can create several entity fields with the same column_name.

    ExpressionField Expressions

    The system provides for the storage of data as is as well as its transformation during sampling. Let us assume that we need to obtain the age of a book in days at the same time when we obtain the issue date. This number is difficult to store in the database since, in this case, we would have to recalculate and update data daily. The age can be calculated on the side of the database:

    SELECT DATEDIFF(NOW(), PUBLISH_DATE) AS AGE_DAYS FROM my_book

    For this, it is necessary to describe a virtual field in the entity whose value is based on a SQL expression with other field(s):

    new EntityExpressionField('AGE_DAYS',
    	'DATEDIFF(NOW(), %s)', array('PUBLISH_DATE')
    )

    As with the rest of fields, the first parameter to set is the name. The second parameter is the text of an SQL expression, but in this case other entity fields must be replaced with placeholders according to the format sprintf. The third parameter should transmit the array with entity field names in a certain order set in the expression.

    Note: %s or %1$s, %2$s, etc. are recommended as placeholders. For example, when several fields (FIELD_X + FIELD_Y) * FIELD_X participate in the expression EXPR, the expression can be described as follows:
    '(%s + %s) * %s', [FIELD_X, FIELD_Y, FIELD_X];
    or:
    '(%1$s + %2$s) * %1$s', [FIELD_X, FIELD_Y].

    Very often, expressions can be used to aggregate data (for example, COUNT(*) or SUM(FIELD)). These examples will be reviewed in the chapter Data Retrieval.

    Note: expression fields can be used only in data sampling to select, filter, group, and sort according to them. Since such columns do not exist physically in the database table, there is no place to write the field value, and the system will generate an exception.

    User-Defined (Custom) Fields

    In addition to the fields ScalarField and ExpressionField, the entity may contain User-Defined Fields. They are configured through the Administrative interface and require no additional description on the entity side. Only a selected Object of the user-defined field must be indicated in the entity:/p>

    class BookTable extends EntityDataManager
    {
    	...
    	
    	public static function getUfId()
    	{
    		return 'MY_BOOK';
    	}
    	
    	...
    }

    Note: Support for SqlExpression values for user fields in ORM is added starting from the Main module version 20.5.200 (main).

    Later on, it is this identifier that must be indicated when attaching user-defined fields to an entity:

    Thus, it is possible to select and update values of user-defined fields on the same basis as values of standard fields of an entity.

    Example

    The following entity was obtained following this chapter:

    namespace SomePartnerMyBooksCatalog;
    
    use BitrixMainEntity;
    
    class BookTable extends EntityDataManager
    {
    	public static function getTableName()
    	{
    		return 'my_book';
    	}
    	
    	public static function getUfId()
    	{
    		return 'MY_BOOK';
    	}
    
    	public static function getMap()
    	{
    		return array(
    			new EntityIntegerField('ID', array(
    				'primary' => true,
    				'autocomplete' => true
    			)),
    			new EntityStringField('ISBN', array(
    				'required' => true,
    				'column_name' => 'ISBNCODE'
    			)),
    			new EntityStringField('TITLE'),
    			new EntityDateField('PUBLISH_DATE')
    		);
    	}
    }
    
    // code to create a table in MySQL
    // (obtained by calling BookTable::getEntity()->compileDbTableStructureDump())
    CREATE TABLE `my_book` (
    	`ID` int NOT NULL AUTO_INCREMENT,
    	`ISBN` varchar(255) NOT NULL,
    	`TITLE` varchar(255) NOT NULL,
    	`PUBLISH_DATE` date NOT NULL,
    	PRIMARY KEY(`ID`)
    );

    Therefore, it is possible to describe regular scalar fields, single out a primary key from among them, and indicate the autoincrementive fields and the required fields. If there is a discrepancy between the column name in the table and a desired name in the entity, it can be fixed.

    Attention! The getMap method is used only as a means to obtain the primary configuration of an entity. If you want to obtain the actual list of entity fields, please use the method BookTable::getEntity()->getFields().

    The only thing left is to record the entity code in the project. According to the general file naming rules in D7, the entity code must be stored in the file: local/modules/somepartner.mybookscatalog/book.php

    After that, the system will automatically connect the file upon finding calls of the BookTable class.

    Note: the example above uses the recommended data entry. The legacy entry form as an array:

    'ID' => array(
    'data_type' => 'integer',
    'primary' => true,
    'autocomplete' => true,
    ),

    has been kept for compatibility. Upon initialization, objects of classes BitrixMainEntity* are still created. You can use both variants, but recommended and correct variant is via objects.

    Operations with Entities

    There are three methods available for writing inside the described class: BookTable::add, BookTable::update, BookTable:delete.

  • BookTable::add
  • BookTable::update
  • BookTable::delete
  • Validators
  • Events
  • Value formatting
  • Calculated values
  • Error warnings
  • BookTable::add

    The add-entry method admits as an input parameter an array with values containing entity field names as the keys:

    namespace SomePartnerMyBooksCatalog;
    
    use BitrixMainType;
    
    $result = BookTable::add(array(
    	'ISBN' => '978-0321127426',
    	'TITLE' => 'Patterns of Enterprise Application Architecture',
    	'PUBLISH_DATE' => new TypeDate('2002-11-16', 'Y-m-d')
    ));
    
    if ($result->isSuccess())
    {
    	$id = $result->getId();
    }

    The method returns the result object EntityAddResult, and the example above shows how to check the successful adding of an entry and obtain the ID of the added entry.

    Note. The objects of the class BitrixMainTypeDate and BitrixMainTypeDateTime must be used as values of the fields DateField and DateTimeField and also for user-defined fields Date and Date with Time. By default, the designer receives a line date in the website format but the format of the date to be submitted can also be indicated explicitly.

    BookTable::update

    Entry update follows a similar procedure; only the value of the primary key is added to the array of values in the parameters:

    $result = BookTable::update($id, array(
    	'PUBLISH_DATE' => new TypeDate('2002-11-15', 'Y-m-d')
    ));

    In the example, the date indicated in the new entry is corrected. As a result, the object EntityUpdateResult is returned, and it also has a test method isSuccess() (to make sure that there was no errors in the query) and, additionally, it is possible to learn whether the entry was actually updated: getAffectedRowsCount().

    BookTable::delete

    Only the primary key is needed to delete the record:

    $result = BookTable::delete($id);

    Operation Results

    If one or more errors occur during the operation, their text can be obtained from the result:

    $result = BookTable::update(...);
    
    if (!$result->isSuccess())
    {
    	$errors = $result->getErrorMessages();
    }

    Default Values

    Sometimes the majority of new entries always contain the same value for a certain field or it is calculated automatically. Let us assume that the book catalog has today’s date as the issue/publishing date by default (it is logical to add a book to the catalog on the day of its issue). Let us return to the description of the field in the entity and use the parameter default_value:

    new EntityDateField('PUBLISH_DATE', array(
    	'default_value' => new TypeDate
    ))

    Now, when adding an entry with no expressly indicated issue date, its value will be the current day:

    $result = BookTable::add(array(
    	'ISBN' => '978-0321127426',
    	'TITLE' => 'Some new book'
    ));

    Let us consider a more complicated task: there is no possibility to promptly add books on the day of their issue but it is known that, as a rule, new books are issued on Fridays. Accordingly, they will be added only in the course of the following week:

    new EntityDateField('PUBLISH_DATE', array(
    	'default_value' => function () {
    		// figure out last friday date
    		$lastFriday = date('Y-m-d', strtotime('last friday'));
    		return new TypeDate($lastFriday, 'Y-m-d');
    	}
    ))

    Any callable value can be the value of the parameter default_value: a function name, an array from class/object and a name of the method, or an anonymous function.

    Validators

    Before writing new data to the database their correctness must be checked without fail. This can be done with the help of validators:

    new EntityStringField('ISBN', array(
    	'required' => true,
    	'column_name' => 'ISBNCODE',
    	'validation' => function() {
    		return array(
    			new EntityValidatorRegExp('/[d-]{13,}/')
    		);
    	}
    ))

    Now each time you add or edit an entry, the ISBN will be checked using the template [d-]{13,} – the code must contain only numbers and a hyphen, with a minimum of 13 digits.

    Validation is set using the parameter validation in the field designer and is a callback that returns an array of validators.

    Note: Why validation – callback, and not just an array of validators? It is a kind of deferred load: validators will be instantiated only when data validation is really needed. Generally no validation is needed for data sampling from the database.

    An inheritor EntityValidatorBase or any callable that is to return a true or a text of an error, or an object of EntityFieldError (if you want to use own code of the error) is accepted as a validator.

    It is known for sure that the ISBN code must contain 13 digits, and these digits can be separated by several hyphens:

    978-0321127426
    978-1-449-31428-6
    9780201485677

    To make sure that there are exactly 13 digits, let us write our own validator:

    new EntityStringField('ISBN', array(
    	'required' => true,
    	'column_name' => 'ISBNCODE',
    	'validation' => function() {
    		return array(
    			function ($value) {
    				$clean = str_replace('-', '', $value);
    				
    				if (preg_match('/^d{13}$/', $clean))
    				{
    					return true;
    				}
    				else
    				{
    					return ‘The ISBN code must contain 13 digits.’;
    				}
    			}
    		);
    	}
    ))

    The value of this field is submitted to the validator as the first parameter, but more optional information is also available:

    new EntityStringField('ISBN', array(
    	'required' => true,
    	'column_name' => 'ISBNCODE',
    	'validation' => function() {
    		return array(
    			function ($value, $primary, $row, $field) {
    				// value – field value
    				// primary – an array with the primary key, in this case [ID => 1]
    				// row – all arrays of data submitted to ::add or ::update
    				// field – an object of the field under validation – EntityStringField('ISBN', ...)
    			}
    		);
    	}
    ))

    This set of data allows for a much wider range of complex checks.

    If several validators are attached to a field and there is a need to find out on the program level which of them exactly has worked, the error code can be used. For example, the last digit of the ISBN code is a control digit that serves to check the correctness of the numerical part of ISBN. You have to add a validator for checking it and to process its result in a specific way:

    // describing validator in the entity field
    new EntityStringField('ISBN', array(
    	'required' => true,
    	'column_name' => 'ISBNCODE',
    	'validation' => function() {
    		return array(
    			function ($value) {
    				$clean = str_replace('-', '', $value);
    
    				if (preg_match('/^d{13}$/', $clean))
    				{
    					return true;
    				}
    				else
    				{
    					return ‘ISBN code must contain 13 digits.’;
    				}
    			},
    			function ($value, $primary, $row, $field) {
    				// checking the last digit
    				// ...
    				// if the number is wrong, a special error is returned
    				return new EntityFieldError(
    					// if the number is wrong, a special error is returned
    				);
    			}
    		);
    	}
    ))
    // performing the operation
    $result = BookTable::update(...);
    
    if (!$result->isSuccess())
    {
    	// checking which errors have been revealed
    	$errors = $result->getErrors();
    	
    	foreach ($errors as $error)
    	{
    		if ($error->getCode() == 'MY_ISBN_CHECKSUM')
    		{
    			// our validator has worked
    		}
    	}
    }

    2 standard error codes are available by default: BX_INVALID_VALUE if the validator has worked, and BX_EMPTY_REQUIRED if no required field is indicated when adding an entry.

    Validators work both when adding new entries and when updating the existing entries. This behavior is based on the general purpose of validators consisting in guaranteeing correct and integral data in the database. The event mechanism is available in order to check data only upon their addition or updating and also for other manipulations.

    We recommend that you use standard validators in standard situations:

    • EntityValidatorRegExp – check by regular expression,
    • EntityValidatorLength – check the minimum/maximum line length,
    • EntityValidatorRange – check the minimum/maximum number value,
    • EntityValidatorUnique – check the uniqueness of a value.

    The validators described above cannot apply to the User-defined fields. Their values shall be configured in field settings through the administrative interface.

    Events

    In the example with validators, one of the checks for the ISBN field consisted in checking the availability of 13 digits. In addition to numbers, ISBN code may include hyphens, but technically speaking they have no value. In order to store only “clean” data in the database (13 digits only, without hyphens), we can use an internal event handler:

    class BookTable extends EntityDataManager
    {
    	...
    	
    	public static function onBeforeAdd(EntityEvent $event)
    	{
    		$result = new EntityEventResult;
    		$data = $event->getParameter("fields");
    
    		if (isset($data['ISBN']))
    		{
    			$cleanIsbn = str_replace('-', '', $data['ISBN']);
    			$result->modifyFields(array('ISBN' => $cleanIsbn));
    		}
    
    		return $result;
    	}
    }

    The method onBeforeAdd set up in the entity is automatically recognized by the system as a handler for the event “before addition” thus allowing change of data or additional checks to be done in it. In the example, we have changed the ISBN code using the method modifyFields.

    // before transformation
    978-0321127426
    978-1-449-31428-6
    9780201485677
    
    // after transformation
    9780321127426
    9781449314286
    9780201485677

    After such a transformation, we can return again to the neat validator RegExp instead of using an anonymous function (because we already know that the value will contain no acceptable hyphens and only numbers must remain):

    'validation' => function() {
    	return array(
    		//function ($value) {
    		//	$clean = str_replace('-', '', $value);
    		//
    		//	if (preg_match('/^d{13}$/', $clean))
    		//	{
    		//		return true;
    		//	}
    		//	else
    		//	{
    		//		return 'The ISBN code must contain 13 digits.';
    		//	}
    		//},
    		new EntityValidatorRegExp('/d{13}/'),
    		...
    	);
    }

    In addition to data change, the event handler makes it possible to delete data or even abort the operation. For example, let us assume that the updating of the ISBN code for the books that already exist in the catalog must be prohibited. It can be done in the event handler onBeforeUpdate using one of two ways:

    public static function onBeforeUpdate(EntityEvent $event)
    {
       $result = new EntityEventResult;
       $data = $event->getParameter("fields");
    
       if (isset($data['ISBN']))
       {
          $result->unsetFields(array('ISBN'));
       }
    
       return $result;
    }

    In this option, the ISBN will be deleted “with no fuss” as if it were not submitted. The second option consists in prohibiting its update and generating an error:

    public static function onBeforeUpdate(EntityEvent $event)
    {
    	$result = new EntityEventResult;
    	$data = $event->getParameter("fields");
    
    	if (isset($data['ISBN']))
    	{
    		$result->addError(new EntityFieldError(
    			$event->getEntity()->getField('ISBN'),
    			'Changing the ISBN code for the existing books is prohibited'
    		));
    	}
    
    	return $result;
    }

    If an error is returned, we have formed the object EntityFieldError in order for us to learn during subsequent error processing in which field, exactly, the check was activated. If the error applies to more than one field or to the entire entry, the use of the object EntityEntityError will be more appropriate:

    public static function onBeforeUpdate(EntityEvent $event)
    {
    	$result = new EntityEventResult;
    	$data = $event->getParameter("fields");
    
    	if (...) // comprehensive data check
    	{
    		$result->addError(new EntityEntityError(
    			'Impossible to update an entry'
    		));
    	}
    
    	return $result;
    }

    Two events were used in the examples: onBeforeAdd and onBeforeUpdate, there are nine such events in total:

    • onBeforeAdd (parameters: fields)
    • onAdd (parameters: fields)
    • onAfterAdd (parameters: fields, primary)
    • onBeforeUpdate (parameters: primary, fields)
    • onUpdate (parameters: primary, fields)
    • onAfterUpdate (parameters: primary, fields)
    • onBeforeDelete (parameters: primary)
    • onDelete (parameters: primary)
    • onAfterDelete (parameters: primary)

    The following diagram shows the sequence in which the event handlers are called, and the actions a handler may carry out.

    It goes without saying that these events can be handled in the entity itself as well as in the methods with the same name. In order to subscribe to an event in an arbitrary point of script execution, call for the event manager:

    $eventManager = MainEventManager::getInstance();
    $eventManager->addEventHandler(
    	"main",
    	"SomePartnerMyBooksCatalogBook::OnBeforeUpdate",
    	
    );

    Value formatting

    Sometimes it may become necessary to store data in one format and work with them in the program in another. The most common example: work with an array and its serialization before saving into the database. For this, the field parameters save_data_modification and fetch_data_modification are available. They are set up similarly to the validators through callback.

    Let us use the example of a book catalog in order to describe the text field EDITIONS_ISBN: it will store the ISBN codes of other editions of the book, if any:

    new EntityTextField('EDITIONS_ISBN', array(
    	'save_data_modification' => function () {
    		return array(
    			function ($value) {
    				return serialize($value);
    			}
    		);
    	},
    	'fetch_data_modification' => function () {
    		return array(
    			function ($value) {
    				return unserialize($value);
    			}
    		);
    	}
    ))

    We have indicated the serialization of the value before saving into the database in the parameter save_data_modification, and we have set up de-serialization during sampling from the database in the parameter fetch_data_modification. Now, when writing business logic you can simply work with the array without having to look into conversion issues.

    Attention! Before creating a serialized field, make sure the serialization will not interfere during filtering or linking tables. Search by a single value in WHERE among serialized lines is highly inefficient. You may want to opt for a normalized data storage scheme.

    Since serialization is the most typical example for conversion of values it is singled out into a separate parameter serialized:

    new EntityTextField('EDITIONS_ISBN', array(
    	'serialized' => true
    ))

    However, you can still describe your callables for other data modification options.

    Value calculating

    More often than not, developers have to implement counters where a new value is to be calculated on the database side for the sake of data integrity instead of selecting the old value and recalculating it on the application side. In other words, the queries of the following type must be executed:

    UPDATE my_book SET READERS_COUNT = READERS_COUNT + 1 WHERE ID = 1

    If the numeric field, READERS_COUNT is described in the entity, the counter increment can be launched as follows:

    BookTable::update($id, array(
    	'READERS_COUNT' => new DBSqlExpression('?# + 1', 'READERS_COUNT')
    ));

    The placeholder ?# means that the following argument in the designer is the database ID – the name of the database, table, or column, and this value will be masked appropriately. For all variable parameters, the use of placeholders is highly recommended. This approach will help to avoid problems with SQL injections.

    For example, if an increment number of readers is variable, it would be better to describe the expression as follows:

    // correct
    BookTable::update($id, array(
    	'READERS_COUNT' => new DBSqlExpression('?# + ?i', 'READERS_COUNT', $readersCount)
    ));
    
    // incorrect
    BookTable::update($id, array(
    	'READERS_COUNT' => new DBSqlExpression('?# + '.$readersCount, 'READERS_COUNT')
    ));

    The list of placeholders currently available:

    • ? or ?s – the value is masked in put between single quotes ‘
    • ?# – the value is masked as an identifier
    • ?i – the value is reduced to integer
    • ?f – the value is reduced to float

    Error warnings

    The examples above have a peculiarity that the data update query is called without checking the result.

    // call without checking successful query execution
    BookTable::update(...);
    
    // with check
    $result = BookTable::update(...);
    if (!$result->isSuccess())
    {
    	    // error processing
    }

    The second option is undoubtedly preferable from the point of view of control. However, if the code is executed only in the agent mode, we have no use for the list of errors occurred during validation. In this case, if the query has not gone through due to a “failed” validation and isSuccess() check was not called, the system will generate E_USER_WARNING with a list of errors which may be seen in the website log (provided that .settings.php is set up properly).

    In view of the results of this chapter, some changes have occurred in the entity description. It looks as follows now:

    namespace SomePartnerMyBooksCatalog;
    
    use BitrixMainEntity;
    use BitrixMainType;
    
    class BookTable extends EntityDataManager
    {
    	public static function getTableName()
    	{
    		return 'my_book';
    	}
    	
    	public static function getUfId()
    	{
    		return 'MY_BOOK';
    	}
    
    	public static function getMap()
    	{
    		return array(
    			new EntityIntegerField('ID', array(
    				'primary' => true,
    				'autocomplete' => true
    			)),
    			new EntityStringField('ISBN', array(
    				'required' => true,
    				'column_name' => 'ISBNCODE',
    				'validation' => function() {
    					return array(
    						new EntityValidatorRegExp('/d{13}/'),
    						function ($value, $primary, $row, $field) {
    							// check the last digit
    							// ...
    							// if the digit is incorrect we will return a special error
    							return new EntityFieldError(
    								$field, 'ISBN control digit does not match', 'MY_ISBN_CHECKSUM'
    							);
    						}
    					);
    				}
    			)),
    			new EntityStringField('TITLE'),
    			new EntityDateField('PUBLISH_DATE', array(
    				'default_value' => function () {
    					// figure out last friday date
    					$lastFriday = date('Y-m-d', strtotime('last friday'));
    					return new TypeDate($lastFriday, 'Y-m-d');
    				}
    			)),
    			new EntityTextField('EDITIONS_ISBN', array(
    				'serialized' => true
    			)),
    			new EntityIntegerField('READERS_COUNT')
    		);
    	}
    
    	public static function onBeforeAdd(EntityEvent $event)
    	{
    		$result = new EntityEventResult;
    		$data = $event->getParameter("fields");
    
    		if (isset($data['ISBN']))
    		{
    			$cleanIsbn = str_replace('-', '', $data['ISBN']);
    			$result->modifyFields(array('ISBN' => $cleanIsbn));
    		}
    
    		return $result;
    	}
    }

    Copy this code and play with all the options described above.

    Objects

    To start using the objects you need a described entity only. Just replace fetch with fetchObject in your code:

    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    

    Now $book — is a full-scale object of Book entity, having multiple methods for manipulating its data and establishing relations with other entities.

    Object class

    All entity objects are descendants of class BitrixMainORMObjectifyEntityObject, with each entity having its own class for objects. By default, such class is created automatically. When you have generated ORM class annotation, with IDE highlighting this moment:

    As seen above, EO_Book object class is located in the same namespace as the Table class, with the same name, but with instead of suffix Table has the prefix EO_ (abbreviated EntityObject). Such prefix is added for backward compatibility: existing projects already can have the class Book or structure

    use SomeAnotherBook;

    resulting in conflict of repeated use of Book word. We deemed the prefix EO_ as sufficiently unique. Particularly, when in a standard case use of _ character contradicts to code naming standards: no conflicts should be present with manually described classes.

    At the same time, we have allowed to conveniently customize your own, suitable class name and even place such class in another namespace, if required:

    //File bitrix/modules/main/lib/test/typography/book.php
    
    namespace BitrixMainTestTypography;
    
    class Book extends EO_Book
    {
    }
    

    Key moment — inheriting from object’s base virtual class EO_Book.

    Entity must be notified about a new class as follows:

    //File bitrix/modules/main/lib/test/typography/booktable.php
    
    namespace BitrixMainTestTypography;
    
    class BookTable extends BitrixMainORMDataDataManager
    {
    	public static function getObjectClass()
    	{
    		return Book::class;
    	}
    	//...
    }

    Now, the method fetchObject will return objects of class BitrixMainTestTypographyBook. And after re-generating annotations, the new class start showing IDE:

    Please be advised: defining your own custom classes is recommended only when you need to directly use the class name or for expanding the class with your extra features. It’s not recommended to use names for standard classes in your code, for example instanceof EO_Book, new EO_Book or EO_Book::class. In such cases, it’s preferable to describe your class with more «authentic» name that corresponds to naming standards, or to use impersonal methods BookTable::getObjectClass(), BookTable::createObject(), BookTable::wakeUpObject() and etc.

    You may not only add your own functionality in your class, but also re-define standard named methods. It’s not recommended to use descriptions for class properties with such names as primary, entity and dataClass because such names have been already used by the base class.

    Named methods

    Majority of methods are designated as named methods. This means that each field has the available set of personal methods:

    $book->getTitle();
    $book->setTitle($value);
    $book->remindActualTitle();
    $book->resetTitle();
    // and etc.
    

    You can overview full list of methods in other articles under this section.

    The approach mentioned above was selected due to several reasons:

    • incapsulation — you can control access individually to each field;
    • convenient use — no need to memorize field names for each entity by heart, IDE will remind you and will expedite input by autocompletion;
    • code readability — such entries look coherently and exhaustive.

    With this approach, all methods have an alternative in the form of universal methods, getting field name as one of arguments:

    $fieldName = 'TITLE';
    
    $book->get($fieldName);
    $book->set($fieldName, $value);
    $book->remindActual($fieldName);
    $book->reset($fieldName);
    // and etc.
    

    Such approach is convenient when field names are stored in memory and you can handle them in anonymized mode.

    The named method you have specified directly is called when describing your class for object and re-defining any named method via a universal method:

    namespace BitrixMainTestTypography;
    
    class Book extends EO_Book
    {
    	public function getTitle()
    	{
    		return 'custom title';
    	}
    }
    
    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    echo $book->getTitle(); // prints 'custom title'
    echo $book->get('TITLE'); // also prints 'custom title'
    

    Exception is the method fill: it doesn’t call the named method. Design-wise, this method is aimed at optimizing database handling. In case of simultaneously calling several fields — simultaneous calls for individual methods will create an excessive load.

    Internal implementation for named methods is based on magic-method __call. Code generation can act as an alternative: compiling of classes with all methods and their subsequent caching. Bitrix24 has selected the magic method due the following reasons:

    • lower memory consumption compared to code generation, when system requires handling of excessively cumbersome classes;
    • no longer required task of caching the generated classes and monitoring of entity updates.

    Disadvantage of magic-methods is an increased consumption of processor computing resources, which can be resolved in individual cases by directly specifying frequently used methods (as it’s done with the method getId in the base class). At the same time, such issue is the most easy to scale out, allowing to add new computing resources instead of a perpetual upgrade of a single operational computing power.

    Unavailable magic-methods and code generation would prevent automatic coverage of all fields from the class Table: you would need to describe all necessary fields and methods manually.

    Value types

    Objects convert values to field type. It means that values will have the value formats and string — the string formats:

    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    var_dump($book->getId());
    // prints int 1
    
    var_dump($book->getTitle());
    // prints string 'Title 1' (length=7)
    

    Please, be advised regarding to BooleanField: expects true or false, despite the fact that database could store other values:

    //(new BooleanField('IS_ARCHIVED'))
    //	->configureValues('N', 'Y'),
    
    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    var_dump($book->getIsArchived());
    // prints boolean true
    
    // set values are boolean as well
    $book->setIsArchived(false);
    

    Data read (get, require, remindActual, primary, collectValues, runtime)

    • get

      Data reading is implemented using several methods. The most simple returns a field value or null when value is missing (for example, when field is not specified in select when retrieved):

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      $title = $book->getTitle();
      
    • require

      When you are sure that field must be completed by a value and the scenario is not viable without this value, you can set this value as mandatory/required by the method require:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      $title = $book->requireTitle();
      

      In this case, the result requireTitle() won’t be different from the abovementioned getTitle(). And the next example will finish with thrown exception, because field won’t be containing a value:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1, ['select' => ['ID', 'PUBLISHER_ID', 'ISBN']])
      	->fetchObject();
      
      $title = $book->requireTitle();
      // SystemException: "TITLE value is required for further operations"
      
    • remindActual

      One more getter remindActual will be useful when re-setting value to differentiate the original value from the previously set value during the session and not yet saved in the database:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      echo $book->getTitle();
      // prints "Title 1"
      
      $book->setTitle("New title");
      
      echo $book->getTitle();
      // prints "New title"
      
      echo $book->remindActualTitle();
      // prints "Title 1"
      

      As an alternative, you can use universal unnamed methods:

      $fieldName = 'TITLE';
      
      $title = $book->get($fieldName);
      $title = $book->require($fieldName);
      $title = $book->remindActual($fieldName);
      
    • primary

      System «getter» primary is implemented as a virtual read-only property, to avoid using the method getPrimary(), thus reserving the PRIMARY field name with corresponding named getter method. Property returns primary key values in the array’s format independently from whether the primary key is or composite or singular:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      $primary = $book->primary;
      // returns ['ID' => 1]
      
      $id = $book->getId();
      // returns 1
      
    • collectValues

      Method collectValues is used to get all object values as an array.

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      $values = $book->collectValues();
      

      This example returns all available values. When some field values are re-set using the «setter», but not yet saved, returns these specific values. Unmodified fields source actual values.

      You can use optional filters to specify set of fields and data type:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      $values = $book->collectValues(BitrixMainORMObjectifyValues::ACTUAL);
      // returns only current values, without not yet saved values
      
      $values = $book->collectValues(BitrixMainORMObjectifyValues::CURRENT);
      // returns only current values, not yet saved in the database
      
      $values = $book->collectValues(BitrixMainORMObjectifyValues::ALL);
      // equals to calling collectValues() without parameters - first CURRENT, then ACTUAL
      

      Second argument passes the mask, similar to the used in fill, defining field types:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      $values = $book->collectValues(
      	BitrixMainORMObjectifyValues::CURRENT,
      	BitrixMainORMFieldsFieldTypeMask::SCALAR
      );
      // returns only updated scalar field values
      
      $values = $book->collectValues(
      	BitrixMainORMObjectifyValues::ALL,
      	BitrixMainORMFieldsFieldTypeMask::ALL & ~BitrixMainORMFieldsFieldTypeMask::USERTYPE
      );
      // returns values for all fields, except for user fields
      
    • runtime

      Only universal get is provided for runtime fields created within individual queries:

      $author = BitrixMainTestTypographyAuthorTable::query()
      	->registerRuntimeField(
      		new BitrixMainEntityExpressionField(
      			'FULL_NAME', 'CONCAT(%s, " ", %s)', ['NAME', 'LAST_NAME']
      		)
      	)
      	->addSelect('ID')
      	->addSelect('FULL_NAME')
      	->where('ID', 17)
      	->fetchObject();
      
      echo $author->get('FULL_NAME');
      // prints 'Name 17 Last name 17'
      

      Storing of such values is isolated from standard field values inside the object and, correspondingly, the rest of data handling methods are not applicable.

    Write (set, reset, unset)

    • set

      Setting value is performed in the familiar manner:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      $book->setTitle("New title");
      

      The object memorizes its original values. From this moment, the current value can be accessed via the main «getter» get and the database original/actual value can be accessed via auxiliary «getter» method remindActual:

      $book->getTitle(); // current value
      $book->remindActualTitle(); // actual database value
      

      Primary key can be set only in new objects, you cannot update it in existing objects. When it’s needed, you have to create a new object and delete the old one. Also, BitrixMainORMFieldsExpressionField fields cannot be set due to values being calculated automatically and cannot be modified from outside.

      When setting a value different from the current value, such value won’t be modified and won’t be included into SQL query when saving the object.

    • reset

      To cancel the new value and rollback to the old one, you can use the auxiliary «setter» method reset:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      echo $book->getTitle();
      // prints "Title 1"
      
      $book->setTitle("New title");
      
      echo $book->getTitle();
      // prints "New title"
      
      $book->resetTitle();
      
      echo $book->getTitle();
      // prints "Title 1"
      
    • unset

      One more auxiliary «setter» method unset deletes object as if it wasn’t retrieved from the database and wasn’t set at all:

      $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
      	->fetchObject();
      
      echo $book->getTitle();
      // prints "Title 1"
      
      $book->unsetTitle();
      
      echo $book->getTitle();
      // null
      

      «Setter» methods also have universal variants for calling with field title as an argument:

      $fieldName = 'TITLE';
      
      $book->set($fieldName, "New title");
      $book->reset($fieldName);
      $book->unset($fieldName);
      

      All actions to update the value result in updates during session only. Read more about how to save the object to register modifications in the database here: Create and edit (save, new).

    Checks (isFilled, isChanged, has)

    • isFilled

      The method isFilled is used to verify, if object contains a current actual value from the database:

      use BitrixMainTestTypographyBook;
      
      // Values from fetch* and wakeUp methods are deemed as actual 
      // the example demonstrates how only primary key is passed when initializing an object
      $book = Book::wakeUp(1);
      
      var_dump($book->isTitleFilled());
      // false
      
      $book->fillTitle();
      
      var_dump($book->isTitleFilled());
      // true
    • isChanged

      The method isChanged responds to question if a new value was set during the session:

      use BitrixMainTestTypographyBook;
      
      // object may have source value, but may not 
      // this doesn't affect the further behaviour
      $book = Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1']);
      
      var_dump($book->isTitleChanged());
      // false
      
      $book->setTitle('New title 1');
      
      var_dump($book->isTitleChanged());
      // true

      Such behaviour is applicable for new objects as well, until their values are stored in the database.

    • has

      The method has checks, if an object has a field value — an actual value from the database or value, specified during the session. In fact, its an abbreviation from isFilled() || isChanged():

      use BitrixMainTestTypographyBook;
      
      $book = Book::wakeUp(['ID' => 1, 'TITLE' => 'Title 1']);
      $book->setIsArchived(true);
      
      var_dump($book->hasTitle());
      // true
      
      var_dump($book->hasIsArchived());
      // true
      
      var_dump($book->hasIsbn());
      // false

    Object status

    Object can receive 3 statuses:

    • new, with data never before saved in the database;
    • actual, with data matching to the data stored in the database;
    • modified, with data different from the data stored in the database.

    You can check object status using the public read-only property state and class constants BitrixMainORMObjectifyState:

    use BitrixMainTestTypographyBook;
    use BitrixMainORMObjectifyState;
    
    $book = new Book;
    $book->setTitle('New title');
    
    var_dump($book->state === State::RAW);
    
    $book->save();
    
    var_dump($book->state === State::ACTUAL);
    
    $book->setTitle('Another one title');
    
    var_dump($book->state === State::CHANGED);
    
    $book->delete();
    
    var_dump($book->state === State::RAW);
    
    // true

    Create and edit (save, new)

    The method save is used for registering object updates in the database:

    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    $book->setTitle("New title");
    
    $book->save();
    

    Note: if you copy this example and attempt to execute it with a test entity from the namespace BitrixMainTestTypography, due to specifics of test data you will get an SQL error. You will see, however, that a portion of the query with test data is built correctly.

    From the moment of saving, all current object values are converted into actual values:

    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    echo $book->remindActualTitle();
    // prints "Title 1"
    
    $book->setTitle("New title");
    
    echo $book->remindActualTitle();
    // prints "Title 1"
    
    $book->save();
    
    echo $book->remindActualTitle();
    // prints "New title"
    

    Regarding the new objects, there are two aprroaches for creating them. The most readable approach — directly via instantiation:

    $newBook = new BitrixMainTestTypographyBook;
    $newBook->setTitle('New title');
    $newBook->save();
    
    $newAuthor = new BitrixMainTestTypographyEO_Author;
    $newAuthor->setName('Some name');
    $newAuthor->save();
    

    The method operates with both standard EO_ classes and re-defined classes. Even if you initially used EO_ class, and then decided to create your own class, you won’t have to re-write an existing code — backward compatibility will be saved automatically. System class with prefix EO_ becomes an «alias» to your class.

    More universal and applicable method to create new object, is to use entity’s factory:

    $newBook = BitrixMainTestTypographyBookTable::createObject();
    $newBook->setTitle('New title');
    $newBook->save();
    

    By design, new object sets all default values, described in the getMap «mapping». You can get a completely clean object by passing a corresponding argument in constructor:

    $newBook = new BitrixMainTestTypographyBook(false);
    $newBook = BitrixMainTestTypographyBookTable::createObject(false);
    

    Value status changes similarly as during editing. Before saving the object value is deemed as current, after saving in the database, it’s status becomes as the actual.

    Deleting action

    Use the delete() method for deletion:

    // delete record
    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    $book->delete();
    
    // deleting by primary key
    $book = BitrixMainTestTypographyBook::wakeUp(1);
    
    $book->delete();

    First only the record for this object is deleted from the database. When you need to delete or attempt to perform other actions with bindings, you will need direct actions.

    Deleting using the self-titled method of Table-class, triggers all the required Events. That’s why additional action can be described in the event handler onDelete.

    Restoring (wakeUp)

    When you already have available records, you don’t have to retrieve them from database again. Object can be restored having primary key values as a minimum:

    $book = BitrixMainTestTypographyBook::wakeUp(1);

    You may indicate not only the primary key, but also partial or full set of data:

    $book = BitrixMainTestTypographyBook::wakeUp(['ID' => 1, 'TITLE' => 'Title 1', 'PUBLISHER_ID' => 253]);

    Similar to creating objects, the method is applicable for EO_ classes as well, for calling directly from entity:

    // your class
    $book = BitrixMainTestTypographyBook::wakeUp(
    	['ID' => 1, 'TITLE' => 'Title 1']
    );
    
    // system class
    $book = BitrixMainTestTypographyEO_Book::wakeUp(
    	['ID' => 1, 'TITLE' => 'Title 1']
    );
    
    // using the entity factory
    $book = BitrixMainTestTypographyBookTable::wakeUpObject(
    	['ID' => 1, 'TITLE' => 'Title 1']
    );
    

    You can pass not only scalar values in wakeUp, but also values of Relations:

    $book = BitrixMainTestTypographyBook::wakeUp([
    	'ID' => 2,
    	'TITLE' => 'Title 2',
    	'PUBLISHER' => ['ID' => 253, 'TITLE' => 'Publisher Title 253'],
    	'AUTHORS' => [
    		['ID' => 17, 'NAME' => 'Name 17'],
    		['ID' => 18, 'NAME' => 'Name 18']
    	]
    ]);
    

    Filling action

    When not all object fields are completed with data and you need to fill in the missing data, do not use the following approach:

    // initially only ID and NAME are available
    $author = BitrixMainTestTypographyEO_Author::wakeUp(
    	['ID' => 17, 'NAME' => 'Name 17']
    );
    
    // we need to write LAST_NAME, retrieving it from database
    $row = BitrixMainTestTypographyAuthorTable::getByPrimary($author->getId(),
    	['select' => ['LAST_NAME']]
    )->fetch();
    
    // adding value to the object
    $author->setLastName($row['LAST_NAME']);
    

    In this case, the value will be deemed as a newly set, but not an actual value (which, theoretically can lead to unforeseen collisions in further object handling).

    The correct way to use the name method for object fill:

    // initially only ID and NAME are available
    $author = BitrixMainTestTypographyEO_Author::wakeUp(
    	['ID' => 17, 'NAME' => 'Name 17']
    );
    
    // add LAST_NAME from the database
    $author->fillLastName();
    

    In addition to name methods, a generic method is available as well. It provides a significantly more options then other generic methods:

    $author = BitrixMainTestTypographyEO_Author::wakeUp(17);
    
    // filling several fields
    $author->fill(['NAME', 'LAST_NAME']);
    
    // filling all presently unfilled fields
    $author->fill();
    
    // filling fields by mask, for example, all unfilled scalar fields
    $author->fill(BitrixMainORMFieldsFieldTypeMask::SCALAR);
    
    // unfilled scalar and user fields
    $author->fill(
    	BitrixMainORMFieldsFieldTypeMask::SCALAR
    	| BitrixMainORMFieldsFieldTypeMask::USERTYPE
    );
    
    /*
     * Masks are available as follows:
     *
     * SCALAR - scalar fields (ORMScalarField)
     * EXPRESSION - expressions (ORMExpressionField)
     * USERTYPE - user fields
     * REFERENCE - relations 1:1 and N:1 (ORMFieldsRelationsReference)
     * ONE_TO_MANY - relations 1:N (ORMFieldsRelationsOneToMany)
     * MANY_TO_MANY - relations N:M (ORMFieldsRelationsManyToMany)
     *
     * FLAT - scalar fields and expressions
     * RELATION - all relations
     *
     * ALL - absolutely all available fields
     */
    

    If you need to additionally fill in several objects, it’s strongly not recommended to perform this command in a loop: this will result to a significant number of queries to the database. Handling several objects of the same type requires a similar Collection method.

    Relations (addTo, removeFrom, removeAll)

    Detailed description can be found in the next article for Relations. However, you can find specifications for managing method relations below.

    Relations fields can be handled by the already described methods get, require, fill, reset, unset.

    Important! Despite the fact that Collections object is used as a relations value, relations can be modified only via the methods addTo, removeFrom, removeAll for partner objects. Modifying a collection directly (add, remove) doesn’t lead to a desirable result.

    • addTo

      The addTo method adds a new relations between objects:

      // publisher initialization
      $publisher = BitrixMainTestTypographyPublisherTable::getByPrimary(253)
      	->fetchObject();
      
      // book initialization
      $book = BitrixMainTestTypographyBookTable::getByPrimary(2)
      	->fetchObject();
      
      // adding book to a relation collection
      $publisher->addToBooks($book);
      
      // saving
      $publisher->save();
      

      Calling the method binds the object only in the system memory, you need to register the changes using the save method.

    • removeFrom

      Deleting relations removeFrom operates in a similar manner:

      // publisher initialization
      $publisher = BitrixMainTestTypographyPublisherTable::getByPrimary(253)
      	->fetchObject();
      
      // book initialization
      $book = BitrixMainTestTypographyBookTable::getByPrimary(2)
      	->fetchObject();
      
      // deleting a single specific publisher book
      $publisher->removeFromBooks($book); 
      
      // saving
      $publisher->save();
      
    • removeAll

      Deleting all records can be done via a single call:

      // publisher initialization
      $publisher = BitrixMainTestTypographyPublisherTable::getByPrimary(253)
      	->fetchObject();
      	
      // deleting all publisher books
      $publisher->removeAllBooks();
      
      // saving
      $publisher->save();
      

      This operation requires knowledge of the source value: which Books are available at the Publisher presently. That’s why, if BOOKS field is not initially accessed, it will be accessed automatically before deleting.

    As an alternative, you can use generic unnamed methods:

    $fieldName = 'BOOKS';
    
    $publisher->addTo($fieldName, $book);
    $publisher->removeFrom($fieldName, $book);
    $publisher->removeAll($fieldName);
    

    ArrayAccess

    Interface for accessing object as an array can ensure backward compatibility when switching from arrays to objects:

    $author = BitrixMainTestTypographyAuthorTable::getByPrimary(17)->fetchObject();
    		
    echo $author['NAME'];
    // call is similar to the method $author->getName()
    
    $author['NAME'] = 'New name';
    // call is similar to the method $author->setName('New name')
    

    As to runtime fields, you may only read their values in this case, but not set them:

    $author = BitrixMainTestTypographyAuthorTable::query()
    	->registerRuntimeField(
    		new BitrixMainEntityExpressionField('FULL_NAME', 'CONCAT(%s, " ", %s)', ['NAME', 'LAST_NAME'])
    	)
    	->addSelect('ID')
    	->addSelect('FULL_NAME')
    	->where('ID', 17)
    	->fetchObject();
    
    echo $author['FULL_NAME'];
    // call is similar to the method $author->get('FULL_NAME');
    
    $author['FULL_NAME'] = 'New name';
    // throws exception
    

    Collections

    Collections class

    Operation is based on the same logic as for EO_, and for Objects. Each entity has its own Collection class, inherited from BitrixMainORMObjectifyCollection. The Book entity will have the EO_Book_Collection view by default. To set your own class, create a descendants for this class and designate it in the Table entity class:

    //File bitrix/modules/main/lib/test/typography/books.php
    
    namespace BitrixMainTestTypography;
    
    class Books extends EO_Book_Collection
    {
    }
    
    //File bitrix/modules/main/lib/test/typography/booktable.php
    
    namespace BitrixMainTestTypography;
    
    class BookTable extends BitrixMainORMDataDataManager
    {
    	public static function getCollectionClass()
    	{
    		return Books::class;
    	}
    	//...
    }

    Now the method fetchCollection will return the collection BitrixMainTestTypographyBooks of class objects BitrixMainTestTypographyBook. Annotations allow for IDE to create hints, simplifying the developer’s work.

    Access to Collection items

    • foreach

      Base collection class implements the Iterator interface, allowing to get items:

      $books = BitrixMainTestTypographyBookTable::getList()
      	->fetchCollection();
      
      foreach ($books as $book)
      {
      	// ...
      }
    • getAll, getByPrimary

      Collection items can also be fetched directly. The method getAll returns all the contained objects as an array:

      $books = BitrixMainTestTypographyBookTable::getList()
      	->fetchCollection();
      
      $bookObjects = $books->getAll();
      
      echo $bookObjects[0]->getId();
      // prints ID value for the first object
      

      The method getByPrimary gets specific objects, contained in the collection:

      // 1. example with standard primary key
      $books = BitrixMainTestTypographyBookTable::getList()
      		->fetchCollection();
      	
      $book = $books->getByPrimary(1);
      // book with ID=1
      
      // 2. example with composite primary key
      $booksToAuthor = BitrixMainTestTypographyBookAuthorTable::getList()
      	->fetchCollection();
      
      $bookToAuthor = $booksToAuthor->getByPrimary(
      	['BOOK_ID' => 2, 'AUTHOR_ID' => 18]
      );
      // assigns relations for book object with ID=2 and author with ID=18
      
    • has, hasByPrimary

      You can check availability of specific object in the collection using the method has:

      $book1 = BitrixMainTestTypographyBook::wakeUp(1);
      $book2 = BitrixMainTestTypographyBook::wakeUp(2);
      
      $books = BitrixMainTestTypographyBookTable::query()
      	->addSelect('*')
      	->whereIn('ID', [2, 3, 4])
      	->fetchCollection();
      
      var_dump($books->has($book1));
      // prints false
      
      var_dump($books->has($book2));
      // prints true
      

      Similarly, the method hasByPrimary is convenient for checking by the primary key:

      $books = BitrixMainTestTypographyBookTable::query()
      	->addSelect('*')
      	->whereIn('ID', [2, 3, 4])
      	->fetchCollection();
      
      var_dump($books->hasByPrimary(1));
      // prints false
      
      var_dump($books->hasByPrimary(2));
      // prints true
      
      
      
    • add, []

      Objects are added by the method add and interface ArrayAccess, allowing to use the structure []:

      $book1 = BitrixMainTestTypographyBook::wakeUp(1);
      
      $books = BitrixMainTestTypographyBookTable::query()
      	->addSelect('*')
      	->whereIn('ID', [2, 3, 4])
      	->fetchCollection();
      
      $books->add($book1);
      // or
      $books[] = $book1;
    • remove, removeByPrimary

      You can delete object from collection either directly or by specifying the primary key:

      $book1 = BitrixMainTestTypographyBook::wakeUp(1);
      
      $books = BitrixMainTestTypographyBookTable::getList()
      	->fetchCollection();
      
      $books->remove($book1);
      // book with ID=1 is deleted from collection
      
      $books->removeByPrimary(2);
      // book with ID=2 is deleted from collection
      

    Group actions

    Collections allow performing group actions for items contained inside such collections.

    • save (adding)

      The method save() performs primary saving in case of new objects, by generating a single group query:

      use BitrixMainTestTypographyBooks;
      use BitrixMainTestTypographyBook;
      
      $books = new Books;
      
      $books[] = (new Book)->setTitle('Title 112');
      $books[] = (new Book)->setTitle('Title 113');
      $books[] = (new Book)->setTitle('Title 114');
      
      $books->save(true);
      
      // INSERT INTO ...  (`TITLE`, `ISBN`) VALUES
      	('Title 112', DEFAULT),
      	('Title 113', DEFAULT),
      	('Title 114', '114-000')

      The method receives the parameter $ignoreEvents = true, cancelling the ORM events when adding entries. In case of multiple inserting with autoincremental field (ID) you cannot get multiple values of this field which is possible when inserting a single entry using the function similar to mysqli_insert_id().

      In the rest of cases, when entity doesn’t have autoincremental fields, events are left to the discretion of a developer. Events are executed by default.

    • save (editing)

      The method save() saves already existing, but modified objects using a single query UPDATE:

      use BitrixMainTestTypographyPublisherTable;
      use BitrixMainTestTypographyBookTable;
      
      $books = BookTable::getList()->fetchCollection();
      $publisher = PublisherTable::wakeUpObject(254);
      
      foreach ($books as $book)
      {
      	$book->setPublisher($publisher);
      }
      
      $books->save();
      
      // UPDATE ... SET `PUBLISHER_ID` = '254'
      	WHERE `ID` IN ('1', '2')

      Group update works only in case, when set of updated data is the same for all objects. When at least a single object has different data, all entries/records will be saved individually.

      As in the case with the adding action, the update action can disable event using the parameter $ignoreEvents in the method save(). By default, events are executed for each collection item individually.

    • fill

      Collection operation fill is a great alternative to the similar operation in Object, executed in a loop. In case of a loop, number of database queries will be equal to the number of objects:

      /** @var BitrixMainTestTypographyBook[] $books */
      $books = [
      	BitrixMainTestTypographyBook::wakeUp(1),
      	BitrixMainTestTypographyBook::wakeUp(2)
      ];
      
      foreach ($books as $book)
      {
      	$book->fill();
      	// SELECT ... WHERE ID = ...
      	// do not do this!
      }

      In case of collection the query will be an only single one:

      $books = new BitrixMainTestTypographyBooks;
      // or $books = BitrixMainTestTypographyBookTable::createCollection();
      
      $books[] = BitrixMainTestTypographyBook::wakeUp(1);
      $books[] = BitrixMainTestTypographyBook::wakeUp(2);
      
      $books->fill();
      // SELECT ... WHERE ID IN(1,2)
      

      As in the case with objects, the parameter fill can pass the array with names of fields for completing or a mask type as follows:

      $books->fill(['TITLE', 'PUBLISHER_ID']);
      $books->fill(BitrixMainORMFieldsFieldTypeMask::FLAT);

      You can find more details on possible parameter values in this article for Objects fill.

    • get*List

      Not the most rare case: getting list of values for dedicated field from the query result. In standard case this can look as follows:

      $books = BitrixMainTestTypographyBookTable::getList()
      	->fetchCollection();
      
      $titles = [];
      
      foreach ($books as $book)
      {
      	$titles[] = $book->getTitle();
      }

      Named group «getter» method allows to reduce such loop to a single code line:

      $books = BitrixMainTestTypographyBookTable::getList()
      	->fetchCollection();
      
      $titles = $books->getTitleList();

      Such «getters» are available for all entity fields and are described in annotations for IDE.

    Restoring collection

    Restoring collection from completed data operates the same as described in the Objects:

    // restoring by primary key
    $books = BitrixMainTestTypographyBooks::wakeUp([1, 2]);
    
    // restoring by set of fields
    $books = BitrixMainTestTypographyBooks::wakeUp([
    	['ID' => 1, 'TITLE' => 'Title 1'],
    	['ID' => 2, 'TITLE' => 'Title 2']
    ]);

    With the difference that array with object data is passed to be placed in the collection.

    Relations

    The diagram below highlights the relations for test entities: Book, Author andPublisher with all combinations and direction for relations: 1:N,

    Starting point is the assumption that a book belongs to a single publisher, but can have several authors and it’s sold in several stores.

    1:N

    In our presented test environment, a book can belong to and published strictly by a single publishing house. We’ll get the relation «1 publisher — N books».

    Book and a Publisher

    In such cases, add the field PUBLISHER_ID to the table Books with value designating the Publisher.

    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsIntegerField;
    
    class BookTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    			(new IntegerField('PUBLISHER_ID'))
    		];
    	}
    }
    

    However, a single field is not enough for ORM to recognize relations between Book and Publisher entities. For ORM to recognize such relations, use the multifields BitrixMainORMFieldsRelations. In this case, the Reference type field is needed for indicating «multiple to one» relation:

    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsIntegerField;
    use BitrixMainORMFieldsRelationsReference;
    use BitrixMainORMQueryJoin;
    
    class BookTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    			(new IntegerField('PUBLISHER_ID')),
    
    			(new Reference(
    					'PUBLISHER',
    					PublisherTable::class,
    					Join::on('this.PUBLISHER_ID', 'ref.ID')
    				))
    				->configureJoinType('inner')
    		];
    	}
    }
    

    Reference constructor parameters:

    /support/training/course/index.php?COURSE_ID=68&LESSON_ID=24526

    Parameter Description
    $name Field name.
    $referenceEntity Class for bound entity.
    $referenceFilter «Join» conditions. Expects a filter object. In difference to regular filter use, add prefixes «this.» and «ref.» to column names here to designate association to current and related bounded entities accordingly.

    For readability, a created class BitrixMainORMQueryJoin is available, with a only method on that returns filter object BitrixMainORMQueryFilterConditionTree, indicating the most popular condition whereColumn.

    Additionally, you can configure «join» type. By default, it’s left join; example above sets inner join.

    Now you can use the described relations during data fetch:

    $book = BitrixMainTestTypographyBookTable::getByPrimary(1, [
    	'select' => ['*', 'PUBLISHER']
    ])->fetchObject();
    
    echo $book->getPublisher()->getTitle();
    // prints Publisher Title 253
    

    Access to Publisher entity object is implemented via «getter» getPublisher(). This way, you can connect more deeply nested relations chains, and use the «getter» chains to reach terminal objects.

    To set a relation, it’s sufficient to pass Publisher entity object in a corresponding «settter»:

    // publisher initialization
    $publisher = BitrixMainTestTypographyPublisherTable::wakeUpObject(253);
    
    // book initialization
    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    // setting object value
    $book->setPublisher($publisher);
    
    // saving
    $book->save();
    

    PUBLISHER_ID field value will be completed automatically from the passed object.

    The array results will not look so compact:

    $result = BitrixMainTestTypographyBookTable::getByPrimary(1, [
    	'select' => ['*', 'PUBLISHER']
    ]);
    
    print_r($result->fetch());
    /* prints
    Array (
    	[ID] => 1
    	[TITLE] => Title 1
    	[PUBLISHER_ID] => 253
    	[ISBN] => 978-3-16-148410-0
    	[IS_ARCHIVED] => Y
    	[MAIN_TEST_TYPOGRAPHY_BOOK_PUBLISHER_ID] => 253
    	[MAIN_TEST_TYPOGRAPHY_BOOK_PUBLISHER_TITLE] => Publisher Title 253
    )
    */
    

    Unique names, based at the class name and namespace, are assigned to related entity fields. You can use the «alias» mechanism to get more brief and practical names:

    $result = BitrixMainTestTypographyBookTable::getByPrimary(1, [
    	'select' => ['*', 'PUB_' => 'PUBLISHER']
    ]);
    
    print_r($result->fetch());
    /* prints
    Array (
    	[ID] => 1
    	[TITLE] => Title 1
    	[PUBLISHER_ID] => 253
    	[ISBN] => 978-3-16-148410-0
    	[IS_ARCHIVED] => Y
    	[PUB_ID] => 253
    	[PUB_TITLE] => Publisher Title 253
    )
    */
    

    Publisher and Books

    At the moment, access to a relation operates only in a direction «Book» -> «Publisher». To make it two-way, you’ll need to describe the relation at the side of Publisher entity:

    namespace BitrixMainTestTypography;
    
    use BitrixMainORMDataDataManager;
    use BitrixMainORMFieldsRelationsOneToMany;
    
    class PublisherTable extends DataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    
    			(new OneToMany('BOOKS', BookTable::class, 'PUBLISHER'))->configureJoinType('inner')
    		];
    	}
    }
    

    OneToMany constructor parameters:

    Parameter Description
    $name Field name.
    $referenceEntity Class for entity to be related/bound.
    $referenceFilter `Reference` field name in the partner entity, used to establish the relation.

    Additionally, you can re-define join type. By default, uses the type, specified in the Reference field for entity to be related.

    Now you can use the described relation when selecting data:

    $publisher = BitrixMainTestTypographyPublisherTable::getByPrimary(253, [
    	'select' => ['*', 'BOOKS']
    ])->fetchObject();
    
    foreach ($publisher->getBooks() as $book)
    {
    	echo $book->getTitle();
    }
    
    // loop prints "Title 1" and "Title 2"
    

    The example above highlights an essential advantage of object-based model compared to array-based one. Despite the fact that two entities were selected (two books were found for a single Publisher), in actuality, result gets only a single object. System has independently recognized this case and included all the publisher books in a single Collection.

    Requesting an array from the result returns a classic data structure with duplicating Publisher data:

    $data = BitrixMainTestTypographyPublisherTable::getByPrimary(253, [
    	'select' => ['*', 'BOOK_' => 'BOOKS']
    ])->fetchAll();
    
    // returns
    Array (
    	[0] => Array (
    		[ID] => 253
    		[TITLE] => Publisher Title 253
    		[BOOK_ID] => 2
    		[BOOK_TITLE] => Title 2
    		[BOOK_PUBLISHER_ID] => 253
    		[BOOK_ISBN] => 456-1-05-586920-1
    		[BOOK_IS_ARCHIVED] => N 
    	) 
    	[1] => Array (
    		[ID] => 253
    		[TITLE] => Publisher Title 253
    		[BOOK_ID] => 1
    		[BOOK_TITLE] => Title 1
    		[BOOK_PUBLISHER_ID] => 253
    		[BOOK_ISBN] => 978-3-16-148410-0
    		[BOOK_IS_ARCHIVED] => Y
    	)
    )

    To add a new Book to the Publisher, use the named «setter» addTo:

    // publisher initialization
    $publisher = BitrixMainTestTypographyPublisherTable::getByPrimary(253)
    	->fetchObject();
    
    // book initialization
    $book = BitrixMainTestTypographyBookTable::getByPrimary(2)
    	->fetchObject();
    
    // adding book into relations collection
    $publisher->addToBooks($book);
    
    // saving
    $publisher->save();
    

    You can delete relation from the side of a book by assigning another publisher by setting setPublisher() or specifying a null. Specialized «setters» removeFrom() and removeAll() are available to do the same for the Publisher:

    // book initialization
    $book = BitrixMainTestTypographyBook::wakeUp(2);
    
    // publisher initialization
    $publisher = BitrixMainTestTypographyPublisherTable::getByPrimary(253, [
    	'select' => ['*', 'BOOKS']
    ])->fetchObject();
    
    // deleting a specific single publisher book
    $publisher->removeFromBooks($book);
    
    // or deleting all publisher books
    $publisher->removeAllBooks();
    
    // when saving, the PUBLISHER_ID field is updated in Books to be empty
    // the books themselves are not deleted, the only relation is deleted
    $publisher->save();
    

    It’s important: for ensuring the correct operation the relation field must contain a value. In the example above its specified in the fetched data. You need to preliminarily call the method fill if you didn’t fetch values from the database or aren’t sure if they are filled in the specific object:

    // book initialization
    $book = BitrixMainTestTypographyBookTable::wakeUpObject(2);
    
    // the publisher will have a primary key completed only
    $publisher = BitrixMainTestTypographyPublisherTable::wakeUpObject(253);
    
    // complete relation field
    $publisher->fillBooks();
    
    // delete specific single book
    $publisher->removeFromBooks($book);
    
    // or delete all books
    $publisher->removeAllBooks();
    
    // when saving, the PUBLISHER_ID field in Books will be updated to be empty
    // the books themselves won't be deleted
    $publisher->save();
    

    In case of arrays, the operations with addTo, removeFrom and removeAll are impossible; you can create relation only from the side of Books entity.

    1:1

    One-for-one relation operates in a similar manner to one-to-many relations with the only difference that both entities will have both Reference fields instead of Reference + OneToMany pair.

    N:M

    These are primitive relations without auxiliary data

    A book can have several authors and an author can have several books. In such cases, a separate table is created with two fields AUTHOR_ID and BOOK_ID. The ORM won’t have to issue it as a separate entity, its sufficient to describe the relation by the special field ManyToMany:

    //File bitrix/modules/main/lib/test/typography/booktable.php
    
    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsRelationsManyToMany;
    
    class BookTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    
    			(new ManyToMany('AUTHORS', AuthorTable::class))
    				->configureTableName('b_book_author')
    		];
    	}
    }
    
    //File bitrix/modules/main/lib/test/typography/authortable.php
    
    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsRelationsManyToMany;
    
    class AuthorTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    
    			(new ManyToMany('BOOKS', BookTable::class))
    				->configureTableName('b_book_author')
    		];
    	}
    }

    Field description for both entities is optional — a single field can have a description; however, the access to data is granted only to this one field.

    The constructor passes the field name and partner entity class. In case of primitive relations, it’s sufficient to call the method configureTableName with indicated table name, storing related data. The more complex case will be overviewed below, in the example for Books and Stores relations.

    In this case, system memory automatically creates a temporary entity for handling a staging table. In actuality, you won’t see its traces anywhere, but for purposes of understanding the process and possible additional settings, we overview such case. System entity for staging table has the following approximate contents:

    class ... extends BitrixMainORMDataDataManager
    {
    	public static function getTableName()
    	{
    		return 'b_book_author';
    	}
    
    	public static function getMap()
    	{
    		return [
    			(new IntegerField('BOOK_ID'))
    				->configurePrimary(true),
    
    			(new Reference('BOOK', BookTable::class,
    				Join::on('this.BOOK_ID', 'ref.ID')))
    				->configureJoinType('inner'),
    
    			(new IntegerField('AUTHOR_ID'))
    				->configurePrimary(true),
    
    			(new Reference('AUTHOR', AuthorTable::class,
    				Join::on('this.AUTHOR_ID', 'ref.ID')))
    				->configureJoinType('inner'),
    		];
    	}
    }
    

    This is no more than standard entity with references (directed relations 1:N) to the source partner entities. Field names are generated based on entity names and their primary keys:

    new IntegerField('BOOK_ID') - snake_case from Book + primary field ID
    new Reference('BOOK') - snake_case from Book
    new IntegerField('AUTHOR_ID') - snake_case from Author + primary field ID
    new Reference('AUTHOR') - snake_case from Author
    

    To directly set field name, use the following configuration methods (this is especially pertinent in entities with composite primary keys to avoid confusion):

    //File bitrix/modules/main/lib/test/typography/booktable.php
    
    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsRelationsManyToMany;
    
    class BookTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    
    			(new ManyToMany('AUTHORS', AuthorTable::class))
    				->configureTableName('b_book_author')
    				->configureLocalPrimary('ID', 'MY_BOOK_ID')
    				->configureLocalReference('MY_BOOK')
    				->configureRemotePrimary('ID', 'MY_AUTHOR_ID')
    				->configureRemoteReference('MY_AUTHOR')
    		];
    	}
    }
    
    //File bitrix/modules/main/lib/test/typography/authortable.php
    
    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsRelationsManyToMany;
    
    class AuthorTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    
    			(new ManyToMany('BOOKS', BookTable::class))
    				->configureTableName('b_book_author')
    				->configureLocalPrimary('ID', 'MY_AUTHOR_ID')
    				->configureLocalReference('MY_AUTHOR'),
    				->configureRemotePrimary('ID', 'MY_BOOK_ID')
    				->configureRemoteReference('MY_BOOK')
    		];
    	}
    }

    The method configureLocalPrimary indicates how the field relation from current entity’s primary key will be named. In similar fashion configureRemotePrimary indicates the primary key fields for partner entity key. Methods configureLocalReference and configureRemoteReference set reference names to source entities. For the configuration described above, the relations system entity will be approximately as follows:

    class ... extends BitrixMainORMDataDataManager
    {
    	public static function getTableName()
    	{
    		return 'b_book_author';
    	}
    
    	public static function getMap()
    	{
    		return [
    			(new IntegerField('MY_BOOK_ID'))
    				->configurePrimary(true),
    
    			(new Reference('MY_BOOK', BookTable::class,
    				Join::on('this.MY_BOOK_ID', 'ref.ID')))
    				->configureJoinType('inner'),
    
    			(new IntegerField('MY_AUTHOR_ID'))
    				->configurePrimary(true),
    
    			(new Reference('MY_AUTHOR', AuthorTable::class,
    				Join::on('this.MY_AUTHOR_ID', 'ref.ID')))
    				->configureJoinType('inner'),
    		];
    	}
    }

    Just as in case with Reference and OneToMany, you can also redefine type of join by the method configureJoinType (default value — «left»):

    (new ManyToMany('AUTHORS', AuthorTable::class))
    	->configureTableName('b_book_author')
    	->configureJoinType('inner')
    

    Data reading operates similarly to the relations 1:N:

    // fetched from author side
    $author = BitrixMainTestTypographyAuthorTable::getByPrimary(18, [
    	'select' => ['*', 'BOOKS']
    ])->fetchObject();
    
    foreach ($author->getBooks() as $book)
    {
    	echo $book->getTitle();
    }
    // prints "Title 1" and "Title 2"
    
    // retrieved from side of books
    $book = BitrixMainTestTypographyBookTable::getByPrimary(2, [
    	'select' => ['*', 'AUTHORS']
    ])->fetchObject();
    
    foreach ($book->getAuthors() as $author)
    {
    	echo $author->getLastName();
    }
    // prints "Last name 17" and "Last name 18"
    

    Once again, retrieving objects instead of arrays is more advantageous due to not having «duplicated» data, as it happens with arrays:

    $author = BitrixMainTestTypographyAuthorTable::getByPrimary(18, [
    	'select' => ['*', 'BOOK_' => 'BOOKS']
    ])->fetchAll();
    
    // вернет
    Array (
    	[0] => Array 
    		[ID] => 18
    		[NAME] => Name 18
    		[LAST_NAME] => Last name 18
    		[BOOK_ID] => 1
    		[BOOK_TITLE] => Title 1
    		[BOOK_PUBLISHER_ID] => 253
    		[BOOK_ISBN] => 978-3-16-148410-0
    		[BOOK_IS_ARCHIVED] => Y 
    	)
    	[1] => Array (
    		[ID] => 18
    		[NAME] => Name 18
    		[LAST_NAME] => Last name 18
    		[BOOK_ID] => 2
    		[BOOK_TITLE] => Title 2
    		[BOOK_PUBLISHER_ID] => 253
    		[BOOK_ISBN] => 456-1-05-586920-1
    		[BOOK_IS_ARCHIVED] => N
    	)
    )

    Creating relations between objects of two entities occurs in the same manner as in case with relations 1:N:

    // from author side
    $author = BitrixMainTestTypographyAuthorTable::getByPrimary(17)
    	->fetchObject();
    
    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    $author->addToBooks($book);
    
    $author->save();
    
    
    // from books' side
    $author = BitrixMainTestTypographyAuthorTable::getByPrimary(17)
    	->fetchObject();
    
    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    $book->addToAuthors($author);
    
    $book->save();
    

    Methods removeFrom and removeAll operate in the same manner.

    No constructions are designed for such constructor arrays. See the example below for Books with Stores to overview how to bind entities using the arrays.

    Relations with auxiliary data

    STORE_ID BOOK_ID QUANTITY
    33 1 4
    33 2 0
    43 2 9

    When there is additional data (number of books in stock) and not only primary keys for source entities, such relation must be described by a separate entity:

    namespace BitrixMainTestTypography;
    
    use BitrixMainORMDataDataManager;
    use BitrixMainORMFieldsIntegerField;
    use BitrixMainORMFieldsRelationsReference;
    use BitrixMainORMQueryJoin;
    
    class StoreBookTable extends DataManager
    {
    	public static function getTableName()
    	{
    		return 'b_store_book';
    	}
    
    	public static function getMap()
    	{
    		return [
    			(new IntegerField('STORE_ID'))
    				->configurePrimary(true),
    
    			(new Reference('STORE', StoreTable::class,
    				Join::on('this.STORE_ID', 'ref.ID')))
    				->configureJoinType('inner'),
    
    			(new IntegerField('BOOK_ID'))
    			A	->configurePrimary(true),
    
    			(new Reference('BOOK', BookTable::class,
    				Join::on('this.BOOK_ID', 'ref.ID')))
    				->configureJoinType('inner'),
    
    			(new IntegerField('QUANTITY'))
    				->configureDefaultValue(0)
    		];
    	}
    }

    ManyToMany fields were used for simple relations, but here their use will be significantly limited. Relations can be created and deleted, but without access to auxiliary field QUANTITY. Use of removeFrom*() can delete the relation and addTo*() can add the relation with QUANTITY value only by default, without the option to update the QUANTITY value. That’s why in such cases more flexible approach would be using proxy entity directly:

    // book object
    $book = BitrixMainTestTypographyBookTable::getByPrimary(1)
    	->fetchObject();
    
    // store object
    $store = BitrixMainTestTypographyStoreTable::getByPrimary(34)
    	->fetchObject();
    
    // new book and store relations object
    $item = BitrixMainTestTypographyStoreBookTable::createObject()
    	->setBook($book)
    	->setStore($store)
    	->setQuantity(5);
    
    // saving
    $item->save();

    Number of books update:

    // existing relation object
    $item = BitrixMainTestTypographyStoreBookTable::getByPrimary([
    	'STORE_ID' => 33, 'BOOK_ID' => 2
    ])->fetchObject();
    
    // quantity update
    $item->setQuantity(12);
    
    // saving
    $item->save();
    

    Deleting the relation:

    // existing relation object
    $item = BitrixMainTestTypographyStoreBookTable::getByPrimary([
    	'STORE_ID' => 33, 'BOOK_ID' => 2
    ])->fetchObject();
    
    // deleting
    $item->delete();
    

    The relation object is handled in the same manner as objects of any other entities. Arrays must also use standard approaches for data handling:

    // adding
    BitrixMainTestTypographyStoreBookTable::add([
    	'STORE_ID' => 34, 'BOOK_ID' => 1, 'QUANTITY' => 5
    ]);
    
    // updating
    BitrixMainTestTypographyStoreBookTable::update(
    	['STORE_ID' => 34, 'BOOK_ID' => 1],
    	['QUANTITY' => 12]
    );
    
    // deleting
    BitrixMainTestTypographyStoreBookTable::delete(
    	['STORE_ID' => 34, 'BOOK_ID' => 1]
    );

    As mentioned above, using the field ManyToMany in case with auxiliary data is unproductive. More correct way is to use the type OneToMany:

    //File bitrix/modules/main/lib/test/typography/booktable.php
    
    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsRelationsOneToMany;
    
    class BookTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    
    			(new OneToMany('STORE_ITEMS', StoreBookTable::class, 'BOOK'))
    		];
    	}
    }
    //File bitrix/modules/main/lib/test/typography/storetable.php
    
    namespace BitrixMainTestTypography;
    
    use BitrixMainORMFieldsRelationsOneToMany;
    
    class StoreTable extends BitrixMainORMDataDataManager
    {
    	public static function getMap()
    	{
    		return [
    			// ...
    
    			(new OneToMany('BOOK_ITEMS', StoreBookTable::class, 'STORE'))
    		];
    	}
    }

    In such case fetched data won’t be different from the relations 1:N, only in this case, returns StoreBook relations objects and not partner-entities:

    $book = BitrixMainTestTypographyBookTable::getByPrimary(1, [
    	'select' => ['*', 'STORE_ITEMS']
    ])->fetchObject();
    
    
    foreach ($book->getStoreItems() as $storeItem)
    {
    	printf(
    		'store "%s" has %s of book "%s"',
    		$storeItem->getStoreId(), $storeItem->getQuantity(), $storeItem->getBookId()
    	);
    	// prints store "33" has 4 of book "1"
    }
    

    Class annotations

    Majority of Object and Collection methods are virtual, processed via magic __call. At the same time, they are created for intuitively clear and self-explanatory named methods; without IDE autocomplete their significance substantially falls.

    We have created a special service file with annotation for all entities for IDE to be aware that these methods exist, helping to navigate in the large number of classes and methods.

    Starting from the Main version 20.100.0, file with ORM kernel class annotations is included into distribution package and located at /bitrix/modules/main/meta/orm.php.

    The cli-command orm:annotate is used for generating such annotations:

    $ cd bitrix
    $ php bitrix.php orm:annotate

    Note: before using CLI-environment ensure, that you have set project dependencies using composer.

    Modules are scanned during command execution and specifically all files from folders bitrix/modules/[module]/lib. When file detects an entity «mapping» (class Table, subclass BitrixMainORMDataDataManager), its map is analyzed (list of fields).

    Command result contains the file (by default bitrix/modules/orm_annotations.php), containing description of entity Object and Collection classes. It also declares Table class duplicate and several actually non-existent helper classes, assisting in IDE autocomplete from the moment of query to the use of resulting objects.

    By default, scans only the Main module. Scanning random modules can be set directly:

    // annotating entities for arbitrary module:
    
    $ php bitrix.php orm:annotate -m tasks
    // annotating several modules:
    
    $ php bitrix.php orm:annotate -m main,intranet,faceid
    // annotating all modules:
    
    $ php bitrix.php orm:annotate -m all

    In the nearest future we plan to introduce monitoring for all known entities in the development mode, to be able to call annotations automatically when updating the fields. Then, console won’t have to be used as much often.

    Partially, it’s convenient to selectively replace classes on re-generation. When annotations already have the described modules, repeat annotation for one of them will update description only one of its classes and other won’t be deleted. Use the parameter -c for the reset:

    $ php bitrix.php orm:annotate -c -m all

    To view all available command parameters, execute the command:

    $ php bitrix.php help orm:annotate

    Backward compatibility

    Introduction of objects release in the main module version 18.0.4 some ORM internal mechanisms were updated and streamlined.

    • Field names now are case-insensitive. Previously, two fields LAST_NAME and last_name could be described and these two fields would have been different fields. But now it’s the same entity that cannot initialize. This update is related to named methods in Objects.
    • The fetched data cannot have an assigned same-name alias in different case, for example: getList([‘select’ => [‘id’ => ‘ID’]]).
    • Previously, BooleanField field received an empty string as a value, resulting in erroneous value interpretation. Now an empty string is prohibited, it can be indicated as true/false or values, specified in field configuration.

    Objects do not support the following yet:

    • Fields with serialization, due to existing inconsistency: field type is indicated as StringField or TextField but actually stores an array, which contradicts to the declared type. Upcoming updates will add the new field type: ArrayField.

    Data retrieval

    The most frequent objective is to retrieve data with various conditions of filtering, grouping and sorting.

    getList

    For the new API to be less intimidating and more familiar to a developer, the name for the most popular method was preserved: getList. However, if previously each getList had its own set of parameters and closed-off opaque behaviour, now this method is uniform for all entities and is subject to general rules. Even if a developer wants to add some «scaffolding» in getList, he or she won’t be able to do it.

    The BookTable entity, taken as an example, is not an exception. Such parameters are accepted by the method BookTable::getList?

    BookTable::getList(array(
    	'select'  => ... // field names to be retrieved in the result
    	'filter'  => ... // filter description for WHERE and HAVING
    	'group'   => ... // direct indication of fields for result grouping
    	'order'   => ... // sorting parameters
    	'limit'   => ... // number of records
    	'offset'  => ... // offset for limit
    	'runtime' => ... // dynamically-defined fields
    ));

    getList always returns the object DBResult to retrieve data using the method fetch():

    $rows = array();
    $result = BookTable::getList(array(
    	...
    ));
    while ($row = $result->fetch())
    {
    	$rows[] = $row;
    }

    To fetch data for all records, use the method fetchAll():

    $result = BookTable::getList($parameters);
    $rows = $result->fetchAll();
    
    // or just briefly:
    $rows = BookTable::getList($parameters)->fetchAll();

    Now let’s overview all parameters in detail.

    select

    Parameter `select` is defined as array with entity fields array:

    BookTable::getList(array(
    	'select' => array('ISBN', 'TITLE', 'PUBLISH_DATE')
    ));
    
    // SELECT ISBN, TITLE, PUBLISH_DATE FROM my_book

    If original field names included into the result are not satisfactory due to some reasons — you can use aliases:

    BookTable::getList(array(
    	'select' => array('ISBN', 'TITLE', 'PUBLICATION' => 'PUBLISH_DATE')
    ));
    
    // SELECT ISBN, TITLE, PUBLISH_DATE AS PUBLICATION FROM my_book

    This example the field name `PUBLISH_DATE` is replaced with `PUBLICATION` and specifically such name will be used in the resulting array.

    When all fields must be selected, you can use the character ‘*’:

    BookTable::getList(array(
    	'select' => array('*')
    ));

    Select only the «ScalarField» fields and expression «ExpressionField» fields and relations with other entities won’t be affected: they can always be indicated directly.

    filter

    Parameter `filter` inherited the format for iblock filter:

    // WHERE ID = 1
    BookTable::getList(array(
    	'filter' => array('=ID' => 1)
    ));
    
    // WHERE TITLE LIKE 'Patterns%'
    BookTable::getList(array(
    	'filter' => array('%=TITLE' => 'Patterns%')
    ));

    Filter can be a multi-level array with merging expressions AND/OR:

    // WHERE ID = 1 AND ISBN = '9780321127426'
    BookTable::getList(array(
    	'filter' => array(
    		'=ID' => 1,
    		'=ISBN' => '9780321127426'
    	)
    ));
    
    // WHERE (ID=1 AND ISBN='9780321127426') OR (ID=2 AND ISBN='9781449314286')
    BookTable::getList(array(
    	'filter' => array(
    		'LOGIC' => 'OR',
    		array(
    			// 'LOGIC' => 'AND', // by default, items are patched using AND
    			'=ID' => 1,
    			'=ISBN' => '9780321127426'
    		),
    		array(
    			'=ID' => 2,
    			'=ISBN' => '9781449314286'
    		)
    	)
    ));

    Full list of comparison operators that may be used in filter:

    • = equal (works with array as well)
    • % substring
    • > more
    • < less
    • @  IN (EXPR), passes an array or object DBSqlExpression as a value
    • !@  NOT IN (EXPR) passes an array or object DBSqlExpression as a value
    • != not equal
    • !% not substring
    • >< between, passes an array(MIN, MAX) as a value
    • >= more or equal
    • <= less or equal
    • =% LIKE
    • %= LIKE
    • Clarification to prefixes

    • == boolean expression for ExpressionField (for example, for EXISTS() or NOT EXISTS())
    • !>< not between, passes the array(MIN, MAX)
    • !=% NOT LIKE
    • !%= NOT LIKE
    • ‘==ID’ => null — condition for the field ID equals NULL (converts to ID IS NULL in SQL query)
    • ‘!==NAME’ => null — condition for the field NAME not equal NULL (converts to NAME IS NOT NULL in SQL query)

    Attention! Without the directly set comparison operator = executes LIKE by default. In this case, uses the filter building code from the Iblocks module that presupposes such behaviour due to historical reasons.

    For the field type int the following is set:
    — [dw]pre-ORM release[/dw][di]From the module main version 18.0.3.[/di]: = (comparison, array is deployed into set of «OR» conditions =)
    — after the release: IN().

    group

    The parameter `group` enumerates fields for grouping:

    BookTable::getList(array(
    	'group' => array('PUBLISH_DATE')
    ));

    In majority of cases, you don’t have to directly set the grouping: system automatically does this for you. You can find more details below in section about dynamically defined fields.

    order

    Parameter `order` allows specifying sort order:

    BookTable::getList(array(
    	'order' => array('PUBLISH_DATE' => 'DESC', 'TITLE' => 'ASC')
    ));
    
    BookTable::getList(array(
    	'order' => array('ID') // default sort order - ASC
    ));

    offset/limit

    Parameters `offset` and `limit` help to limit number of fetched records or to implement pagewise selection:

    // 10 last records
    BookTable::getList(array(
    	'order' => array('ID' => 'DESC')
    	'limit' => 10
    ));
    
    // 5th page with records, 20 per page
    BookTable::getList(array(
    	'order' => array('ID')
    	'limit' => 20,
    	'offset' => 80
    ));

    runtime

    Calculated fields (ExpressionField) mentioned in the first portion of this article are needed not for entity description, but for fetching for different calculations with grouping.

    The most simple example, calculation of number of records in an entity can be executed as follows:

    BookTable::getList(array(
    	'select' => array('CNT'),
    	'runtime' => array(
    		new EntityExpressionField('CNT', 'COUNT(*)')
    	)
    ));
    // SELECT COUNT(*) AS CNT FROM my_book

    In this example, the field not only converts the value of a field, but implements an arbitrary SQL expression with COUNT function.

    After registering a field in section `runtime`, it can be referenced not only in `select` section, but in other sections as well:

    BookTable::getList(array(
    	'select' => array('PUBLISH_DATE'),
    	'filter' => array('>CNT' => 5),
    	'runtime' => array(
    		new EntityExpressionField('CNT', 'COUNT(*)')
    	)
    ));
    // select days, when to issue more than 5 books
    // SELECT PUBLISH_DATE, COUNT(*) AS CNT FROM my_book GROUP BY PUBLISH_DATE HAVING COUNT(*) > 5

    Note. This example demonstrates the abovementioned automatic grouping: the system itself has identified it to be grouped by the field PUBLISH_DATE. You can find more details on such behaviour here.

    When calculated field is required only in the section `select` (as it often happens), the section `runtime` is optional: you can save time, placing the expression directly to `select`.

    BookTable::getList(array(
    	'select' => array(
    		new EntityExpressionField('MAX_AGE', 'MAX(%s)', array('AGE_DAYS'))
    	)
    ));
    // SELECT MAX(DATEDIFF(NOW(), PUBLISH_DATE)) AS MAX_AGE FROM my_book

    Please note, that inside the new Expression field MAX_AGE an already existing other Expression field AGE_DAYS is used. This way, the system allows using inserted expressions that will be sequentially deployed in the final SQL code.

    The `runtime` section can register not only Expression fields, but also the fields of any other types. The `runtime` mechanism adds new field to an entity as if it was described in it originally inside the method `getMap`. However, such field is located in the visibility scope only within a single query; such field will be unavailable in the next getList query and you’ll have re-register it again.

    Fetched data caching

    Caching of specific fetched data is available starting from module version 16.5.9. No need to describe it in the entity itself. By default, it’s not cached.

    The key cache was added to parameters in getList:

    $res = BitrixMainGroupTable::getList(array("filter"=>array("=ID"=>1), "cache"=>array("ttl"=>3600)));

    The same is implemented using the Query:

    $query = BitrixMainGroupTable::query();
    $query->setSelect(array('*'));
    $query->setFilter(array("=ID"=>1));
    $query->setCacheTtl(150);
    $res = $query->exec();

    It’s possible that the cached retrieved data will contain the object ArrayResult.

    By default, the retrieved data from JOIN aren’t cached. However, if you believe that your actions are correct, you can cache it directly:

    "cache"=>array("ttl"=>3600, "cache_joins"=>true);
    //or
    $query->cacheJoins(true);

    Cache reset occurs in any method add/update/delete. Forced cache reset for a table:

    /* Example for user table */
    BitrixMainUserTable::getEntity()->cleanCache();

    Project admin can restrict the caching or updating of TTL.

    Short calls

    In addition to GetList there are several methods allowing to get specific data in shorter format:

    • getById($id) — fetches data using the primary key;
    • getByPrimary($primary, array $parameters) — gets data via primary key using additional parameters;

      Note: both methods can pass id either as integer or by directly specified this element as key, by passing an array. You need to use an array if you have several primary fields. When you pass a non-primary key element in the array, it will be an error.

      BookTable::getById(1);
      BookTable::getByPrimary(array('ID' => 1));
      
      // such calls will be similar to the next call of getList:
      BookTable::getList(array(
      	'filter' => array('=ID' => 1)
      ));
    • getRowById($id) — fetches by primary key, but returns an array with data;

      $row = BookTable::getRowById($id);
      
      // similar result can be retrieved as follows:
      $result = BookTable::getById($id);
      $row = $result->fetch();
      
      // or as follows
      $result = BookTable::getList(array(
      	'filter' => array('=ID' => $id)
      ));
      
      $row = $result->fetch();
    • getRow(array $parameters) — fetches not using the primary key, but some other parameters. Returns only a single record.

      $row = BookTable::getRow(array(
      	'filter' => array('%=TITLE' => 'Patterns%'),
      	'order' => array('ID')
      ));
      
      // similar result can be retrieved as follows:
      $result = BookTable::getList(array(
      	'filter' => array('%=TITLE' => 'Patterns%'),
      	'order' => array('ID')
      	'limit' => 1
      ));
      
      $row = $result->fetch();

    Query object

    All parameters for getList, getRow as well as others are passed jointly, executing the query and returning the result: all is done in a single call. However, there is an alternative method of query configuration and execution oversight: the object EntityQuery:

    // gets data using getList
    $result = BookTable::getList(array(
    	'select' => array('ISBN', 'TITLE', 'PUBLISH_DATE')
    	'filter' => array('=ID' => 1)
    ));
    
    // similar method using EntityQuery
    $q = new EntityQuery(BookTable::getEntity());
    $q->setSelect(array('ISBN', 'TITLE', 'PUBLISH_DATE'));
    $q->setFilter(array('=ID' => 1));
    $result = $q->exec();

    Such approach can be convenient, when you need flexibility in building a query. For example, when query parameters are not known beforehand and are software-generated, you can use a single Query object instead of several various arguments. This object accumulates query parameters:

    $query = new EntityQuery(BookTable::getEntity());
    attachSelect($query);
    attachOthers($query);
    $result = $query->exec();
    
    function attachSelect(EntityQuery $query)
    {
    	$query->addSelect('ID');
    	
    	if (...)
    	{
    		$query->addSelect('ISBN');
    	}
    }
    
    function attachOthers(EntityQuery $query)
    {
    	if (...)
    	{
    		$query->setFilter(...);
    	}
    	
    	if (...)
    	{
    		$query->setOrder(...);
    	}
    }

    Also the EntityQuery object allows to build a query without executing it. This can be useful for executing subqueries or simply for getting query text and subsequent use:

    $q = new EntityQuery(BookTable::getEntity());
    $q->setSelect(array('ID'));
    $q->setFilter(array('=PUBLISH_DATE' => new TypeDate('2014-12-13', 'Y-m-d')));
    $sql = $q->getQuery();
    
    file_put_contents('/tmp/today_books.sql', $sql);
    
    // as a result, the query "SELECT ID FROM my_book WHERE PUBLISH_DATE='2014-12-31'" will be saved into file, but not executed.

    Full list of EntityQuery methods for implementing options described above:

    select, group:

    • setSelect, setGroup — sets array with field names
    • addSelect, addGroup — adds field name
    • getSelect, getGroup — returns array with field names

    filter:

    • setFilter — sets a single- or multidimensional array with filter description
    • addFilter — adds a single filter parameter with a value
    • getFilter — returns current filter description

    order:

    • setOrder — sets array with field names and sort order
    • addOrder — adds a single field with sort order
    • getOrder — returns current sort description

    limit/offset:

    • setLimit, setOffset — sets value
    • getLimit, getOffset — returns current value

    runtime fields:

    • registerRuntimeField — registers new temporary field for original entity

    The Query Object is the key element in data retrieval; its also used inside the standard method getList. That’s why re-defining getList methods is ineffective: calling a corresponding single method is OK, but with similar query directly via Query is not.

    Pre-set data scope fetch

      Global data area

    When required, a single table can be described by several entities, by splitting records into segments:

        class Element4Table extends BitrixIblockElementTable
        {
            public static function getTableName()
            {
                return 'b_iblock_element';
            }
            
            public static function setDefaultScope(Query $query)
            {
                $query->where("IBLOCK_ID", 4);
            }
        }
        
        class Element5Table extends BitrixIblockElementTable
        {
            public static function getTableName()
            {
                return 'b_iblock_element';
            }
            
            public static function setDefaultScope(Query $query)
            {
                $query->where("IBLOCK_ID", 5);
            }
        }
    

    Method setDefaultScope will be executed on each query, skipping the query object. It can set both filter and any other query parameters.

      Local data area

    Starting from version 20.5.500 you can pre-set data via methods with*. This is similar to setDefaultScope, but not at the global level, but at the user’s level: call when necessary. After describing the method in the entity you can call it in the query constructor:

        class UserTable
        {
            public static function withActive(Query $query)
            {
                $query->where('ACTIVE', true);
            }
        }
        
        $activeUsers = UserTable::query()
           ->withActive()
           ->fetchCollection();
    
        // WHERE `ACTIVE`='Y'
    

    The object BitrixMainORMQueryQuery is used as an argument: you can set filter and any other query parameters. Additionally, method can be supplemented by your arguments, also passed when calling from query constructor:

        class UserTable
        {
            public static function withActive(Query $query, $value)
            {
                $query
                    ->addSelect('LOGIN')
                    ->where('ACTIVE', $value);
            }
        }
        
        $activeUsers = UserTable::query()
            ->withActive(false)
            ->fetchCollection();
            
        // SELECT `LOGIN` ... WHERE `ACTIVE`='N
    

    Data retrieval from stored procedures

    ORM is also suitable for such exotic queries for data retrieval targeting not the table, but stored procedures. Such procedures can be created in MSSQL-database.

    Indicate the function name in the method getTableName:

    public static function getTableName()
    {
            // return "foo_table_name"
            return "foo_table_procedure()";
    }

    This code won’t work as demonstrated. The reason: when using the connection BitrixMainDBMssqlConnection all table name entries are pre-screened. An attempt to directly execute such query will cause a thrown exception:

    MS Sql query error: Invalid object name 'foo_table_procedure()'. (400)
    
    SELECT
    
    [base].[bar] AS [BAR],
    
    [base].[baz] AS [BAZ],
    
    FROM [foo_table_procedure()] [base]

    Getting a required result is prevented only by characters [ and ], used by MssqlSqlHelper to protect the «table» name. You can resolve this issue by creating a custom Connection and SqlHelper.

    Alternate solution: install a server mssql extension and implement the following architecture:

    class MssqlSqlHelper extends BitrixMainDBSqlHelper
    {
        public function quote($identifier)
        {
            if (self::isKnowFunctionalCall($identifier))
            {
                return $identifier
            }
            else
            {
                return parent::quote($identifier);
            }
        }
    }
    

    Where self::isKnownFunctionCall is a verification method, returning true, if $identifier is located in “foo_table_procedure()”.

    Data fetching in relations 1:N and N:M

    Logic for LIMIT

    Intuitive standby for LIMIT logic:

       $iblockEntity = IblockTable::compileEntity(…);
       
        $query = $iblockEntity->getDataClass()::query()
            ->addSelect('NAME')
            ->addSelect('MULTI_PROP_1')
        ->setLimit(5);
    
        $elements = $query->fetchCollection();

    You may not expect to get 5 items in this example. The retrieved data limit is indicated at the SQL query level, not at the object level:

        SELECT ... FROM `b_iblock_element`
            LEFT JOIN `b_iblock_element_property` ...
        LIMIT 5

    In fact, retrieving 5 property values with corresponding items results in error. That’s why the retrieved selection may contain less than 5 items, or a single item without partially fetched property values.

    Field selection for relations in a single query

    Selecting several relation fields in a single query results in Cartesian join for all records. For example:

        $iblockEntity = IblockTable::compileEntity(…);
        
        $query = $iblockEntity->getDataClass()::query()
            ->addSelect('NAME')
            ->addSelect('MULTI_PROP_1')
            ->addSelect('MULTI_PROP_2')
            ->addSelect('MULTI_PROP_3');
        
        $elements = $query->fetchCollection();

    Executes the following query:

        SELECT ... FROM `b_iblock_element`
            LEFT JOIN `b_iblock_element_property` ... // 15 property values
            LEFT JOIN `b_iblock_element_property` ... // 7 property values
            LEFT JOIN `b_iblock_element_property` ... // 11 property values 

    And if intuitively it seems that 15 + 7 + 11 = 33 strings are retrieved, in fact, retrieves 15 * 7 * 11 = 1155 strings. In cases when query contains a lot more properties or values, the resulting records count can be in millions, subsequently leading to the insufficient app’s memory for getting a complete result.

    Solving the issues

    To avoid such issues, the following class BitrixMainORMQueryQueryHelper was added along with a universal method decompose:

        /**
        ** Query decomposition with relations 1:N and N:M
        ** 
        ** @param Query $query
        ** @param bool $fairLimit. Setting this option first selects object IDs and the following query selects the rest of data with ID filter
        ** @param bool $separateRelations. Each 1:N or N:M relation is selected using separate queries under this option
        ** @return Collection
        **/
        public static function decompose(Query $query, $fairLimit = true, $separateRelations = true)

    Parameter fairLimit leads to two queries: first, selects record primary with query-defined Limit / Offset, and then all relations are selected by a single query for all primary.

    Additional parameter separateRelations allows executing a separate query per each relation, to avoid Cartesian join for all records.

    Returns a complete object collection with already merged data as the result.

    ORM filter

    The Main module version update 17.5.2 introduced new filter in ORM.

    Single conditions

    Example of a simple query:

    BitrixMainUserTable::query()
       ->where("ID", 1)
       ->exec();
    
    // WHERE `main_user`.`ID` = 1

    When you need another comparison operator, it’s indicated directly:

    BitrixMainUserTable::query()
       ->where("ID", "<", 10)
       ->exec();
    
    // WHERE `main_user`.`ID` < 10

    Example with using IS NULL:

    BitrixMainUserTable::query()
       ->whereNull("ID")
       ->exec();
    
    // WHERE `main_user`.`ID` IS NULL

    There are whereNot* similar methods for all where* methods. Example:

    BitrixMainUserTable::query()
       ->whereNotNull("ID")
       ->exec();
    
    // WHERE `main_user`.`ID` IS NOT NULL

    In addition to shared where, you can use the following operational methods:

    whereNull($column) / whereNotNull($column)
    
    whereIn($column, $values|Query|SqlExpression) / whereNotIn($column, $values|Query|SqlExpression)
    
    whereBetween($column, $valueMin, $valueMax) / whereNotBetween($column, $valueMin, $valueMax)
    
    whereLike($column, $value) / whereNotLike($column, $value)
    
    whereExists($query|SqlExpression) / whereNotExists($query|SqlExpression)

    For arbitrary expression with binding to entity fields, use the method [dw]whereExpr[/dw][di]Available from the main module version 20.400.0.[/di]:

    BitrixMainUserTable::query()
    ->whereExpr('JSON_CONTAINS(%s, 4)', ['SOME_JSON_FIELD'])
    ->exec();
    
    // WHERE JSON_CONTAINS(`main_user`.`SOME_JSON_FIELD`, 4)
    

    Expression arguments and multipart fields are similar to the ExpressionField constructor and are inserted using the function [ds]sprintf[/ds][di]sprintf — Returns formatted string.

    Learn more in the PHP documentation.[/di].

    List of operators is stored at BitrixMainORMQueryFilterOperator::$operators (see array keys):

    = , <> , != , < , <= , > , >= , in , between , like , exists

    Comparison with another field

    Separate method whereColumn simplifies the field comparison to one another:

    BitrixMainUserTable::query()
       ->whereColumn('NAME', 'LOGIN')
       ->exec();
    
    // WHERE `main_user`.`NAME` = `main_user`.`LOGIN`

    This method is not significantly different from the where method, and formally it’s the same call with a small wrapper:

    BitrixMainUserTable::query()
       ->where('NAME', new QueryFilterExpressionColumn('LOGIN'))
       ->exec();
    
    // WHERE `main_user`.`NAME` = `main_user`.`LOGIN`

    whereColumn provides a flexible use for columns used in filter, for example:

    BitrixMainUserTable::query()
       ->whereIn('LOGIN', [
          new Column('NAME'),
          new Column('LAST_NAME')
       ])
       ->exec();
    
    // WHERE `main_user`.`LOGIN` IN (`main_user`.`NAME`, `main_user`.`LAST_NAME`)

    Columns can be used in any operator. They can be perceived specifically as fields for specific entities and not just arbitrary SQL expression.

    Multiple conditions

    The following record is presented for several conditions:

    BitrixMainUserTable::query()
       ->where('ID', '>', 1)
       ->where('ACTIVE', true)
       ->whereNotNull('PERSONAL_BIRTHDAY')
       ->whereLike('NAME', 'A%')
       ->exec();
    
    // WHERE `main_user`.`ID` > 1 AND `main_user`.`ACTIVE` = 'Y' AND `main_user`.`PERSONAL_BIRTHDAY` IS NOT NULL AND `main_user`.`NAME` LIKE 'A%'

    Note: boolean fields with values Y/N, 1/0 and etc. can use true and false.

    When you need to indicate several conditions in a single call, use the following format: (operator methods can be replaced by operator codes)

    BitrixMainUserTable::query()
       ->where([
          ['ID', '>', 1],
          ['ACTIVE', true],
          ['PERSONAL_BIRTHDAY', '<>', null],
          ['NAME', 'like', 'A%']
       ])
       ->exec();
    
    // WHERE `main_user`.`ID` > 1 AND `main_user`.`ACTIVE` = 'Y' AND `main_user`.`PERSONAL_BIRTHDAY` IS NOT NULL AND `main_user`.`NAME` LIKE 'A%'

    OR and nested filters

    Storage of all filter conditions in Query uses the condition container BitrixMainEntityQueryFilterConditionTree. In addition to standard conditions, allows to add several instances of ConditionTree, thus creating any levels of branching and nesting depth.

    All calls of where shown above — is proxying to base container. Next two calls will lead to completely identical result:

    BitrixMainUserTable::query()
       ->where([
          ['ID', '>', 1],
          ['ACTIVE', true]
       ])
       ->exec();
    
    BitrixMainUserTable::query()
       ->where(Query::filter()->where([
          ["ID", '>', 1],
          ['ACTIVE', true]
       ]))->exec();
    
    // WHERE `main_user`.`ID` > 1 AND `main_user`.`ACTIVE` = 'Y'

    Uses filter object instead of array. This allows to create subfilters and change logic from AND to OR:

    BitrixMainUserTable::query()
       ->where('ACTIVE', true)
       ->where(Query::filter()
          ->logic('or')
          ->where([
             ['ID', 1],
             ['LOGIN', 'admin']
          ])
       )->exec();
    
    // WHERE `main_user`.`ACTIVE` = 'Y' AND (`main_user`.`ID` = 1 OR `main_user`.`LOGIN` = 'admin')

    The following chain of calls is permitted:

    BitrixMainUserTable::query()
       ->where('ACTIVE', true)
       ->where(Query::filter()
          ->logic('or')
          ->where('ID', 1)
          ->where('LOGIN', 'admin')
       )
       ->exec();
    
    // WHERE `main_user`.`ACTIVE` = 'Y' AND (`main_user`.`ID` = 1 OR `main_user`.`LOGIN` = 'admin')

    Expressions

    Filter allows ExpressionField used as field names. Such fields are automatically registered as entity field runtime.

    BitrixMainUserTable::query()
       ->where(new ExpressionField('LNG', 'LENGTH(%s)', 'LAST_NAME'), '>', 10)
       ->exec();
    
    // WHERE LENGTH(`main_user`.`LAST_NAME`) > '10'

    Helper is added to simplify such constructions. The helper builds calculated fields:

    BitrixMainUserTable::query()
       ->where(Query::expr()->length("LAST_NAME"), '>', 10)
       ->exec();
    
    // WHERE LENGTH(`main_user`.`LAST_NAME`) > '10'
    
    BitrixMainUserTable::query()
       ->addSelect(Query::expr()->count("ID"), 'CNT')
       ->exec();
    
    // SELECT COUNT(`main_user`.`ID`) AS `CNT` FROM `b_user` `main_user`

    Helper has the most popular SQL expressions:

    • count
    • countDistinct
    • sum
    • min
    • avg
    • max
    • length
    • lower
    • upper
    • concat

    Compatibility with getList

    If you use getList instead of Query call chain, inserts filter instead of array:

    BitrixMainUserTable::getList([
       'filter' => ['=ID' => 1]
    ]);
    
    BitrixMainUserTable::getList([
       'filter' => Query::filter()
          ->where('ID', 1)
    ]);
    
    // WHERE `main_user`.`ID` = 1

    JOIN conditions

    Descriptions of references is provided in the following format:

    new EntityReferenceField('GROUP', GroupTable::class,
        Join::on('this.GROUP_ID', 'ref.ID')
    )

    The method `on` is a brief and more semantically proper record Query::filter() with preset condition per columns. Returns filter instance and can build any conditions for JOIN:

    new EntityReferenceField('GROUP', GroupTable::class,
        Join::on('this.GROUP_ID', 'ref.ID')
            ->where('ref.TYPE', 'admin')
            ->whereIn('ref.OPTION', [
                new Column('this.OPTION1'),
                new Column('this.OPTION2'),
                new Column('this.OPTION3')
            ]
    )
    

    Anywhere with indicated field names its implied that you can specify any chain of branching:

    ->whereColumn('this.AUTHOR.UserGroup:USER.GROUP.OWNER.ID', 'ref.ID');

    Array format

    There is an existing method for conversion from array to object BitrixMainORMQueryFilterConditionTree::createFromArray to use filter as an array. Array format looks as follows:

    $filter = [
        ['FIELD', '>', 2],
        [
            'logic' => 'or',
            ['FIELD', '<', 8],
            ['SOME', 9]
        ],
        ['FIELD', 'in', [5, 7, 11]],
        ['FIELD', '=', ['column' => 'FIELD2']],
        ['FIELD', 'in', [
            ['column' => 'FIELD1'],
            ['value' => 'FIELD2'],
            'FIELD3']
        ],
        [
            'negative' => true,
            ['FIELD', '>', 19]
        ],
     ];
    

    With standard comparison, pass the value either directly:

    ['FIELD', '>', 2]
    

    or as an array with key value:

    ['FIELD', '>', ['value' => 2]]
    

    Use the array with key column in a comparison with column:

    ['FIELD1', '>', ['column' => 'FIELD2']]
    

    Nested filter are passed as similar nested arrays. Use self-title keys for replacement of object methods for negative() and to update the logic():

    $filter = [
        ['FIELD', '>', 2],
        [
            'logic' => 'or',
            ['FIELD', '<', 8],
            ['SOME', 9]
        ],
        [
            'negative' => true,
            ['FIELD', '>', 19]
        ]
    ]
    

    the rest of methods where* are replaced by corresponding comparison operators in, between, like and etc.

    ['FIELD', 'in', [5, 7, 11]]
    

    Attention: exercise close caution when using arrays. Do not insert unchecked data, passed by user, as a filter due to the possible dangerous conditions for data deployment in the database. Please, verify all input conditions using the field whitelist.

    Entity Relations (legacy variant)

    Create, update, delete, and select data makes quite sizeable functionality sufficient for the organization of work with unrelated data. However, web projects often imply relations among entities. That is why various ways of relating the entities are provided for:

  • One-to-one relations 1:1
  • One-to-many relations 1:N
  • Many-to-many M:N
  • 1:1 Relations

    The simplest type of relation is when an entity element refers to an element of another or the same entity. The examples described a deal with a book catalog, but until now book entries contained no indication of their authors. Let us implement that. First, let us describe the entity The author of the book:

    <?php
    
    namespace SomePartnerMyBooksCatalog;
    
    use BitrixMainEntity;
    
    class AuthorTable extends EntityDataManager
    {
    	public static function getTableName()
    	{
    		return 'my_book_author';
    	}
    
    	public static function getMap()
    	{
    		return array(
    			new EntityIntegerField('ID', array(
    				'primary' => true,
    				'autocomplete' => true
    			)),
    			new EntityStringField('NAME'),
    			new EntityStringField('LAST_NAME')
    		);
    	}
    }

    This example is based on the assumption that a book has only one author which is a person with a first name and a last name, and an entry about such author is contained in the entity Author. At the same time, one author may have many books. Thus, we obtain the relation 1 author : N books.

    This relation can be described as follows:

    class BookTable extends EntityDataManager
    {
    	...
    	public static function getMap()
    	{
    		return array(
    			...
    			new EntityIntegerField('AUTHOR_ID'),
    			new EntityReferenceField(
    				'AUTHOR',
    				'SomePartnerMyBooksCatalogAuthor',
    				array('=this.AUTHOR_ID' => 'ref.ID')
    			)
    		);
    	}
    	...
    }

    First, we should add a numeric field AUTHOR_ID where the author’s ID will be stored. Based on this field, the relation between entities will be configured through a new type of field – ReferenceField. This is a virtual field that has no actual reflection in the database:

    new EntityReferenceField(
    	'AUTHOR',
    	'SomePartnerMyBooksCatalogAuthor',
    	array('=this.AUTHOR_ID' => 'ref.ID')
    	array('join_type' => 'LEFT')
    )
    // LEFT JOIN my_book_author ON my_book.AUTHOR_ID = my_book_author.ID
    Parameters Description
    First a field name is set up
    Second name of a partner entity with which the relation is being established
    Third describes which fields are used to connect the entities, and is set up in a format similar to the filter for the section select in getList. Keys and values are field names with prefixes:

    • this. – field of the current entity,
    • ref. – field of the partner entity.
    Fourth, additional table connection type join_type can be specified – LEFT (by default), RIGHT, or INNER.

    Now the described relation can be used during data sampling:

    BookTable::getList(array(
    	'select' => array('TITLE', 'AUTHOR.NAME', 'AUTHOR.LAST_NAME')
    ));
    
    SELECT 
    	`somepartner_mybookscatalog_book`.`TITLE` AS `TITLE`,
    	`somepartner_mybookscatalog_author`.`NAME` AS `SOMEPARTNER_MYBOOKSCATALOG_AUTHOR_NAME`,
    	`somepartner_mybookscatalog_author`.`LAST_NAME` AS `SOMEPARTNER_MYBOOKSCATALOG_AUTHOR_LAST_NAME`
    FROM `my_book`
    LEFT JOIN `my_book_author` `somepartner_mybookscatalog_author` ON `somepartner_mybookscatalog_book`.`AUTHOR_ID` = `somepartner_mybookscatalog_author`.`ID`

    The switch to the Author entity is carried out by the entry AUTHOR: reference field name is specified, and the context switches to this entity after the dot. After that, the field name from this entity, including the Reference, can be specified, thus going even further and generating a new table connection:

    'select' => array('AUTHOR.CITY.COUNTRY.NAME')

    This could be a query to select a book author’s residence country if there was the structure Countries -> Cities -> Book authors living in the cities..

    In order to make sure that the field names of different entities do not overlap, the system generates unique aliases for the entities connected. Sometimes they are not very readable. In these cases, we can use the reassignment of aliases you already know:

    BookTable::getList(array(
    	'select' => array(
    		'TITLE',
    		'AUTHOR_NAME' => 'AUTHOR.NAME',
    		'AUTHOR_LAST_NAME' => 'AUTHOR.LAST_NAME'
    	)
    ));
    SELECT 
    	`somepartner_mybookscatalog_book`.`TITLE` AS `TITLE`,
    	`somepartner_mybookscatalog_author`.`NAME` AS `AUTHOR_NAME`,
    	`somepartner_mybookscatalog_author`.`LAST_NAME` AS `AUTHOR_LAST_NAME`
    FROM ...

    Similarly to the source entity, the symbol * can be used to select all the scalar fields of an entity. Short aliases can also be used:

    BookTable::getList(array(
    	'select' => array(
    		'TITLE',
    		'AR_' => 'AUTHOR.*'
    	)
    ));
    SELECT 
    	`somepartner_mybookscatalog_author`.`ID` AS `AR_ID`,
    	`somepartner_mybookscatalog_author`.`NAME` AS `AR_NAME`,
    	`somepartner_mybookscatalog_author`.`LAST_NAME` AS `AR_LAST_NAME`
    FROM `my_book` `somepartner_mybookscatalog_book` 
    LEFT JOIN `my_book_author` `somepartner_mybookscatalog_author` ON `somepartner_mybookscatalog_book`.`AUTHOR_ID` = `somepartner_mybookscatalog_author`.`ID`

    As mentioned above, the conditions for the entity relation are described similarly to a filter. This means that tables can be connected by several fields, and SqlExpression can also be used:

    $author_type = 5;
    
    new EntityReferenceField(
    	'AUTHOR',
    	'SomePartnerMyBooksCatalogAuthor',
    	array(
    		'=this.AUTHOR_ID' => 'ref.ID',
    		'=ref.TYPE' => new DBSqlExpression('?i', $author_type)
    	)
    )
    // LEFT JOIN my_book_author ON my_book.AUTHOR_ID = my_book_author.ID AND my_book_author.TYPE = 5

    The field ReferenceField, like other fields, can be described during data selection in the ‘runtime’ section and be used for the connection of other entities with which the relations were not initially described.

    If a field of an adjacent entity is used frequently, ExpressionField can be used, and the remote field can be determined as local.

    new EntityExpressionField('AUTHOR_NAME', '%', 'AUTHOR.NAME')

    In this example, the difference is not evident, but if you have a longer chain of junctions instead of AUTHOR.NAME, the use of one short name may come in handy.

    Relation 1:N, or back Reference

    The ReferenceField concept means that this field must be located in the entity N of the relation 1:N. Thus, the Reference must indicate just one entry: in the example above, it is assumed that a book may have only 1 author, and thus the ReferenceField in the Book entity indicates one entry of the Author entity.

    It is easy to select a book author because the Book entity has an indication of the relation with the author entity. But how can we select all of the books of an author if the Author entity contains no explicit indication to the Books?

    The point is that the Reference described in the book entity is sufficient for two-way selection; we only have to use a special syntax:

    SomePartnerMyBooksCatalogAuthorTable::getList(array(
    	'select' => array(
    		'NAME',
    		'LAST_NAME',
    		'BOOK_TITLE' => 'SomePartnerMyBooksCatalogBookTable:AUTHOR.TITLE'
    	)
    ));
    SELECT 
    	`somepartner_mybookscatalog_author`.`NAME` AS `NAME`,
    	`somepartner_mybookscatalog_author`.`LAST_NAME` AS `LAST_NAME`,
    	`somepartner_mybookscatalog_author_book_author`.`TITLE` AS `BOOK_TITLE`
    FROM `my_book_author` `somepartner_mybookscatalog_author` 
    LEFT JOIN `my_book` `somepartner_mybookscatalog_author_book_author` ON `somepartner_mybookscatalog_author_book_author`.`AUTHOR_ID` = `somepartner_mybookscatalog_author`.`ID`

    Instead of the Reference name, we have to indicate the Name of an entity which has a Reference to the current entity:Name of the reference to the current entity. Following such construction, the context switches to the Book entity, and the TITLE field and other fields can be selected in it.

    Relations M:N

    Any book can be characterized from the point of view of genre/category, whether business or fiction literature, history or training books, thrillers, comedies, dramas, books on marketing, development, sales, etc. For simplicity sake, let us call all of this as tags, and assign several tags to each book.

    Let us describe the entity of tags:

    <?php
    
    namespace SomePartnerMyBooksCatalog;
    
    use BitrixMainEntity;
    
    class TagTable extends EntityDataManager
    {
    	public static function getTableName()
    	{
    		return 'my_book_tag';
    	}
    
    	public static function getMap()
    	{
    		return array(
    			new EntityIntegerField('ID', array(
    				'primary' => true,
    				'autocomplete' => true
    			)),
    			new EntityStringField('NAME')
    		);
    	}
    }

    In order to connect this entity with the Books using the principle N:M (one book may have several tags, one tag may be connected with several books), it is necessary to create in the database an interim entity with a table to store data about the connections of books with tags.

    < true
    			)),
    			new EntityReferenceField(
    				'BOOK',
    				'SomePartnerMyBooksCatalogBook',
    				array('=this.BOOK_ID' => 'ref.ID')
    			),
    			new EntityIntegerField('TAG_ID', array(
    				'primary' => true
    			)),
    			new EntityReferenceField(
    				'TAG',
    				'SomePartnerMyBooksCatalogTag',
    				array('=this.TAG_ID' => 'ref.ID')
    			)
    		);
    	}
    }

    In this case, the entity is just required to store the connection Book ID – tag ID, and relevant ReferenceFields will help to describe this connection in software.

    The interim entity itself may be of little interest. Standard tasks in this case involve obtaining a list of tags for a book or a list of books according to a tag. These tasks are solved using the methods of work with the Reference described above:

    // tags for the book with ID = 5
    SomePartnerMyBooksCatalogBookTable::getList(array(
    	'filter' => array('=ID' => 5),
    	'select' => array(
    		'ID',
    		'TITLE',
    		'TAG_NAME' => 'SomePartnerMyBooksCatalogBookTag:BOOK.TAG.NAME'
    	)
    ));
    SELECT 
    	`somepartner_mybookscatalog_book`.`ID` AS `ID`,
    	`somepartner_mybookscatalog_book`.`TITLE` AS `TITLE`,
    	`somepartner_mybookscatalog_book_book_tag_book_tag`.`NAME` AS `TAG_NAME`
    FROM `my_book` `somepartner_mybookscatalog_book` 
    LEFT JOIN `my_book_to_tag` `somepartner_mybookscatalog_book_book_tag_book` ON `somepartner_mybookscatalog_book_book_tag_book`.`BOOK_ID` = `somepartner_mybookscatalog_book`.`ID`
    LEFT JOIN `my_book_tag` `somepartner_mybookscatalog_book_book_tag_book_tag` ON `somepartner_mybookscatalog_book_book_tag_book`.`TAG_ID` = `somepartner_mybookscatalog_book_book_tag_book_tag`.`ID`
    WHERE `somepartner_mybookscatalog_book`.`ID` = 5

    The entry SomePartnerMyBooksCatalogBookTag:BOOK.TAG.NAME may seem complicated, but it is actually pretty simple when considered by parts:

    Parameters Description
    BookTable::getList source entity – BookTable
    SomePartnerMyBooksCatalogBookTag:BOOK switch to the entity BookTag through its reference BOOK, current entity – BookTag
    TAG switch following the reference TAG from BookTag, current entity – Tag
    NAME a field from the current entity Tag

    The call chain will be very similar in order to obtain books with a specific tag:

    // books for the tag with ID = 11
    SomePartnerMyBooksCatalogTagTable::getList(array(
    	'filter' => array('=ID' => 11),
    	'select' => array(
    		'ID',
    		'NAME',
    		'BOOK_TITLE' => 'SomePartnerMyBooksCatalogBookTag:TAG.BOOK.TITLE'
    	)
    ));

    Thus, using two types of switch between entities, REFERENCE and Entity:REFERENCE, the necessary related information can be easily accessed.

    Using ORM or why fields from SELECT and ORDER BY are automatically included into GROUP BY

    Very often, developers have a question when using ORM and during data retrieval specifically: «Why did GROUP BY automatically include some fields? I haven’t indicated this directly when calling getList/Query«. This article overviews such phenomenon and why it’s designed this way.

    In the past, Bitrix24 products have supported three DMBS: MySQL, Oracle, SQL Server. And if MySQL is clearly familiar to and used by the general developer community, the Oracle and SQL Server is the choice of more serious and large-scale projects. Enterprise segment mandates DMBS to satisfy higher-level requirements, including compliance to standards.

    Below is the example of how the data retrieval operates in the abovelisted systems:

    SELECT t.* FROM a_city t

    This is a standard fetching of data from the table: you have 7 populated cities, located in the numbered regional areas.

    Set conditions for retrieval — only No.1 and No.1 regions:

    SELECT NAME, REGION FROM a_city t WHERE t.REGION IN ('1','2') 

    As you can see, data format didn’t change, only the filtering is performed.

    Now group the retrieved selection — count how many cities are located in each region:

    SELECT COUNT(*), t.REGION FROM a_city t WHERE t.REGION IN ('1','2') GROUP BY t.REGION 

    Previous selection was «collapsed» by unique REGION values and each of such value the number of «collapsed records» was calculated:

    Group the selection SELECT NAME, REGION FROM a_city t WHERE t.REGION IN ('1','2') by region.

    Up to this moment this use case is fairly straightforward and simple.

    Now, let’s overview quite widespread case: developer decides that he some cities have insufficient number of inhabitants in the grouped selection:

    SELECT COUNT(*), t.REGION, POPULATION FROM a_city t WHERE t.REGION IN ('1','2') GROUP BY t.REGION 

    Great, MySQL has successfully processed the query and returned a number of inhabitants (but in fact, it didn’t), developer is satisfied (incorrectly). This issue, it seems, is resolved, but somehow the same query in Oracle returns an error

    ORA-00979: not a GROUP BY expression

    а SQL Server responds

    Column ‘a_city.POPULATION’ is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.

    Let’s take a look on what numbers have been returned by MySQL instead of an error:

    SELECT NAME, REGION, POPULATION FROM a_city t WHERE t.REGION IN ('1','2') 

    This is how the selection looked like before grouping had started. It seems, MySQL just took the first available values for each region. What does it give to the developer? Absolutely nothing: these numbers are meaningless, their values cannot be predicted.

    The solution is to select the summary number of inhabitants for a region:

    SELECT COUNT(*), t.REGION, SUM(POPULATION) FROM a_city t WHERE t.REGION IN ('1','2') GROUP BY t.REGION  

    or medium number of inhabitants in cities for each region:

    SELECT COUNT(*), t.REGION, ROUND(AVG(POPULATION),0) FROM a_city t WHERE t.REGION IN ('1','2') GROUP BY t.REGION  

    In such case, all databases will successfully process the query, because now they recognize how to handle the column POPULATION during «collapsing».

    Important rule: when selection has an aggregation or a grouping (aggregation by unique value) for at least single column, the rest of selectable columns also must be aggregated or grouped.

    But, let’s return to the query builder in в Bitrix Framework: it monitors compliance to this rule and upon detecting non-aggregated fields, adds them to the section GROUP BY.

    The provided example with cities with unavailable direct aggregation, query will look as follows:

    SELECT COUNT(*), t.REGION, t.POPULATION FROM a_city t WHERE t.REGION IN ('1','2') GROUP BY t.REGION, t.POPULATION  

    The result demonstrates, how many inhabitants do cities in the specific regions have. Only this way the DMBS recognizes the developer’s input.

    So, the result is as follows: you need to either indicate the aggregation or group by field, otherwise its value will be meaningless. For example, developer decides to add a string by ID and sees how the ID field automatically goes to GROUP BY and «breaks» the result:

    SELECT COUNT(*), t.REGION, SUM(t.POPULATION) FROM a_city t WHERE t.REGION IN ('1','2') GROUP BY t.REGION, t.ID ORDER BY ID DESC  

    As you might think, if ID is not added to the grouping, the Oracle and SQL Server will refuse to execute the query again, referring to uncertainty in data aggregation. What is the reason this time?

    The sorting in this case occurs already after grouping/aggregating the data. It’s not important, if ID is included in the source table: after the grouping we get a new virtual table with aggregated data.

    It means, you need to add the ID field into the intermediary grouped result: only then the sorting by this field will be possible. And at this point, we are circling back to the previous rule and clarify it:

    Important: When the fetched data has an aggregated or grouped data (aggregated by unique value) for at least a single column, then al the rest of columns from SELECT and ORDER BY must be aggregated or grouped in the same manner.

    Adhering to this rule will help you understand which calculations are done by the database for you. If you disregard it, you will be confused by the retrieved results (in case of MySQL): the data will seem truthful, but incorrect.

    Note: this rule doesn’t apply to WHERE: this filtration is performed specifically BEFORE data is grouped, with desirable original column values. Filtering by aggregated values is performed in the section HAVING and if it contains column without aggregation — this column values must be pre-grouped before getting any meaningful data in the result. Now, the ORM query builder will ensure the distribution in filter WHERE and HAVING. You don’t have to pay special attention to this aspect, as well as to automatic grouping.

    Conclusion

    If an automatic adding of fields to GROUP BY in a specific query is an unpleasant surprise for you, then:

    • You have added field to the fetched selection by habit or accidentally; in reality you don’t need its value
    • или

    • You have forgotten to indicate the aggregation (MAX, MIN, SUM, AVG, etc.)

    MySQL had executed the query, without issuing an error, only due to its tolerance to inaccuracies (by default). This is a disservice, because it has returned a false and meaningless result that looks valid at the first glance.

    ORM in Bitrix Framework corrects such inaccuracies independently. In case of direct queries, use the setting ONLY_FULL_GROUP_BY to disable such behaviour and force MySQL to adhere to standard and common sense.

    Page Navigation

    How to implement page navigation in core D7 for ORM selection (Available from version 16.0.0).

    Basics

    Page navigation is an auxiliary object for UI. For legacy kernel/core, page navigation was implemented in the query result object. Handling of limits required calling a special function CDBResult::NavQuery() to pass the query text. However, page navigation in D7 is just an auxiliary object for UI. Query is deemed as primary and page navigation just helps to substitute correct limit and offset.

    Limit requirement in queries. It’s incorrect to relegate php with all potential fetching without limitations. In case the retrieval is executed without limits, you have an option to use new navigation. This means that developer does this at his/her own risk, without vendor recommendation. And you’ll need to solve issues arising from using different databases and other tasks.

    For page navigation, you have to know the number of records. Just as in the old kernel/core, you need to execute a separate query with COUNT. It’s more effective than retrieving a large chunk of data records. For small-scale fetching this issue is not as prevalent. However, if you are getting a voluminous batch of data indeed, current core page navigation will be able to handle it even without COUNT.

    Page navigation tools

    • BitrixMainUIPageNavigation class for standard navigation;
    • BitrixMainUIReversePageNavigation class for reverse navigation;
    • BitrixMainUIAdminPageNavigation class for navigation in administration section;
    • main.pagenavigation components with .default templates (based at the old component’s round), admin (section), modern (for grids).

    Page navigation supports both parameters in GET and SEF. Now you need to directly indicate a navigation identifier instead of global variable $NavNum. The following URL are supported:

    /page.php?nav-cars=page-5&nav-books=page-2&other=params
    /page.php?nav-cars=page-5-size-20&nav-books=page-2
    /page.php?nav-cars=page-all&nav-books=page-2
    /dir/nav-cars/page-2/size-20/
    /dir/nav-cars/page-all/?other=params
    /dir/nav-cars/page-5/nav-books/page-2/size-10

    where nav-cars and nav-books — identifiers for two different page navigations at a single page.

    Page navigation in the administrative section

    $nav = new BitrixMainUIAdminPageNavigation("nav-culture");
    
    $cultureList = CultureTable::getList(array(
       'order' => array(strtoupper($by) => $order),
       'count_total' => true,
       'offset' => $nav->getOffset(),
       'limit' => $nav->getLimit(),
    ));
    
    $nav->setRecordCount($cultureList->getCount());
    
    $adminList->setNavigation($nav, Loc::getMessage("PAGES"));
    
    while($culture = $cultureList->fetch())
    {
    }

    'count_total' => true — new parameter that forces ORM to execute an individual query COUNT, resulting in retrieved object and can be received via $cultureList->getCount(). Without this parameter there’s no point to execute getCount() and event counterproductive (you’ll get a thrown exception).

    $nav->getOffset() returns first record position for the current navigation page.
    $nav->getLimit() returns number of records at the page or 0, if «all records» are selected.
    $nav->setRecordCount() sets number of records for navigation.

    For convenience, administration section now has an added function $adminList->setNavigation() which just connects the component main.pagenavigation with an admin template.

    Direct page navigation in Public section

    <?
    $filter = array("=IBLOCK_ID"=>6);
    
    $nav = new BitrixMainUIPageNavigation("nav-more-news");
    $nav->allowAllRecords(true)
       ->setPageSize(5)
       ->initFromUri();
    
    $newsList = BitrixIblockElementTable::getList(
       array(
          "filter" => $filter,
          "count_total" => true,
          "offset" => $nav->getOffset(),
          "limit" => $nav->getLimit(),
       )
    );
    
    $nav->setRecordCount($newsList->getCount());
    
    while($news = $newsList->fetch())
    {
    }
    ?>
    
    <?
    $APPLICATION->IncludeComponent(
       "bitrix:main.pagenavigation",
       "",
       array(
          "NAV_OBJECT" => $nav,
          "SEF_MODE" => "Y",
       ),
       false
    );
    ?>

    Now navigation initialization is added:

    $nav->allowAllRecords(true) ->setPageSize(5) ->initFromUri();

    Public section now has more settings, such as «allow all records», and it’s all needed before initializing the object (the admin section was already re-defined by descendant class). Additionally, getting current page from URL is optional, because it can be sourced and set from anywhere.

    The component has one important parameter: "SEF_MODE" => "Y" used to generate SEF for page navigation.

    Reverse page navigation in Public section

    <?
    $filter = array("=IBLOCK_ID"=>6);
    
    $cnt = BitrixIblockElementTable::getCount($filter);
    
    $nav = new BitrixMainUIReversePageNavigation("nav-news", $cnt);
    $nav->allowAllRecords(true)
       ->setPageSize(5)
       ->initFromUri();
    
    $newsList = BitrixIblockElementTable::getList(
       array(
          "filter" => $filter,
          "offset" => $nav->getOffset(),
          "limit" => $nav->getLimit(),
       )
    );
    
    while($news = $newsList->fetch())
    {
    }
    ?>
    
    <?
    $APPLICATION->IncludeComponent(
       "bitrix:main.pagenavigation",
       "",
       array(
          "NAV_OBJECT" => $nav,
          "SEF_MODE" => "Y",
       ),
       false
    );
    ?>

    The descendant class ReversePageNavigation is constructed with mandatory received number of records already in the constructor. This is done because the reverse navigation cannot mathematically operate without the number of records. The method getCount() was updated in ORM for this purpose: now it receives filter parameter on input.

    Page navigation in UI grid

    <?
    $filter = array("=IBLOCK_ID"=>6);
    
    $nav = new BitrixMainUIPageNavigation("nav-grid-news");
    $nav->allowAllRecords(true)
       ->setPageSize(5)
       ->initFromUri();
    
    $newsList = BitrixIblockElementTable::getList(
       array(
          "filter" => $filter,
          "count_total" => true,
          "offset" => $nav->getOffset(),
          "limit" => $nav->getLimit(),
       )
    );
    
    $rows = array();
    while($news = $newsList->fetch())
    {
       $cols = array(
          "ID" => $news["ID"],
          "NAME" => $news["NAME"],
       );
    
       $rows[] = array(
          "columns"=>$cols,
       );
    }
    
    $nav->setRecordCount($newsList->getCount());
    ?>
    
    <?
    $APPLICATION->IncludeComponent(
       "bitrix:main.interface.grid",
       "",
       array(
          "GRID_ID"=>"news_grid",
          "HEADERS"=>array(
             array("id"=>"ID", "name"=>"ID", "default"=>true),
             array("id"=>"NAME", "name"=>"Name", "default"=>true),
          ),
          "ROWS"=>$rows,
          "FOOTER"=>array(array("title"=>"Total", "value"=>$newsList->getCount())),
          "NAV_OBJECT"=>$nav,
          "NAV_PARAMS"=>array(
             "SEF_MODE" => "Y"
          ),
       )
    );
    ?>

    "NAV_PARAMS"=>array( "SEF_MODE" => "Y" ) — new parameter that passes new parameter values to navigation component.

    Page navigation without COUNT

    Only direct navigation is possible. General principle: developer manually pinpoints a little more records back and forth to determine, if you can show «more» records at the page. This helps developer to define the limit shown at the page.

    <?
    $filter = array("=IBLOCK_ID"=>6);
    
    $nav = new BitrixMainUIPageNavigation("nav-less-news");
    $nav->allowAllRecords(true)
       ->setPageSize(5)
       ->initFromUri();
    
    $newsList = BitrixIblockElementTable::getList(
       array(
          "filter" => $filter,
          "offset" => ($offset = $nav->getOffset()),
          "limit" => (($limit = $nav->getLimit()) > 0? $limit + 1 : 0),
       )
    );
    
    $n = 0;
    while($news = $newsList->fetch())
    {
       $n++;
       if($limit > 0 && $n > $limit)
       {
          break;
       }
    }
    
    $nav->setRecordCount($offset + $n);
    ?>
    
    <?
    $APPLICATION->IncludeComponent(
       "bitrix:main.pagenavigation",
       "",
       array(
          "NAV_OBJECT" => $nav,
          "SEF_MODE" => "Y",
          "SHOW_COUNT" => "N",
       ),
       false
    );
    ?>

    Attention: «already known» number of records is inserted: $nav->setRecordCount($offset + $n);

    This is done for navigation component to understand that pages are still available. However, for templates that show number of records, you need to pass "SHOW_COUNT" => "N" to prevent attempts to display an incorrect number (this number will be correct only at the last page).

    Attention! Navigation without COUNT indicates a very significant amount of records. That’s why it’s not recommended to allow all records via the method $nav->allowAllRecords(true).

    Please see the file with examples.

    ORM integration in the information blocks

    Starting from iblock module version 19.0.0 now supports ORM when handling iblock elements.

    Backward compatibility

    Legacy kernel iblock module events are not supported. Standard features of other modules, based on such event calls, are no longer supported as well.

    Presently, functional blocks support is not implemented:

      For API handling the elementы (Add, Edit, Delete)

    • PREVIEW_PICTURE, DETAIL_PICTURE image resize;
    • iblock faceted index update (if used);
    • element seo-parameter update;
    • tagged cache reset;
    • access permission assignment;
    • document processing support;
    • drive space quote check for file properties;
    • product availability with SKU recalculation;
    • price recalculation for product sorting with SKU;
    • indexing by search module;
    • element operations logging.

      For API handling with sections (adding, editing, deleting).

    • automatic recalculation for LEFT_MARGIN, RIGHT_MARGIN, GLOBAL_ACTIVE, DEPTH_LEVEL field value;
    • PREVIEW_PICTURE, DETAIL_PICTURE image resize;
    • iblock faceted index update (if used);
    • updating seo parameters for a section and child entities (subsections and elements);
    • tagged cache rest;
    • assigning access permissions to section and child entities;
    • drive space quote check for file fields;
    • indexing by search module;
    • sections property assignment;
    • section operations logging.

    Attention! Listed features must be implemented independently.

    List of related links:

    • [ds]REST ORM API for iblocks[/ds][di]
      REST API for information blocks is available from the iblocks module version 20.5.0.

      Integration with ORM is taken as the basis for accessing the iblock data via REST. Specifically, the main concept was adopted: a single iblock is an individual ORM entity and iblock element is a record (object) of entity.

      Learn more…[/di]

    Concept and architecture

    Each iblock is an independent data type with its own set of properties. It’s represented by a separate entity in the ORM:

    Entity class name includes a new field value taken from API symbolic code iblock config. This code ensures the class unique status independently from ID and environment.

    Important! You need to set API symbolic code (API_CODE field) for specific block to start using ORM. It’s a string with a symbol from 1 to 50, starting from a letter and consisting of Latin characters and numerals.

    Property contains not just scalar values, but also relations with individual mini-entities having two key fields: VALUE and DESCRIPTION. Singular properties are presented in the iblock element by the field Reference, multiple properties — OneToMany:

    You can add extra fields to entities of some property types. For example, link to a bound iblock element:

    More details on basic property types

    You can find direction among a large number of properties using [ds]annotations mechanism[/ds][di]Majority of Object and Collection methods — are virtual, processed via magic__call. At the same time, they are designed for the intuitively clear and self-explanatory named methods and without autocomplete in IDE their value significantly decreases.
    For the IDE to know about their existence and to help find direction in the large number of classes and methods, Bitrix24 have provided a special service file with annotations for all entities.

    Learn more …[/di]. All iblocks will be described as ORM entities when indexing the iblock module. You need to clearly designate iblock class to get hints in the code:

    // connecting iblock module
    BitrixMainLoader::includeModule('iblock');
    
    // input data
    $iblockId = 32;
    $iblockElementId = 678;
    
    // iblock object
    $iblock = BitrixIblockIblock::wakeUp($iblockId);
    
    // element object
    /** @var BitrixIblockElementsEO_ElementLink $element */
    $element = $iblock->getEntityDataClass()::getByPrimary($iblockElementId)
        ->fetchObject();
    
    // getting SOME_STRING property
    $element->getSomeString();
    

    Class autoloading automatically triggers the call getEntityDataClass(), i. e. you won’t have to pre-compile iblock entity.

    If you want to use IDE hints on iblock element type checking, you need to directly set its class by annotation type:

    • for element;
      /** @var BitrixIblockElementsEO_ElementLink $element */
      
    • for collection,
      /** @var BitrixIblockElementsEO_ElementLink_Collection $element */
      

    where Link in the class name — iblock API symbolic code.

    Attention! Property’s symbolic code shall not match with element field name in an iblock. Otherwise, you won’t be able to work with property by name (symbolic code) in ORM.

    Read and write

    Getting property values requires only their names listed in query:

    $elements = $iblock->getEntityDataClass()::getList([
        'select' => ['ID', 'SOME_FIELD', 'ANOTHER_FIELD.ELEMENT']
    ])->fetchCollection();
    
    foreach ($elements as $element)
    {
        echo $element->getSomeField()->getValue();
        echo $element->getAnotherField()->getElement()->getTitle();
    }
    

    Standard ORM relations mechanics are supported.

    When filtering, keep in mind the property structure. Property field is a link (Reference or OneToMany) to a complete entity, that’s why the property name indicated in filter leads to nothing:

    // incorrect example
    $element = $iblock->getEntityDataClass()::query()
        ->where('SOME_FIELD', 'some value')
        ->fetchObject();
    

    Property value is stored in the entity field, always named as VALUE. That’s why it’s correct to indicate it specifically in filter:

    // correct example
    $element = $iblock->getEntityDataClass()::query()
        ->where('SOME_FIELD.VALUE', 'some value')
        ->fetchObject();
    

    You can use both constructor of corresponding class for creating new object,

    $newElement = new BitrixIblockElementsEO_ElementLink;
    

    as well as entity factory.

    $newElement = $iblock->getEntityDataClass()::createObject();
    

    Property values and property descriptions are edited directly via property object:

    // setting string value
    $element->getSomeString()->setValue('new value');
    
    // setting description
    $element->getSomeString()->setDescription('new descr');
    
    // setting element binding
    $element->getSomeElement()->setElement($anotherElement);
    

    Additionally, you can set the value directly in the property field:

    $element->setSomeString('new value');
    

    You can also use pseudo-object for property value BitrixIblockORMPropertyValue:

    use BitrixIblockORMPropertyValue;
    
    // value only
    $value = new PropertyValue('new value');
    
    // value and description
    $value = new PropertyValue('new value', 'new descr');
    
    // setting value/description
    $element->setSomeString($value);  
    

    Setting values for multiple properties operates similarly with the only difference that it’s performed for OneToMany and not for Reference:

    use BitrixIblockORMPropertyValue;
    
    foreach ($element->getOtherField() as $value)
    {
        $value->setValue('new value');
        $value->setDescription('new descr');
    }
    
    $element->addToOtherField(new PropertyValue('new value'));
    $element->addToOtherField(new PropertyValue('new value', 'new descr'));
    

    Element object is saved in the same manner as any other ORM object:

    $element->save();
    

    Despite the property values being actually stored in different tables as relations with object, everything will be placed in correct locations upon saving.

    You can Delete element via the ‘delete’ object-method:

    $element->delete();
    

    Property values are processed automatically when deleting as well as saving. Section associations are deleted as well.

    Events and custom property types

    Events

    You can use standard ORM mechanisms to subscribe to iblock entity events:

    use BitrixMainORMDataDataManager;
    		
    // iblock ID
    $iblockId = 32;
    
    // iblock object
    $iblock = BitrixIblockIblock::wakeUp($iblockId);
    
    // event manager
    $em = BitrixMainORMEventManager::getInstance();
    
    $em->registerEventHandler(
        $iblock->getEntityDataClass(),
        DataManager::EVENT_ON_BEFORE_ADD,
        'mymodule',
        'MyClass',
        'method'
    );
    

    Attention! At this moment, active iblock events support is not available.

    Custom property types

    You need to set an individual callback GetORMFields to add your own fields to entity when describing the property:

    public static function GetUserTypeDescription()
    {
        return [
            ...
            "GetORMFields" => array(__CLASS__, "GetORMFields"),
        ];
    }
    
    /**
     * @param BitrixMainORMEntity $valueEntity
     * @param BitrixIblockProperty $property
     */
    public static function GetORMFields($valueEntity, $property)
    {
        $valueEntity->addField(
            ...
        );
    }
    

    Inheritance

    Iblock ORM API allows to [dw]inherit[/dw][di]
    Option to inherit elements from ORM is available fromiblock module version 21.500.0.
    [/di]entity for specific iblock, as well as to supplement or re-define its behaviour. You can inherit classes for the entity itself and its object. It’s sufficient to indicate Table iblock class for inheriting an entity:

    class MyExtTable extends BitrixIblockElementsElement{API_CODE}Table
    {
    }
    

    Parent class name uses API CODE from iblock settings. Object class description is specified by [ds]ORM general rules[/ds][di]
    All entity objects are descendants for class BitrixMainORMObjectifyEntityObject, with each entity having its own class for objects. By default, such class is created automatically, in passing.

    Learn more…[/di], including configuring the class Table:

    class MyExtTable extends BitrixIblockElementsElement{API_CODE}Table
    {
        public static function getObjectClass()
        {
            return MyExt::class;
        }
    }
    
    class MyExt extends EO_MyExt
    {
    }
    

    Components

    Components are the main tool for developers when it comes to working with the projects created with Bitrix Framework. Developers’ professionalism largely depends on their ability to use this tool.

    Component — is a logically completed code intended for the retrieval of information from infoblocks and other sources and its conversion to HTML code for displaying as fragments of web pages. It consists of a component proper (controller) and a template (form). The component manipulates data using API of one or more modules. The component template displays data on the page.

    Classical workflow diagram of a component:

    Carrier Rider Mapper

    Components implement the design pattern Carrier Rider Mapper to the full extent.

    • Carrier. Carrier of any information that can be accessed by several clients simultaneously.
    • Rider (Reader or Writer) — objects through which the Carrier provides acces to the information stored in it. The clients read and write information stored in the Carrier exclusively using only the objects of the Reader and Writer type. Thus, Reader and Writer are information access interfaces.
    • Mapper (Scanneror Formatter) – objects that cover Reader or Writer, accordingly. Mappers are responsible for the conversion of data formats to the formats convenient for the clients.

    Information flow from carrier to client (read): Carrier -> Reader -> Scanner -> Client.

    Information flow from client to carrier (write): Carrier <- Writer <- Formatter <- Client.

    Introducing mappers between Carrier-Rider and clients permits connecting one and the same Carrier-Rider with different types of clients by using appropriate (different) mappers.

    The Use of Components

    Components are used for the following purposes:

    • Creating fully functionining sections on a website, e.g. news section, photo gallery, goods catalog, etc. These sections are created using composite components;
    • Creating frequently used areas in the template or on website pages (e.g., authorization forms and subscription forms);
    • Representing dynamically updated information (e.g., news feed and an occasional photo);
    • Performing any other operations with data.

    When placing a component on a page the user sets parameters that are used to retrieve the page program module on this particular page. The set of parameters (including their types) is to be established in the component parameter file as a special hash array.

    Several components may be placed on a website page. One component may be in charge of displaying the text of the article, the second may display banners, the third may display news related to the subject of this article, etc. One and the same component may be used on different pages of the website and on any website within the same product installation.

    Components may be simple and composite.

    Main Specifics of Component Technique

    • Logic and visual form are separated in components. Logic is the component itself, and form is the component display template. Several forms may be created for the same logic, including forms which depend on the template of the current website. Visual form (display template) may be written using any template language that can be connected from PHP. For example, templates may be written using PHP, Smarty, XSL etc.
    • There is no need to change the component logic in order to change the specifics of its display. That is why it is quite easy to manage the external appearance of the information displayed by the component. Display template is by far simpler than the component as a whole.
    • Components are stored together in one folder. It ensures the integrity and transparency of a website structure. The folder is available for queries. This means the component and its templates can easily connect their additional resources.

    Note: The evolution of the development of Bitrix Framework using the components of regular standards has led to the components that are currently used in 2.0 standard. The regular standard (version 1.0 components) is not supported any longer. However, sometimes outdated components can be found in the projects operating on older versions of Bitrix Site Manager.

    Simple and composite components

    Components are subdivided into simple (single page) and composite (compound, multi-page). A simple component displays on one physical page available under a specific URL. A composite component replaces a set of simple components. For example, a news section can be implemented using several simple components located on a separate physical page each, or using one composite component located on one physical page.

    In terms of the structure and means of connection, simple and composite components are very similar. However, in terms of functioning, they are very different.

    Simple components

    Simple (single page) components create an area on a page surface. They come at hand when you need to render data from different modules on a single page (for example: blogs and information blocks) or data from different information blocks (news and commercial catalog). Evidently, simple components are not convenient to create a whole news section or a catalog because this requires the creation of a considerable number of pages and controlling their linkage.

    Composite components

    Composite (compound, multi-page) components create the site sections. For example, the commercial catalog component creates the whole catalog section including the catalogs page, the groups page and the product pages. Essentially, a composite component is a set of pages from the visitor’s point of view, but represented by a single page physically. The foundation of composite components are simple components and the use of the single-page logic.

    The advantage of composite components is the automatic preparation of parameters of the underlying simple components: you do not have to care about the interrelationships of internal parameters.

    Composite components resolve the following problems:

    • Avoid the creation of numerous pages to add all required sub-components to them.
      There is no need to configure common parameters of each individual component.
    • Avoid the need to establish composite associations between sub-components.
      For example, you do not have to configure the link creation rules etc.
    • Components (even those driven by custom view templates) can be enriched with new pages.
      For example, if you add page (sub-component) showing 10 most rated forum visitors, this page becomes momentarily available in the public section.
    • The component view template can be changed at one stroke instead of configuring each of the underlying sub-components.

    A typical composite component workflow is as follows:

    1. the component takes user actions on input and defines the exact page to show to a visitor and attaches an appropriate template to the page;
    2. the page template attaches simple components and configures their parameters as required;
    3. the simple components perform the requested operations: request data from the system kernel, format and display data to the visitor; show various controls (links, forms, buttons etc.);
    4. the visitor sends a new request to the composite component using the controls.

    Example

    Let us consider the difference between composite and simple components using the creation of a news section as an example.

    A news section may be organized, for example, by placing a news list component on the page index.php and a detailed news component on the page news.php. In this case, the news list component must have entry parameters set in such a manner so that it could generate links to the page containing the detailed news (with news code). The detailed news component must have entry parameters set in such a manner so that it could generate a link to the page containing the news list.

    ЧTo set a link to the page with detailed news the path to this page must be specified along with the name of the parameter in which a news code will be submitted for display. The same parameter name must also be set in the entry parameters of the detailed news component to indicate where it shall retrieve the code of the news to be displayed. Even in this oversimplified case, the settings are not so simple. What if it is a set of tens of components of a forum?

    The use of a composite news component is a better option for website developers. For example, this component can be simply installed on the page index.php. The composite component itself will take care of link and parameter alignment. No further actions are required on the part of the website developer.

    Template pages of the composite component will contain a connection of the relevant regular components with the correct setting of their entry parameters. Regular components will perform their regular functions: they do no care who retrieves them or why. For regular components, only the correct setting of their entry paramenters is important.

    MVC pattern is implemented as follows:

    • News composite component (controller) receives an HTTP query (user’s actions);
    • News composite component (controller) checks if a news code is set through the HTTP query and connects from its template the page containing the news list or the page containing the detailed news (view);
    • The connected page, in its turn, connects the relevant regular component at the same time installing entry paramenters accordingly;
    • Regular component performs its work: requests data from the kernel (model), formats them, and displays to the visitor, and also displays control elements (links, forms, buttons, etc.);
    • The user sends a new HTTP query to the news composite component (controller) using control elements;
    • The procedure is repeated as necessary.

    Location of a Component in the System and Its Connection

    All components are stored in the /bitrix/components/ folder. The system components are in the /bitrix/components/bitrix/ folder. The content of this folder is updated by the update system and must not be modified by users.

    Note! Modifying the system component folder /bitrix/components/bitrix/ can have unpredictable effects.

    Users can store their own components in any subfolder of /bitrix/components/, or in the /bitrix/components/ root.

    Naming convention

    The name of a subfolder in /bitrix/components/ is the component namespace. For example, all system components belong to the bitrix namespace. When creating a custom component, you should create a namespace and put custom components there.

    For example, a system component news exists located in the bitrix namespace. If you need a custom news component implementing functions unavailable in the standard system component, you should create a namespace (subfolder) in /bitrix/components/ (for example, demo) and put the custom component news there. The two components news will be available in different namespaces: bitrix:news and demo:news; both of them will be shown in the component tree in the visual editor.

    Names of the components are as follows identifier1.identifier2…. For example, catalog, catalog.list, catalog.section.element, etc. The names should be built hierarchically starting from the general term and ending with specific purpose of the component. For example,the component showing the list of goods of this group may have the name catalog.section.elements. The full name of the component is the name of the component indicating the namespace. The full name looks like name_space:component_name. For example, bitrix:catalog.list. If the component is located outside the namespace, the namespace will not be specified. For example, catalog.section.

    Component Connection

    In the most general terms, the component connects as follows:

    <?$APPLICATION->IncludeComponent(
    componentName, // component name
    componentTemplate, // component template, empty line if default template is used
    arParams=array(), // parameters
    parentComponent=null,
    arFunctionParams=array()
    );?>

    The following preset variables are available inside the component (file component.php):

    $componentName – full name of a component (e.g.: bitrix:news.list).

    $componentTemplate – the template with which the component is retrieved.

    $arParams – entry parameters of the component (i.e. parameters with which the component is retrieved). Parameters are also available by their names.

    $componentPath – a path to the component from the website root (e.g.: /bitrix/components/bitrix/news.list).

    $parentComponentName – name of the parent component (empty if there is no parent).

    $parentComponentPath –path to the parent component from the website root (empty if there is no parent).

    $parentComponentTemplate – template of the parent component (empty if there is no parent).

    $arResult — result, read/change. Affects the member of the component class with the same name.

    $this — naturally a link to the current component retrieved (CBitrixComponent class object), all class methods can be used.

    In addition, the variables $APPLICATION, $USER, $DB are named as global in the component.

    Component Structure

    A component stores everything it needs for operation in its folder. That is why they can be easily moved from one project to another.

    Important: Component files cannot be used independently. A component is a single entity, and it is indivisible.

    The component folder may contain the following subfolders and files:

    • The help subfolder (outdated from version 11.0) containing component help files. This folder contains subfolders named by the language abbreviation.
      Each language folder must have a file index.php, which is the main help file for the corresponding language.
      For example, the path name of the english help file can be /help/en/index.php.
      The help files should describe the structure of an array in which the component returns data to the template.
      The subfolder help is optional.
    • subfolder install that contains installation and uninstallation scripts. The installation script name is install.php, and the uninstallation script is uninstall.php. The subfolder install is optional.
    • subfolder lang containing component messages. From version 11.0 and later it may also contain help folder files.
    • subfolder templates in which the component view templates are stored. The subfolder templates is optional if the component has no view templates.
    • The file component.php containing logics (code) of the component. This file is responsible for generating an array $arResult from the obtained parameters ($arParams) that will subsequently get to the component template. This file must always be located in the component folder.
    • The file .description.php containing the component name, description, and its position in the deduction tree (for WYSIWYG editor). This file must always be located in the component folder. Its absence will not affect the work of the component, but will make it impossible to place the component through a visual editor.
    • file .parameters.php, which contains the description of the component input parameters for the visual editor. This file must exist if the component uses input parameters.
    • The file .class.php to support OOP components.
    • any other folders and files containing the resources required by the component, for example: the folder images.

    General Structure of the Component

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?> 
    <? 
     //Check and initialize the incoming parameters of a component
     if(component retrieval is in a valid cache) 
     { 
     //Retrieval of the data from the cache
     } 
     else 
     { 
     //Data request and generation of $arResult array according to
     //the structure described in the component help file
     $this->IncludeComponentTemplate(); 
     //Cached retrieval
     } 
    ?>

    Localization Folders

    Components and their templates support the option of displaying a user’s messages in multiple languages. Therefore, if a component displays the contents of an infoblock, for example, these contents may be preceded by a string constant. E.g.: “Here you can see the contents of the infoblock.” If the user chooses to go to, for example, the English version of the website, this constant may be automatically displayed in English.

    In order to implement this function when displaying string constants, a special function shall be retrieved instead of the constant itself and the identifier of this constant shall be allocated to the special function.

    Example:

    In the file /template.php without localization:

    <?echo “Commercial catalog”?>

    In the file /template.php with localization:

    <?echo GetMessage(“CATALOG”)?>

    In this case we introduce the following in the file /lang/en/template.php:

    <?$MESS[“CATALOG”] = “Commercial catalog”;?>

    For each language version, a folder with the name of the language shall be created in the lang folder (e.g., de, en, etc.) to host the language message files. A language message file for a component file should have the same name as the file itself. These files contain arrays with constant identifiers as keys and the constants themselves translated into a relevant language as values.

    The files should be located according to the same hierarchy with respect to the /lang/anguage_code/ folder where the file is located with respect to a component folder. For example, a language file with English phrases for the /install/uninstall.php file should be located on the path /lang/en/install/uninstall.php. There may be no lang subfolder at all if the component has no language-dependent phrases.

    Localization folders are created separately for components and for each template.

    How to Replace Standard Texts in the Interface.

    Copy the component template to be customized (if it is not customized yet). And then either:

    • Using the module Localization: Settings > Localization.
    • Or manually edit language files /bitrix/templates/site_template_name/components/component_name/templates/template_name/lang/en/template­.php.

    Connection of a Component Language File (Translation File)

    Language files in a component and all its standard files (component.php, .description.php, .parameters.php) are connected automatically. In other component files language files can be connected using the following command:

    $this->IncludeComponentLang($relativePath = "", $lang = False)

    where:

    $relativePath — is a path to the file from the component folder,

    $lang — is the language. If False is submitted, the current language is used.

    Example: $this->IncludeComponentLang("myfile.php");

    Prompts to Components

    Bitrix Framework permits creating prompts to component parameters:

    There are two ways to add prompts. The first one you can see in the projects created on the versions earlier than 11.0, and the second one – in subsequent versions. In both cases a file named .tooltips.php must be created.

    Before version 11.0

    In this case, the file shall be created in the help folder located in the root of the component. In the file, the array $arTooltips shall be created where the component parameters are the keys, and GetMessage() calls are values. For example:

    $arTooltips = array(
       'IBLOCK_TYPE' => GetMessage('VWS_COMP_DVL_TIP_IBLOCK_TYPE'),
       'IBLOCK_ID' => GetMessage('VWS_COMP_DVL_TIP_IBLOCK_ID'),
       'SORT_BY1' => GetMessage('VWS_COMP_DVL_TIP_SORT_BY1'),
       'SORT_ORDER1' => GetMessage('VWS_COMP_DVL_TIP_SORT_ORDER1'),
    );

    Language files for the file .tooltips.php are located, accordingly, in the folder (from the root folder of the component) /lang/language_ID/help/.tooltips.php.

    From version 11.0 on

    This time, the file shall be created in the language folder named lang. In the file, the array $MESS shall be created, where the component parameters with _TIP suffix are the keys, and the prompts are values. For example:

    $MESS["IBLOCK_TYPE_TIP"] = "Select one of the existing information block types in the list";
    $MESS["IBLOCK_ID_TIP"] = "Select an information block of the selected type";
    $MESS["SORT_BY1_TIP"] = "Defines the sorting field";
    $MESS["SORT_ORDER1_TIP"] = "Defines the sorting order";
    

    In this version, there is no need to create a separate lang file with prompts. It is sufficient to save them in the language file .parameters.php (both for the component and for the component template). The prompt format remains the same.

    In both case

    The prompts for parameters of the component IBLOCK_TYPE, IBLOCK_ID, SORT_BY1 and SORT_ORDER1.

    A number of standard parameters (CACHE_TIME, AJAX_MODE, and others) have several prompts. For CACHE_TIME:

    • CACHE_TIME — caching time;
    • CACHE_TYPE — caching type.

    For pager:

    • DISPLAY_TOP_PAGER — show pager above the list;
    • DISPLAY_BOTTOM_PAGER — show pager below the list;
    • PAGER_TITLE — pager element title;
    • PAGER_SHOW_ALWAYS — always show pager;
    • PAGER_TEMPLATE — pager template name;
    • PAGER_DESC_NUMBERING — reverse addressing;
    • PAGER_DESC_NUMBERING_CACHE_TIME — caching time of reverse addressing.

    Attention! Do not create prompts in advance (creating them but leaving empty): component settings will not be displayed.

    Composite Component Structure

    Composite component serves to organize a website section as a whole (forum, catalog, etc.). It connects simple components to the display data. It actually operates as a controller (manager) of simple components. Composite component determines a page to be displayed to the visitor based on HTTP and connects the template of such a page.

    An example of a composite component:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?><?
    
    $arDefaultUrlTemplates404 = array(
       "list" => "index.php",
       "element" => "#ELEMENT_ID#.php"
    );
    
    $arDefaultVariableAliases404 = array();
    
    $arDefaultVariableAliases = array();
    
    $arComponentVariables = array("IBLOCK_ID", "ELEMENT_ID");
    
    
    $SEF_FOLDER = "";
    $arUrlTemplates = array();
    
    if ($arParams["SEF_MODE"] == "Y")
    {
       $arVariables = array();
    
        $arUrlTemplates = 
            CComponentEngine::MakeComponentUrlTemplates($arDefaultUrlTemplates404, 
                                                        $arParams["SEF_URL_TEMPLATES"]);
        $arVariableAliases = 
            CComponentEngine::MakeComponentVariableAliases($arDefaultVariableAliases404, 
                                                           $arParams["VARIABLE_ALIASES"]);
    
       $componentPage = CComponentEngine::ParseComponentPath(
          $arParams["SEF_FOLDER"],
          $arUrlTemplates,
          $arVariables
       );
    
       if (StrLen($componentPage) <= 0)
          $componentPage = "list";
    
       CComponentEngine::InitComponentVariables($componentPage, 
                                                $arComponentVariables, 
                                                $arVariableAliases, 
                                                $arVariables);
    
       $SEF_FOLDER = $arParams["SEF_FOLDER"];
    }
    else
    {
       $arVariables = array();
    
        $arVariableAliases = 
           CComponentEngine::MakeComponentVariableAliases($arDefaultVariableAliases, 
                                                          $arParams["VARIABLE_ALIASES"]);
        CComponentEngine::InitComponentVariables(false, 
                                                 $arComponentVariables, 
                                                 $arVariableAliases, $arVariables);
    
       $componentPage = "";
       if (IntVal($arVariables["ELEMENT_ID"]) > 0)
          $componentPage = "element";
       else
          $componentPage = "list";
    
    }
    
    $arResult = array(
       "FOLDER" => $SEF_FOLDER,
       "URL_TEMPLATES" => $arUrlTemplates,
       "VARIABLES" => $arVariables,
       "ALIASES" => $arVariableAliases
    );
    
    $this->IncludeComponentTemplate($componentPage);
    ?>

    The following arrays are defined in the beginning of the code:

    • $arDefaultUrlTemplates404 — is used to set up paths by default in order to work in a Search Engine Friendly URL (SEF) mode. Each element of the array is a path template and is to be set up as follows:
      "path template code" => "path template"

      Structure of the type «#word#» may be used in the path template. These structures are replaced with the values of the relevant variables when the actual path is being generated. For example, for a path template:

      "element" => "#ELEMENT_ID#.php"

      The actual path will look like 195.php or 7453.php. Path templates may have parameters, for example:

      "element" => "#IBLOCK_ID#/#ELEMENT_ID#.php?SECTION_ID=#SECTION_ID#"

      All path templates used by the component must be set up.

    • $arDefaultVariableAliases404 — is used to set up default aliases for variables in a SEF mode. As a rule, this array is empty (real names of variables are used). In case a variable must have a different name in an HTTP request (address), an alias can be set up for this variable and the variable value can be recovered from the alias during component operation. In case an alias must be set for one or more variables of any path templates, an element similar to that indicated below must appear in the array:
      "path template code" => array(
          "variable 1 name" => "variable 1 alias",
          "variable 2 name" => "variable 2 alias",
          * * *
          )

      For example, if a link to the page containing detailed information about an infoblock element (e.g., a product card) must be similar to the following:

      "/<infoblock mnemonic code>/.php?SID=<element group code>"

      then the path template can be set up as follows:

      "element" => "#IBLOCK_ID#/#ELEMENT_ID#.php?SID=#SECTION_ID#"

      and an alias for the variable SECTION_ID in the array $arDefaultVariableAliases404 can be set up as:

      "element" => array(
          "SECTION_ID" => "SID"
          )

      In this case, links (addresses) will be generated using the SID parameter and the variable SECTION_ID will be set up in the components.

    • $arDefaultVariableAliases — is used to set up default aliases for variables not in a CNC mode. As a rule, this array is empty, i.e. real names of variables are used. In case a variable must have a different name in an HTTP request (address), an alias can be set up for this variable and the variable value can be recovered from the alias during component operation. In case an alias must be set for any variable, an element similar to that indicated below must appear in the array:
      "variable name" => "variable alia"

      For example, if the variable name is defined in the component SECTION_ID, but the SID variable is to be used in the links, an alias for SECTION_ID can be set up as follows:

      "SECTION_ID" => "SID"

      In this case, links (addresses) will be generated using the SID parameter, and the components will contain the SECTION_ID variable. All of these arrays or their parts can be redefined using the input parameters of the component (upon component retrieval). For example, the following array can be set up in the input parameter SEF_URL_TEMPLATES in a SEF mode:

      "SEF_URL_TEMPLATES" => array(
          "element" => "#IBLOCK_CODE#/#ELEMENT_ID#.php?GID=#SECTION_ID#"
          )

      and the following parameter can be set up in the input parameter VARIABLE_ALIASES :

      "VARIABLE_ALIASES" => array(
          "element" => array(
          "SECTION_ID" => "GID",
          ),
      )

      Then, the paths in addresses (links) will have the type similar to /phone/3425.php?GID=28and the following variables will be recovered in the component out of these path: IBLOCK_CODE = phone, ELEMENT_ID = 3425 and SECTION_ID = 28.

    • $arComponentVariables — is used to set up a list of variables wherein the component can accept in an HTTP request and admit aliases. Each element of an array is the name of a variable.

    An input parameter with the preset name SEF_MODE can have two values: Y and N. If $arParams[«SEF_MODE»] is equal to Y, the component works in a SEF mode; otherwise, it does not.

    An input parameter with the preset name SEF_FOLDER makes sense if the component works in a SEF mode. In this case, it contains the path along which the component works. The path may be virtual (i.e. it might not exist physically). For example, the component from the example can be located in the file /fld/n.php, at the same time, it works in a SEF mode, and the input parameter SEF_FOLDER is equal to /company/news/. In this case, the component will respond to queries at the addresses /company/news/index.php, /company/news/25.php etc.

    The following methods are used in order to determine the page to be shown by the composite component and also to recover component variables from the path and aliases:

    • CComponentEngine::MakeComponentUrlTemplates
      array
      CComponentEngine::MakeComponentUrlTemplates($arDefaultUrlTemplates404, 
                                                  $arParams["SEF_URL_TEMPLATES"]);

      The method combines into a single array the default array of path templates and path templates submitted in input parameters of a component. If a template of any path is defined in $arParams[«SEF_URL_TEMPLATES»], it will redefine the default template for such path.

    • CComponentEngine::MakeComponentVariableAliases
      array
      CComponentEngine::MakeComponentVariableAliases($arDefaultVariableAliases404, 
                                                     $arParams["VARIABLE_ALIASES"]);

      The method combines into a single array the default array of variable aliases and the variable aliases submitted in the input parameters of a component. If an alias of a variable is also defined both in the default array and in input parameters, the alias from the input parameters is returned.

    • CComponentEngine::ParseComponentPath
      string
      CComponentEngine::ParseComponentPath($arParams["SEF_FOLDER"],
                                           $arUrlTemplates, $arVariables);

      The method based on the parameter $arParams[«SEF_FOLDER»] and path template array (returned by the method MakeComponentUrlTemplates) determines the path template to which the requested address corresponds. If the template was found, its code is returned. Otherwise, an empty line is returned. In addition, an array of component variables recovered from the path template without parameters is returned in the $arVariables variable. For example, if a path template array (resulted from the array $arDefaultUrlTemplates404 after the redefinition of all or a part of templates through input parameters of the component) looks like:

      $arUrlTemplates = array(
          "list" => "index.php",
          "element" => "#IBLOCK_ID#/#ELEMENT_ID#.php?SID=#SECTION_ID#"
      );

      If the input parameter SEF_FOLDER is equal to /company/news/, and the requested address is equal to /company/news/15/7653.php?SID=28, the method ParseComponentPath will return the line «element» (code of the relevant template), and the array $arVariables will look as follows:

      $arVariables = array(
          "IBLOCK_ID" => 15,
          "ELEMENT_ID" => 7653
      )
    • CComponentEngine::InitComponentVariables
      CComponentEngine::InitComponentVariables($componentPage, 
                                               $arComponentVariables, 
                                               $arVariableAliases, 
                                               $arVariables);

      where:

      • $componentPage — is the code of the template that returned the ParseComponentPath method and to which the requested address corresponds;
      • $arComponentVariables — is an array of variables that a component may admit in a HTTP request and that may have aliases;
      • $arVariableAliases — is an alias array (returned by the method MakeComponentVariableAliases).

      The method recovers variables from $_REQUEST taking into account their possible aliases and returns them in the variable $arVariables. For example, if for the code above the array $arVariableAliases contains an entry

      "element" => array(
          "SECTION_ID" => "SID",
      )

      the method InitComponentVariables will return an array in the $arVariables parameter

      $arVariables = array(
          "IBLOCK_ID" => 15,
          "ELEMENT_ID" => 7653,
          "SECTION_ID" => 28
      )

      Here, the method InitComponentVariables initialized the third element of the array. The first two were initialized by the method ParseComponentPath in the example above. In case the component works in a non-SEF mode, the first parameter submits the value False to the method InitComponentVariables.

    If the component works in a SEF mode based on the path template code and variables from HTTP request (and in case of non-SEF addresses only based on variables from HTTP request) the component determines which page from the template is to be connected and forwards its name to the method invocation:

    $this->IncludeComponentTemplate($componentPage);

    Simple components are connected and their input parameters are set up on pages of a composite component template based on the input parameters of the composite component and certain calculated values and constants. For example, the page «element» of the component template from the example (file of the type /templates/.default/list.php with respect of the component folder) may look as follows:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
    <?$APPLICATION->IncludeComponent(
       "bitrix:news.detail",
       "",
       Array(
          "IBLOCK_ID" => $arParams["IBLOCK_ID"],
          "ELEMENT_ID" => $arResult["VARIABLES"]["ELEMENT_ID"],
          "SECTION_ID" => $arResult["VARIABLES"]["SECTION_ID"],
          "CACHE_TIME" => $arParams["CACHE_TIME"],
       ),
       $component
    );?>

    The last parameter $component in the component connection is an object representing the current component. It is forwarded to the component connection call. Thus, the component to be connected will “know” that it is connected from the composite component. Accordingly, it will be able to use the resources of the composite component, invoke its methods, etc.

    Component Description

    The file .description.php contains a description of the component. This description is used for work with the component (for example, in a visual editor) and also for work in the website editing mode. During the work of the component itself (when invoking the page where the component is located), the description is not used, and the file .description.php is not connected.

    The file .description.php must be located in the component folder. The language file connects automatically (it must be located in the folder /lang/<language>/.description.php of the component folder).

    Typically, the file .description.php has the following structure:

    <?
    $arComponentDescription = array(
       "NAME" => GetMessage("COMP_NAME"),
       "DESCRIPTION" => GetMessage("COMP_DESCR"),
       "ICON" => "/images/icon.gif",
       "PATH" => array(
          "ID" => "content",
          "CHILD" => array(
             "ID" => "catalog",
             "NAME" => "Catalog"
          )
       ),
       "AREA_BUTTONS" => array(
          array(
             'URL' => "javascript:alert('Button');",
             'SRC' => '/images/button.jpg',
             'TITLE' => "Button"
          ),
       ),
       "CACHE_PATH" => "Y",
       "COMPLEX" => "Y"
    );
    ?>

    As we can see, the file determines the array $arComponentDescription which describes the component. This array may have the following keys:

    • NAME — name of the component;
    • DESCRIPTION — description of the component;
    • ICON — path to the pictogram of the component from the component folder. The icon of the component is used in different parts of the system (e.g., in the visual editor);
    • PATH — component location in the virtual tree of the component in the visual editor. The value of this element must be an array with the following keys:
      • ID — code of the tree branch. Node ID must be unique within the entire component tree (including standard nodes). If the nodes have two equal ID, both of them will not open. E.g., for the proprietary component the ID node = “news” is selected, and such ID already exists for standard components.
      • NAME — name of the tree branch. It must be indicated. The NAME is taken from any component from the node. If no NAME is found or there is no language constant necessary, ID is used as a NAME.
      • CHILD — a child or subordinate branch. In the element with the CHILD key a subordinate branch of the tree with the same structure as the parent branch can be set up.

      The tree is limited to three tiers. As a rule, a two-tier tree is built, and the components are located on the second tier. The following service names of the first tier are reserved and cannot be used: content, service, communication, e-store and utility.

      If the PATH key is not established, the component will not be available in the visual editor.

    • AREA_BUTTONS — user buttons shown for the component in the website editor mode;
    • CACHE_PATH — if the value is equal to Y, the cache flush button of the component is shown in the website editor mode (the cache is supposed to be located at the standard path: /<website code>/<relative path to the component>). If equal to a non-empty line different from Y, the cache flush button of the component is shown in the website editor mode (the cache is located at the path equal to the value with the key CACHE_PATH — for non-standard paths);
    • COMPLEX — the element must have the value Y for a composite component and has no significance for simple components.

    Component Parameters

    The file .parameters.php contains the description of the input parameters of the component. The file data are needed exclusively to create the form to enter the properties of the component in Bitrix Framework environment (e.g., in a visual editor). This description is used for work with the component and also during work in the website editing mode. The description is not used and the said file is not connected during the operation of the component itself (when the page with the component is requested).

    The fil .parameters.php must be located in the component folder. The language file is connected automatically (it must be located in the folder /lang/<language>/.parameters.php, of the component folder).

    The file determines the array $arComponentParameters, which describes the input parameters of the component. If necessary, any additional data are selected. E.g., all active types must be selected in order to establish a drop-down list of information block types (input parameter IBLOCK_TYPE_ID).

    The structure of the typical file .parameters.php (based on the example of the components that operate with the module Information Block):

    <?
    CModule::IncludeModule("iblock");
    
    $dbIBlockType = CIBlockType::GetList(
       array("sort" => "asc"),
       array("ACTIVE" => "Y")
    );
    while ($arIBlockType = $dbIBlockType->Fetch())
    {
       if ($arIBlockTypeLang = CIBlockType::GetByIDLang($arIBlockType["ID"], LANGUAGE_ID))
          $arIblockType[$arIBlockType["ID"]] = "[".$arIBlockType["ID"]."] ".$arIBlockTypeLang["NAME"];
    }
    
    $arComponentParameters = array(
       "GROUPS" => array(
          "SETTINGS" => array(
             "NAME" => GetMessage("SETTINGS_PHR")
          ),
          "PARAMS" => array(
             "NAME" => GetMessage("PARAMS_PHR")
          ),
       ),
       "PARAMETERS" => array(
          "IBLOCK_TYPE_ID" => array(
             "PARENT" => "SETTINGS",
             "NAME" => GetMessage("INFOBLOCK_TYPE_PHR"),
             "TYPE" => "LIST",
             "ADDITIONAL_VALUES" => "Y",
             "VALUES" => $arIblockType,
             "REFRESH" => "Y"
          ),
          "BASKET_PAGE_TEMPLATE" => array(
             "PARENT" => "PARAMS",
             "NAME" => GetMessage("BASKET_LINK_PHR"),
             "TYPE" => "STRING",
             "MULTIPLE" => "N",
             "DEFAULT" => "/personal/basket.php",
             "COLS" => 25
          ),
          "SET_TITLE" => array(),
          "CACHE_TIME" => array(),
          "VARIABLE_ALIASES" => array(
             "IBLOCK_ID" => array(
                "NAME" => GetMessage("CATALOG_ID_VARIABLE_PHR"),
             ),
             "SECTION_ID" => array(
                "NAME" => GetMessage("SECTION_ID_VARIABLE_PHR"),
             ),
          ),
          "SEF_MODE" => array(
             "list" => array(
                "NAME" => GetMessage("CATALOG_LIST_PATH_TEMPLATE_PHR"),
                "DEFAULT" => "index.php",
                "VARIABLES" => array()
             ),
             "section1" => array(
                "NAME" => GetMessage("SECTION_LIST_PATH_TEMPLATE_PHR"),
                "DEFAULT" => "#IBLOCK_ID#",
                "VARIABLES" => array("IBLOCK_ID")
             ),
             "section2" => array(
                "NAME" => GetMessage("SUB_SECTION_LIST_PATH_TEMPLATE_PHR"),
                "DEFAULT" => "#IBLOCK_ID#/#SECTION_ID#",
                "VARIABLES" => array("IBLOCK_ID", "SECTION_ID")
             ),
          ),
       )
    );
    ?>

    Let us have a closer look at the keys of the array $arComponentParameters.

    GROUPS

    The value of this key is an array of component parameter groups. In the visual means of the Bitrix Framework (e.g., in a visual editor), the parameters are grouped. The groups in the Bitrix Framework environment are located according to the order set in the file. The array of the component parameter groups consists of the following types of elements:

    "group code" => array(
        "NAME" => "name of the group in the current language"
        "SORT" => "sorting",
        )

    List of standard groups:

    • ADDITIONAL_SETTINGS (sorting — 700). This group appears, for example, when indicating the parameter SET_TITLE.
    • CACHE_SETTINGS (sorting — 600). It appears when indicating the parameter CACHE_TIME.
    • SEF_MODE (sorting 500). The group for all parameters connected with the use of SEF.
    • URL_TEMPLATES (sorting 400). Link templates
    • VISUAL (sorting 300). This group is rarely used. These are parameters responsible for physical appearance.
    • DATA_SOURCE (sorting 200). Infoblock type and ID.
    • BASE (sorting 100). Main parameters.
    • AJAX_SETTINGS (sorting 550). Everything associated with AJAX.

    PARAMETERS

    The value of this key is an array of component parameters. In each parameter group, the parameters are located in the order set in the file. The array of regular parameters of the component consists of the following types of elements:

    "parameter code" => array(
        "PARENT" => "group code",  // if not - ADDITIONAL_SETTINGS shall be established
        "NAME" => "parameter name in the current language",
        "TYPE" => "type of the control element in which the parameter will be established",
        "REFRESH" => "refresh settings or not after selection (N/Y)",
        "MULTIPLE" => "single/multiple value (N/Y)",
        "VALUES" => "array of values for the list (TYPE = LIST)",
        "ADDITIONAL_VALUES" => "show the field for the values to be entered manually (Y/N)",
        "SIZE" => "number of lines for the list (if a list which is not a drop-down list is needed)",
        "DEFAULT" => "default value",
        "COLS" => "field width in characters",
    ),

    The following values are available for the TYPE control element type:

    • LIST — selection from the list of values. For the type LIST the key VALUES contains an array of values of the following type:
      VALUES => array(
         "ID or code to be saved in the component settings" => "language dependent description",
      ),
    • STRING — text entry field.
    • CHECKBOX — yes/no.
    • CUSTOM — permits creating customized control elements.

    The physical appearance of the list changes depending on whether the MULTIPLE and ADDITIONAL_VALUES keys are available/absent:

    • If there are no MULTIPLE and ADDITIONAL_VALUES or they are equal to “N”, a simple list is displayed, and no values are added to the list:

    • If ADDITIONAL_VALUES = «Y», MULTIPLE = «N», the value Other is added to the list with an additional field allowing manual entry of a value:

    • If ADDITIONAL_VALUES = «N», MULTIPLE = «Y», nothing will be added to the list, but it becomes possible to select several elements:

    • If ADDITIONAL_VALUES = «Y», MULTIPLE = «Y», the value Not selected is added to the list with a multiple additional field allowing manual entry of a value.

    The REFRESH parameter permits to refresh the entire form with parameters following value selection. This is done, for example, to select an infoblock of a specific type. I.e., we have two parameters: infoblock type and infoblock code. The starting point is as follows: the first parameter contains all types of infoblocks, the second parameter contains a list of all infoblocks of this website; after the selection of a certain type of infoblock, component parameters are refreshed, and we can only see the infoblocks of the selected type.

    Externally, for the LIST — type parameters, this key is represented as the OK button located close to the relevant parameter (see screenshots above).

    If a certain parameter must appear or remain hidden depending on another parameter, the procedure is as follows. For example, we need to display the list of properties of an infoblock. Assuming that the infoblock ID is contained in the IBLOCK_ID component parameter, let us name the parameter containing property list PROP_LIST. The parameter IBLOCK_ID must have the key REFRESH = ‘Y’ established. The code:

    if (0 < intval($arCurrentValues['IBLOCK_ID'])
    {
       $arPropList = array();
       $rsProps = CIBlockProperty::GetList(array(),array('IBLOCK_ID' => $arCurrentValues['IBLOCK_ID']));
       while ($arProp = $rsProps->Fetch())
       {
          $arPropList[$arProp['ID']] = $arProp['NAME'];
       }
       $arComponentParameters['PARAMETERS']['PROP_LIST'] => array(
          'NAME' => 'parameter title',
          'TYPE' => 'LIST'
          'VALUES' => $arPropList,
       );
    }

    There are special parameters that are standardized so that there is no need to describe them in detail. It is sufficient just to indicate that they exist. For example,

    "SET_TITLE" => array(),
    "CACHE_TIME" => array(),

    The first of the indicated parameters shows whether the component should set a page header and the second one shows all cache-related settings.

    Only composite components can work in a SEF mode or redefine the variables coming from an HTTP request. In this case, two special parameters must be indicated among the parameters:

    • VARIABLE_ALIASES — an array describing variables that the component may receive from an HTTP request. Each element of the array looks like the following:
      "internal name of the variable" => array(
        "NAME" => "name of the variable in the current language",
      )
    • SEF_MODE — an array describing path templates in a SEF mode. Each element of the array looks like the following:
      "path template code" => array(
          "NAME" => "name of the path template in the current language",
          "DEFAULT" => "default path template",
          "VARIABLES" => "an array of internal names of variables that may be used in the template"
      )

    From product version 12 on (new core D7) there is an option to add a control element into the component parameters that permits to indicate color (COLORPICKER).

    To do so, the following must be indicated in the component parameter file .parameters.php:

    $arParams["COLOR"] = Array(
        "PARENT" => "BASE",
        "NAME" => 'Colour selection',
        "TYPE" => "COLORPICKER",
        "DEFAULT" => 'FFFF00'
    );
    

    Component Templates

    Component template is a program code that converts data prepared by a component directly to HTML code.

  • Variables Available in the Component Template
  • Simple Component Template
  • Composite Component Template
  • How the System Searches for the Template
  • Template Connection
  • Template Example
  • Component templates are subdivided into system and user:

    • System templates come together with the component and are located in the subfolder templates of the component folder.
    • User templates of the component are the templates that have been changed according to the requirements of a specific website. They must be located in the folders of website templates (i.e. in /bitrix/templates/website_template/). When the component template is copied using the system it means that they will be located at the following path: /bitrix/templates/website_template/components/namespace/component_name/template_name.

    Component templates are defined by names. Default template is named .default. If no template name is indicated in the component parameter settings, the default template is retrieved. Other templates can have arbitrary names.

    Component templates can be folders or files. If the template does not require translation into other languages, own styles, and other resources, such template may be located in a file. Otherwise, the template should be located in a folder.

    Each component template is indivisible. If the system template of a component must be changed to fit for a specific website, such a component template must be copied to the website template folder in its integrity.

    For example, the component bitrix:catalog.site.selector has the system template dropdown located in the subfolder templates of the component folder. If this component template is to be changed to fit a specific website, the dropdown template folder should be copied into the folder /bitrix/templates/website_template/components/bitrix/catalog.site.selector/ and changed as one deems necessary.

    When including a component into a web page, the administrator shall establish which display template, exactly, will be used in this specific case.

    Variables Available in the Component Template

    The following variables are used in the template:

    • $templateFile – a path to the template from the website root, e.g.: /bitrix/components/bitrix/iblock.list/templates/.default/template.php).
    • $arResult – an array of component operation results.
    • $arParams – an array of input parameters of a component; it may be used to keep track of the set parameters of the template (e.g., displaying full pictures or links).
    • $arLangMessages – an array of language messages of the template (not required for php templates).
    • $templateFolder — a path to the folder with the template from DOCUMENT_ROOT (e.g., /bitrix/components/bitrix/iblock.list/templates/.default).
    • $parentTemplateFolder – a parent template folder. This variable is very convenient to connect additional images or scripts (resources). It should be introduced to form a full path from the template folder.
    • $component — a link to the currently invoked component (CBitrixComponent type).
    • $this — a link to the current template (object describing the template, CBitrixComponentTemplate type)
    • $templateName — the name of the component template (e.g., .default)
    • $componentPath — a path to the component from DOCUMENT_ROOT (e.g., /bitrix/components/bitrix/iblock.list)
    • $templateData — an array for entry; please note that here the data from template.php can be transferred to the file component_epilog.php, and these data will be sent to cache because the file component_epilog.php is executed with each hit.

    Also, within the PHP template, the variables $APPLICATION, $USER and $DB are declared global.

    Simple Component Template

    The file of a simple component template may contain the following subfolders and files:

    • Subfolder /lang where the files of language messages (translations) of the component template are located;
    • The file result_modifier.php that is to be connected directly before connecting the component template. This file receives as input the array of component operation results $arResult and the array of component invocation parameters $arParams. This way, it is possible, for example, to change the array of the component operation results to fit a specific template.
    • The file style.css determines the styles required for this template.
    • The file script.js determines and connects java scripts required for this template. This file may be absent.
    • The file .description.php containing the name and description of the template for the visual editor.
    • The file .parameters.php containing a description of additional input parameters of the template for the visual editor.
    • The file template.ext is the template proper. The extension ext depends on the type of the templating motor to be connected. The default extension is php. This file is mandatory.
    • Any other folders and files containing the resources required for the component template. For example, the image folder containing the images required by the template.

    Composite Component Template

    A composite component template contains the same folders as a simple component template, plus:

    • Templates of simple components that form part of the composite template. These templates are located in the folders of the type /namespace/simple_component_name/ in the composite component template folder.
    • Simple components that form part of the composite template are connected in the templates of pages of the composite component.

    How the System Searches for the Template

    The following search algorithm is used to find a suitable template for a component:

    • Everything begins with the folder /bitrix/templates/current_site_template/components/. In this folder, the file or folder with the template name is searched for along the path /component_namespace/component_name/. If there are none, then the availability of the file template_name.ext is searched for, where the ext extension includes all the available extensions of all templating motors established on the website that are checked one by one. If the template is found, the algorithm is ended.
    • If no template is found on step 1, the search is performed in the folder /bitrix/templates/.default/components/. The algorithm described in step 1 applies. If the template is found, the algorithm is ended.
    • If no template is found in step 2, the search among the system templates (i.e. those supplied with the component) is performed.

    Specifics of the search:

    • If no template name is set, the template with the .default name is searched for.
    • If the template is set as a folder name, then in case of a simple component in this folder the file template.extis searched for, and in case of a composite template — page_name.ext. The ext extension is assumed equal to php first, and then to extensions of other templating motors available on the website.

    For example, the component bitrix:catalog.list must be shown using the template table. Let us assume that on the website, in addition to the standard templating motor (php files), the Smarty motor (tpl files) is also available. The system will first check the folder /bitrix/templates/current_site_template/components/bitrix/catalog.list/ to find a file or a folder named table. If there are none, the system will check the aforementioned folder to find the files table.php and table.tpl. If nothing is found, the system will review the folders /bitrix/templates/.default/components/bitrix/catalog.list/ and /bitrix/components/bitrix/catalog.list/templates/.

    f the component folder is found, the file template.php is searched for first and if no such file is found, the file template.tpl. is searched for. If the template is set as table/template.php, the indicated file is retrieved at once.

    If a simple component is retrieved as a part of a composite component, the simple component template is first searched for within the composite component template, and after that (if not found) in own templates. To make sure this rule works, when calling for simple components within composite components do not forget to indicate the variable $component as the fourth parameter indicating the parent component. I.e. the code for invoking a simple component must look as follows:

    $APPLICATION->IncludeComponent("custom:catalog.element", "", array(...), $component);

    Note:

    The same folder (e.g., /bitrix/templates/current_site_template/components/) contains templates of two components, a composite and a simple:

    • catalog (composite component that contains the simple catalog.section)
    • catalog.section (simple)

    According to website operation conditions, a single template must be used for two catalog.section occurrences. In this case, this template must have a name other than .default; otherwise, it will not be caught.

    Template Connection

    Only <namespace>, component name, and template name (and parameters of the component itself) must be indicated in the connection code. When processing the code, the core first checks the availability of the component template in the current website template: /bitrix/templates/<site template name>/components/<namespace>/<component name>/<template name>/template.php.

    If <namespace> is bitrix it is the folder for templates of standard components. If <namespace> is selected by you <name> for your components is located in /bitrix/components/<name>, it is the folder for the templates of your components.

    If no template file is available, the default website template is checked: /bitrix/templates/.default/components/<namespace>/<component name>/<template name>/template.php.

    Only after that will the component template be connected from the component folder.

    The template is connected by the command:

    $this->IncludeComponentTemplate($templatePage = "");

    Where $templatePage is the name of the current page for a composite component, and an empty line for a simple component.

    Component template connection examples:

    1. Let us connect the template of the current composite component for the Section page:

      $this->IncludeComponentTemplate("section");

    2. Connect the template of the current simple component:

      $this->IncludeComponentTemplate();

    Template Example

    Menu Component Template
    Start test <?if (!defined(«B_PROLOG_INCLUDED»)
    || B_PROLOG_INCLUDED!==true)die();?>
    Script start <?if (!empty($arResult)):?>
    <ul> tag opening — unnumbered list <ul class=»…»>
    Search cycle start <?foreach ($arResult as $arItem):?>
    Link display <?if($arItem[«SELECTED»]):?>
    Active link <li><a href=»<?=$arItem[«LINK»]?>» class=
    «selected»><?=$arItem[«TEXT»]?></a></li>
    Check for cycle continuation <?else:?>
    Inactive link <li><a href=»<?=$arItem[«LINK»]?>»>
    <?=$arItem[«TEXT»]?></a></li>
    Link display ends <?endif?>
    Search cycle ends <?endforeach?>
    <ul> tag closes — unnumbered list </ul>
    Script ends <?endif?>

    Support of Component Classes

    From version 12.0.0 and later, component class support is available. It is implemented in the form of a file /component_name/class.php. Class.php is a reserved file name, and this file is automatically connected upon retrieval:

    $APPLICATION->IncludeComponent()

    In this case, the final call of the method initComponent is made, where class.php (if any) is connected from which the most recent inheritor from CBitrixComponent is taken.

    The actions like:

    class CDemoTest extends CBitrixComponent{}
    class CDemoTestDecorator1 extends CDemoTest {}
    class CDemoTestDecorator2 extends CDemoTest {}

    will not be successful. As a result, CDemoTestDecorator2 will be used.

    Please note that when changing the base class of the component, the behavior of all its descendants (other components) will have to be taken into account.

    Examples of Use

    Let us consider the simplest component taking the square of a parameter.

    File /bitrix/components/demo/sqr/component.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    $arParams["X"] = intval($arParams["X"]);
    if($this->startResultCache())
    {
        $arResult["Y"] = $arParams["X"] * $arParams["X"];
    }
    $this->includeComponentTemplate();
    ?>

    File /bitrix/components/demo/sqr/templates/.default/template.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
    <div class="equation">
    <?echo $arParams["X"];?> squared is equal to <?echo $arResult["Y"];?>
    </div>

    In the real components, there may be three dozen lines and 5-6 similar operations instead of the multiplication operation. As a result, the file component.php becomes difficult to understand.

    Let us separate the component logic into a class.

    File /bitrix/components/demo/sqr/class.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    class CDemoSqr extends CBitrixComponent
    {
        //Parent method passes along all of the parameters transferred to $APPLICATION->IncludeComponent
        //and applies the function htmlspecialcharsex to them. In this case, such processing is excessive.
        //Redefine.
        public function onPrepareComponentParams($arParams)
        {
            $result = array(
                "CACHE_TYPE" => $arParams["CACHE_TYPE"],
                "CACHE_TIME" => isset($arParams["CACHE_TIME"]) ?$arParams["CACHE_TIME"]: 36000000,
                "X" => intval($arParams["X"]),
            );
            return $result;
        }
    
        public function sqr($x)
        {
            return $x * $x;
        }
    }?>

    File /bitrix/components/demo/sqr/component.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    if($this->startResultCache())
    {
        //$this - an instance of CDemoSqr
        $arResult["Y"] = $this->sqr($arParams["X"]);
    }
    $this->includeComponentTemplate();
    ?>

    Now the code in the fileе component.php has become controllable.

    Component Inheritance

    For example:

    The file /bitrix/components/demo/double_sqr/class.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    //Necessary for the correct search of the class CDemoSqr
    CBitrixComponent::includeComponentClass("demo:sqr");
    //Inheritor that expands functionality:
    class CDemoDoubleSqr extends CDemoSqr
    {
        public function sqr($x)
        {
            return parent::sqr($x)*2;
        }
    }?>

    The file /bitrix/components/demo/double_sqr/component.php is identical to the file /bitrix/components/demo/sqr/component.php, the contents can be copied.

    The file /bitrix/components/demo/double_sqr/templates/.default/template.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>
    <div class="equation">
    <?echo $arParams["X"];?> squared multiplied by 2 is equal to <?echo $arResult["Y"];?>
    </div>

    A Component without component.php

    A component may also be created without the file component.php

    To do so, it is sufficient to deactivate the method executeComponent. For example:

    class CDemoSqr extends CBitrixComponent
    {
    ...
        public function executeComponent()
        {
            if($this->startResultCache())
            {
                $this->arResult["Y"] = $this->sqr($this->arParams["X"]);
            }
    
            $this->includeComponentTemplate();
    
            return $this->arResult["Y"];
        }
    };

    Now, the files component.php can be deleted from both components.

    result_modifier.php

    The file result_modifier.php is a tool that is used to arbitrarily modify the data of the component operation. The developer shall create this file independently.

    If the file result_modifier.php is located in the template folder, it is retrieved prior to connecting the template.

    Sequence of Work of the Component with the File result_modifier.php:

    In this file, it is possible to request additional data and introduce them into the array of component operation results $arResult. It may prove to be useful if any additional data are to be displayed but component customization is undesirable since it means abandoning component support and updates.

    Note: The file result_modifier.php will run only if the template is NOT cached. And no dynamic properties of the: title, keywords and descriptioin type can be established through it.

    Language phrases of the component template and the following variables are available in the file:

    • $arParams — parameters, read, change. Does not affect the homonymous component member but changes made here affect $arParams in the file template.php.
    • $arResult — result, read/change. Affects the homonymous component class member.
    • $APPLICATION, $USER, $DB — there is no need to declare them global because they are available by default.
    • $this — link to the current template (object describing the template CBitrixComponentTemplate type)

    component_epilog.php

    The file component_epilog.phpis a tool to modify the component operation data when caching is activated. It should be created by the developer independently (available in version 9.0 and later).

    Sequence of Work of the Component with the Files result_modifier.php and component_epilog.php:

    This file is connected after the execution of the template. Similarly to style files, the parent component stores a list of epilogue files of all the templates of child components (possibly embedded) in its cache and (following cache hit) connects these files in the same order as they were executed without caching. Likewise, when invoking child components, the value $component must be submitted to the template.

    $arParams, $arResult are available in the file component_epilog.php, but these values are retrieved from the cache. A set of keys of the $arResult array to be cached is determined in the file component.php as follows:

                    $this->SetResultCacheKeys(array(
                            "ID",
                            "IBLOCK_TYPE_ID",
                            "LIST_PAGE_URL",
                            "NAV_CACHED_DATA",
                            "NAME",
                            "SECTION",
                            "ELEMENTS",
                    ));

    When developing your own components, always use this structure in order to limit the cache size to the data that are really necessary.

    Note: The file component_epilog.php may contain any code. You should only keep in mind that it will be executed with each hit whether cache is available or not. In versions of the main module earlier than 10.0 a copy of the arResult array was submitted to the component template. Due to this, the modification of this array in the file result_modifier.php did not give any results. The following code shall be located in the result_modifier.php to permit making changes in the results of cached data retrieval:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    global $APPLICATION;
    
    $cp = $this->__component; // component object
    
    if (is_object($cp))
    {
    	// we add two fields - MY_TITLE and IS_OBJECT to arResult of the component
    	$cp->arResult['MY_TITLE'] = 'My title';
    	$cp->arResult['IS_OBJECT'] = 'Y';
    	$cp->SetResultCacheKeys(array('MY_TITLE','IS_OBJECT'));
    	// we save them in copy of arResult used by the template
    	$arResult['MY_TITLE'] = $cp->arResult['MY_TITLE'];
    	$arResult['IS_OBJECT'] = $cp->arResult['IS_OBJECT'];
    
    	$APPLICATION->SetTitle($cp->arResult['MY_TITLE']); // will not work on each hit:  
    //will work only the first time, then everything will be retrieved from the cache and there will be no invocation of $APPLICATION->SetTitle()
    //That is why the title is changed in the component_epilog which is executed on each hit.
    
    }
    ?>

    After that, the changes made to arResult and performed in the template will become available in component_epilog.php.

    Example of the File component_epilog.php

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    global $APPLICATION;
    
    if (isset($arResult['MY_TITLE']))
    	$APPLICATION->SetTitle($arResult['MY_TITLE']);
    ?>

    Specifics of Use

    The file component_epilog.php is connected immediately after the connection and execution of the template. Thus, if the component provides for a template connection and then the component code provides for other operations, they will be executed after the execution of the file component_epilog.php.

    Accordingly, in case the changed data coincide in the component_epilog.php and in the component code after the template is connected, only the latest data will be displayed, i.e. those from the component code.

    Example of Such Situation

    Let us use the file component_epilog.php from the example above. The component code (file component.php) contains the following code:

    <?
    $this->IncludeComponentTemplate();
    if($arParams["SET_TITLE"])
    {
    	$APPLICATION->SetTitle($arResult["NAME"]);
    }
    ?>

    In this case, you will not obtain the desired result since the component data will be displayed instead of those from component_epilog.php.

    The file component_epilog.php contains the following:

    • $arParams — parameters, read/change. Does not affect the homonymous component member.
    • $arResult — result, read/change. Does not affect the homonymous component class member.
    • $componentPath — path to the component folder from DOCUMENT_ROOT (e.g., /bitrix/components/bitrix/iblock.list).
    • $component — link to $this.
    • $this — link to the currently invoked component (CBitrixComponent class object), all class methods may be used.
    • $epilogFile — path to the file component_epilog.php from DOCUMENT_ROOT
    • $templateName — component template name (e.g.: .default)
    • $templateFile — path to the template file from DOCUMENT_ROOT (e.g. /bitrix/components/bitrix/iblock.list/templates/.default/template.php)
    • $templateFolder — path to the template folder from DOCUMENT_ROOT (e.g. /bitrix/components/bitrix/iblock.list/templates/.default)
    • $templateData — please note that this is the way to transfer data from template.php to the file component_epilog.php, and these data will be sent to the cache and become available in component_epilog.php with each hit/
    • $APPLICATION, $USER, $DB — global variables.

    Operation of a Composite Component in a SEF Mode

    Composite components have an embedded SEF generation function. These components always come with the input parameter SEF_MODE which may admit the values Y and N. If the SEF_MODE parameter is equal to N, the component works with physical links and transfers all parameters through the standard parameters of an HTTP request. For example:

    /fld/cat.php?IBLOCK_ID=12&SECTION_ID=371

    If the parameter SEF_MODE is equal to Y, the component generates and processes links based on templates. For example, the component can “understand” and process the link:

    /catalog/section/371.php?IBLOCK_ID=12, even if the component itself is located in the file /fld/cat.php.

    If the parameter SEF_MODE is equal to Y, the component must also have the parameter SEF_FOLDER, containing a path to the folder necessary for the component. This path may or may not coincide with the physical path. For example, in the component bitrix:catalog, connected to the file /fld/cat.php, the parameters SEF_MODE = Y and SEF_FOLDER=/catalog/ can be set up. In this case, the component will respond to queries following the path /catalog/….

    A composite component that can work in an SEF mode must have a set of default path templates. For example, in a composite component bitrix:catalog the following array must be defined:

    $arDefaultUrlTemplatesSEF = array( 
          "list" => "index.php", 
          "section" => "section.php?IBLOCK_ID=#IBLOCK_ID#&SECTION_ID=#SECTION_ID#",
          "element" => "element.php?ELEMENT_ID=#ELEMENT_ID#" 
      );

    These path templates can be redefined using the input parameters of the composite component SEF_URL_TEMPLATES, containing a new array of all the path templates or a part of them.

    When saving a page with a component working in an SEF mode, the editor creates or updates the entry in the urlrewrite system. E.g., when saving the file /fld/cat.php containing the component bitrix:catalog switched in the SEF mode with the parameter SEF_FOLDER=/catalog/, the urlrewrite system creates or update the following entry:

    array( 
     "CONDITION" => "#^/catalog/#", 
     "ID" => "bitrix:catalog", 
     "PATH" => "/fld/cat.php" 
      ),
    • A value of the SEF_FOLDER parameter is written in  CONDITION between the symbols «#^» and «#»;
    • The name of the component is written in ID;
    • A physical path to the saved file is written in PATH.

    If an entry with such PATH and ID already exists, it is updated; if not, it is added.

    In run-time when a physically inexistent page is requested, the urlrewrite mechanism searches for the relevant entry by CONDITION and transfers control to the page PATH.

    The component on the PATH page figures out the requested page and recovers variables hidden in the path.

    Attention! It is mandatory that the set of path templates of this component regarding each path template is unique, not taking into account the parameters and variables. It must be checked when saving the page in a visual editor.

    I.e., the path template set

    "section" => "section/#SECTION_ID#.php?IBLOCK_ID=#IBLOCK_ID#", 
    "element" => "element/#ELEMENT_ID#.php"

    is acceptable, but the path template set

    "section" => "#SECTION_ID#.php?IBLOCK_ID=#IBLOCK_ID#", 
    "element" => "#ELEMENT_ID#.php"

    is not acceptable.

    List of links on the subject:

    • HTTP POST request

    Frequent Errors

    Cannot Find Component Call Code

    There are various reasons to this error:

    • Component call code is not placed between separate <? ?>.

      Solution: Check separation of the component code from another php code on the page.

      I.e. if you have php code on the page like this:

      <?
      php code
      
      component
      
      php code
      ?>

      it will return an error.

      The code shall be written as follows:

      <?
      php code
      ?>
      
      <?
      component
      ?>
      
      <?
      php code
      ?>
      
      
    • Errors in html code on the page.

      Solution: Check the validity of the html code.

    • Inconsistency of file encoding with the project in general.

      Solution: Check the encoding. A quite versatile solution is to write the following strings in the file .htaccess:

      For the website with the windows-1251 encoding:

      php_value mbstring.func_overload 0
      php_value mbstring.internal_encoding cp1251

      For the website with the UTF-8 encoding:

      php_value mbstring.func_overload 2
      php_value mbstring.internal_encoding utf-8
    • Inconsistency between file the owner and user under which the system edits the files.

      Solution: Check the user’s rights.

    Component Caching

    One of the types of caching in Bitrix Framework is component caching.

    Components must use caching in order to process the client’s request faster and to reduce the server load. As a rule, the information that is not dependent on a specific visitor must be cached. For example, the list of website news is the same for all visitors. That is why it makes no sense to select the data from the database each time.

    All dynamic components that are used in order to create web pages have an embedded support of cache control. In order to use the technique, it is sufficient to activate auto cache by clicking a button on the administrative panel. This feature comes in handy at the developing stage when auto cache can be deactivated to make the work easier, and activated again before delivery of the project. In this case, all of the components with auto cache mode activated in their settings will create caches and will entirely switch to a work mode that does not involve database queries.

    Attention! When using the Autocache mode mode (Components cache), the information retrieved by the components is updated according to the parameters of separate components.

    Auto cache control is located on the tab Component caching (Control Panel > Settings > System settings > Cache Settings).

    Note: When component auto cache mode is activated, the components with the caching setting Auto + Managed will be switched to a work mode with caching.

    In order to update the contents of cached objects on a page, you can:

    1. Go to the required page and update its contents using the button Refresh Cache on the control panel.
    2. In Edit Mode use the flush cache buttons on panels of specific components.
    3. Use automatic cache reset upon the expiry of caching time; for this, select the caching mode Cache or Auto + Managed in the component settings.
    4. Use automatic cache reset when data are changed; for this, select the caching mode Auto + Managed in the component settings.
    5. Go to the settings of the selected components and switch them to the work mode without caching.

    Note: For additional information about component caching, please refer to the lesson Caching in Own Components.

    Autocache mode

    Auto cache mode (Components cache) appeared in version 6 of the product replacing the standard component cache. The difference is that auto cache may be globally deactivated for the entire website using one button (Disable Caching) in the administrative part on the page Cache Settings Settings > System settings > Cache Settings.

    As a rule, it is deactivated at the development stage and is activated before the delivery of the project. Do not expect Bitrix Framework itself to choose the caching time and the right moment to do so for a cache flush. Only the developer can do that based on the actual needs of each project: the caching time that is suitable for the information update frequency must be indicated in the component settings.

    Structure and Storage Place

    Cache of the components is stored in the files of the folder /bitrix/cache.

    Component cache identifier is generated based on the following parameters:

    • ID of the current website which determines the path to the cache file (alternative storage means may be used, but it does not affect the work with components),
    • Component name,
    • Component template name,
    • Component parameters,
    • External conditions determined in the component (e.g., list of groups to which the current user is connected).

    In knowing all of these parameters, it is possible to flush the cache of any component. Otherwise, the cache will be reset upon the expiry of the cache time, by the click of a button on the control panel of the public part, or by performing complete cache flush from the administrative part.

    Note: When you reset the cache of a page using the “Refresh Cache” button, please keep in mind that the component may use the binding to groups for cache storage; in this case, non-registered users will continue seeing an outdated page.

    The cache of public components is not reset immediately after adding/changing an infoblock element in the administrative part. This is because, for example, the News information block «does not know», where the news is displayed in the public part and how many components display it. It causes no problems if the caching time is set correctly.

    Component Workflow Structure

    $arParams contains a set of component parameters, component.php works with request input parameters and database, generates a result to the array$arResult. Component template converts results into HTML text.

    Upon the first hit, the generated HTML goes to cache. With subsequent hits, no (or very few) queries are made to the database since the data are read from the cache.

    Attention! Please keep in mind the order of execution; in this case, the component template code and result_modifier.php are not executed.

    Frequent error: in the component template, deferred functions are retrieved: $APPLICATION->SetTitle(), $APPLICATION->AddChainItem() etc. In this case, they work only if caching is off.

    When developing the templates of own components, the developer shall follow a simple rule: the template’s task is to generate HTML text on return based on the input array $arResult.

    The generated cache ID of own components must be capable of unambiguously determining the resulting html. However, avoid sending too much data to the cache ID. That results in wasting disc space and reduces cache hits (thereby making cache less efficient).

    Cache Dependencies (Tagged Cache)

    Starting from the main module of version 9.1.0, cache tags are supported. Cache can be marked with tags and reset also by tags. The cache of the infoblock components can be reset when the information contained in them changes.

    Note. For large data amount that are often updated, tagged caching is not justified.

    Cache Tagging Base Code:

    01: $cache_id = md5(serialize($arParams));
    02: $cache_dir = "/tagged_getlist";
    03:
    04: $obCache = new CPHPCache;
    05: if($obCache->InitCache(36000, $cache_id, $cache_dir))
    06: {
    07:     $arElements = $obCache->GetVars();
    08: }
    09: elseif(CModule::IncludeModule("iblock") && $obCache->StartDataCache())
    10: {
    11:     $arElements = array();
    12:     $rsElements = CIBlockElement::GetList($arParams["order"], $arParams["filter"]);
    13:
    14:     global $CACHE_MANAGER;
    15:     $CACHE_MANAGER->StartTagCache($cache_dir);
    16:     while($arElement = $rsElements->Fetch())
    17:     {
    18:         $CACHE_MANAGER->RegisterTag("iblock_id_".$arElement["ID"]);
    19:         $arElements[] = $arElement;
    20:     }
    21:     $CACHE_MANAGER->RegisterTag("iblock_id_new");
    22:     $CACHE_MANAGER->EndTagCache();
    23:
    24:     $obCache->EndDataCache($arIBlocks);
    25: }
    26: else
    27: {
    28:     $arElements = array();
    29: }

    Line 01 initializes the unique cache file identifier. Then, the catalog is defined from /bitrix/cache where the cache files are stored with different values of $arParams. It is important that this path start from slash but not end with it. When using memcached or APC as the cache it will be of critical importance for the cache reset.

    Lines 04-05 initialize the cached object. If the caching time is not expired, line 07 will be executed and we will obtain the data from the cache file.

    The condition in line 09 will be true nearly always. Here, the module is connected and caching starts.

    Line 12 provides for a database query. It is important that all of the parameters on which a selection result depends “participate” in the cache identifier ($cache_id).

    In line 14, the access to the variable $CACHE_MANAGER. is set. This object will control tags.

    Line 15 – all subsequently allocated tags will be bound to the catalog $cache_dir. When the cache is reset in one of them, the contents of this catalog will be deleted. StartTagCache may be used recursively. For example:

    $CACHE_MANAGER->StartTagCache($cache_dir1);
        $CACHE_MANAGER->StartTagCache($cache_dir2);
            $CACHE_MANAGER->StartTagCache($cache_dir3);
            $CACHE_MANAGER->EndTagCache();
        $CACHE_MANAGER->EndTagCache();
    $CACHE_MANAGER->EndTagCache();
    

    It is important that the calls StartTagCache and EndTagCache are balanced. The object $CACHE_MANAGER creates and tracks the stack of cache catalogs. In this case, the tags allocated for the catalog $cache_dir3 бwill also be connected with $cache_dir2 and $cache_dir1. The tags allocated for cache_dir2 will be also connected with $cache_dir1.

    Line 18 provides for tagging the cache by using the method RegisterTag. The body length may not exceed 100 characters. Tag duplicates are deleted automatically when the RegisterTag method is used.

    Line 22 writes catalog tags to the database table. The count is one insert per tag.

    Cache reset:

    $CACHE_MANAGER->ClearByTag("iblock_id_7");

    Infoblock Components

    To launch the mechanism, a constant in the file dbconn.php must be defined.

    define("BX_COMP_MANAGED_CACHE", true);

    If the method StartResultCache is used, the entry will be retrieved by StartTagCache with a path to the component cache (depending on the page).
    If the method EndResultCache is used (which, in its turn, is retrieved from IncludeComponentTemplate) — by EndTagCache.

    In the infoblock module CIBlockElement::GetList and CIBlockSection::GetList return the object of the CIBlockResult class.

    The method Fetch/GetNext of this object will retrieve $CACHE_MANAGER->RegisterTag("iblock_id_".$res["IBLOCK_ID"]);.
    If the selection does not contain any elements, the value of the infoblock identifier will be retrieved from the filter.

    Cache reset is called from the methods Add/Update/Delete for elements, sections, and infoblocks. When the properties are changed, for example, using SetPropertyValueCode there will be no flushing. In this case, the following code can be used to clear cache:

    if(defined('BX_COMP_MANAGED_CACHE'))
       $GLOBALS['CACHE_MANAGER']->ClearByTag('iblock_id_'.$arParams['IBLOCK_ID']);

    The use of this tagged cache mechanism is not recommended in case of the frequent update of elements or sections. On the other hand, it must be convenient for content managers: all changes are immediately displayed on the website.

    Adding an Own Tag to Component Caches

    When performing instructions of this lesson, it is assumed that you have tagged caching activated.

    Solution 1

    Add the following code in the component body:

    if ($this->StartResultCache(......))
    {
       if (defined('BX_COMP_MANAGED_CACHE') && is_object($GLOBALS['CACHE_MANAGER']))
       {
                $GLOBALS['CACHE_MANAGER']->RegisterTag('my_custom_tag');   
       }
    
       // do something
    
       $this->IncludeComponentTemplate();
    }
    else
    {
       $this->AbortResultCache();
    }
    

    Solution 2

    Add the following code to the component template (в result_modifier.php):

    <?
    if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    if (defined('BX_COMP_MANAGED_CACHE') && is_object($GLOBALS['CACHE_MANAGER']))
    {
       $cp =& $this->__component;
       if (strlen($cp->__cachePath))
       {      
          $GLOBALS['CACHE_MANAGER']->RegisterTag('my_custom_tag');
       }
    }
    ?>
    

    To reset all of the caches marked with your tag, execute the following code:

    if (defined('BX_COMP_MANAGED_CACHE') && is_object($GLOBALS['CACHE_MANAGER']))
       $GLOBALS['CACHE_MANAGER']->ClearByTag('my_custom_tag');
    

    Note: The same cache can be marked with several tags. For example, if you mark the cache of the component bitrix:news.list with your tag, the cache will have two tags: the standard «iblock_id_XX» and your «my_custom_tag». Accordingly, the cache will be reset both in case of adding/changing an element in the infoblock XX (standard functionality) and in case of resetting cache manually through ClearByTag('my_custom_tag').

    Working with Components

    A component is the primary way to retrieve information in Bitrix Framework. Accordingly, it is the work with the component that opens the maximum possibilities to change data display conditions and change (add) system functionalities.

    he following task/solution ratio is recommended:

    • To solve tasks that are aimed at changing the data display form (design) modify the component template.
    • In order to change and expand cached data displayed by the component, please use the options of the file result_modifer.php.
    • In order to implement the logic executed upon each call of the component irrespective of cache, use the options of the file component_epilog.php.
    • In order to make additions and implicit changes (without interfering with the code) of component operation logic the technique of Events can be used.
    • To expand the component operation logic, copy the component to its namespace and change it.
    • To create new logic and new options, create a new component.

    Quite often a task has to be solved using a combination of methods. I.e., for example, editing the template and adding a code in result_modifier.php.

    In the chapters that follow, we will review the work with components in the indicated order of tasks and solutions.

    Attention! When performing any actions while working with components, please do not forget about caching. Heavy coding introduced in component_epilog.php not be subject to cache. However, there are situations when it is better to customize the component and it may result in better performance (especially when a heavy code is used on a home or a most frequently visited page).

    Template Customization

    Template editing is one of the means to obtain the result of the component work that is appropriate for each specific website. Editing the system template is not recommended because after the very first update, the system template will recover its initial state. Only user template can be edited and used.

    When starting to customize a template, please remember:

    All logic must be located in the component, and the template must contain only the display representation of the data obtained!

    As a rule, component template customization is aimed at:

    • Adjusting the component data display form according to the website design;
    • Arranging for component data display in a way unavailable as a standard option.

    User templates of a component are the templates that are changed to meet the requirements of a specific project. They must be located in the portal template folders (i.e. in /bitrix/templates/site_template/). When copying component template using system means they will be located at: /bitrix/templates/site_template/components/namespace/component_name/template_name.

    A template can be copied as follows:

    • Within the file system – by coping the folder /bitrix/components/bitrix/_required_component_/templates/ to the folder /bitrix/templates/website_template/components/namespace/component_name/_template_name.
    • Using system interface means using the command Copy component template (with Edit Mode activated):

    When copying the template, its application to a component can be set and a template editing form can be opened at once:

    It is possible not to apply the copied template to a component at once, but rather to do it later on by selecting the template in the component settings.

    Important! If during component template editing you add new identifiers of language messages, please remember that the identifiers must be unique throughout the entire product.

    Template editing admits adding action logic, but such a modification should be made in the files result_modifier.php and component_epilog.php (which must be located in the template folder) for a more complex change of work result.

    The chapter provides for some examples of template customization.

    Modification of a Simple Component Template within a Composite Component

    A composite component ensures the interaction of simple components with the general subject. Simple components contain the code of immediate work with data. For example, it is inconvenient to set up the components of a social network one by one.

    However, if you only seek to change the external appearance of some elements, it can be easily done without refusing other standard templates.

    For example, you need to change the form of the news list, and the entire news section is built using a composite component. The list of news is built using the simple component List of news (news.list) which, similarly to the component templates for building a detailed news page, feedback, search, etc., forms part of the composite component template News (news). When copying the News template to the site template by using the option Copy component template of the context menu of the component, all of the files of the component template are copied to the website template. This means that when the News component template is updated through the Site Update you, by using a customized template, will lose the template update of all other templates included in the composite component (do not worry, standard templates and components are getting updated as usual). In order to customize only the component template List of news (news.list) and to make sure the composite component News connects it but uses the other standard (updated) templates of the templates included into the composite component, do the following:

    • Create the folder news (or, depending on the composite component, a part of the template you need to customize) manually through file structure of the website in the website template folder (.default or current)
    • Copy the template of the simple component so that its path resulted as follows:
      /bitrix/templates/site_template/components/bitrix/news/.default/bitrix/news.list/.default/
    • Edit the template obtained

    The same approach can be used to edit the templates of simple components connected from the templates of composite components (i.e. when a template of a simple component does not form part of a composite component, and when a simple component is connected in the script of a composite component using the method CMain::IncludeComponent()).

    For example, you have to change the form of creating/editing a group. In a social template Bitrix24 this form is built using the component Create new group in modal dialog box (socialnetwork.group_create.ex) to be connected from the composite component Social Network (socialnetwork_group).

    If no modification of other parts of the composite component is needed, it will suffice to copy the component template socialnetwork.group_create.ex to the website template (/bitrix/templates/site_template/components/bitrix/socialnetwork_group/.default/bitrix/socialnetwork.group_create.ex/.default/) and modify it there.

    In this case, all remaining code will remain standard, i.e. it will be updated and supported by Bitrix, Inc.

    Template Modification or Creation of result_modifier?

    Quite often there are several solutions available to solve the same task. It is up to the developer to choose the final option based on the task at hand. Let us consider the examples of solving a task for which two solutions are available.

    How to embed a video when posting news on a website? It may seem difficult at first. However, it is actually quite easy. The idea consists in connecting the component Media Player (bitrix:player) for a file attached to the news. The component News details (bitrix:news.detail) will be used to display the news.

    Whatever solution you choose, you will have to create a property of the File type in the news infoblock.

    Solution Involving Template Editing

    • Copy the component template news.detail to the website template. You will not have to change the component itself.
    • Create a new page using visual editor and place the component Media Player (bitrix:player) on it. Indicate basic settings (do not enter the path to the video file at this stage). Copy the following code from the obtained:
      <?$APPLICATION->IncludeComponent(
         "bitrix:player",
         "",
         Array(
            "PLAYER_TYPE" => "auto", 
            "USE_PLAYLIST" => "N", 
            "PATH" => "",
            "WIDTH" => "400", 
            "HEIGHT" => "300", 
            "FULLSCREEN" => "Y", 
            "SKIN_PATH" => "/bitrix/components/bitrix/player/mediaplayer/skins", 
            "SKIN" => "bitrix.swf", 
            "CONTROLBAR" => "bottom", 
            "WMODE" => "transparent", 
            "HIDE_MENU" => "N", 
            "SHOW_CONTROLS" => "Y", 
            "SHOW_STOP" => "N", 
            "SHOW_DIGITS" => "Y", 
            "CONTROLS_BGCOLOR" => "FFFFFF", 
            "CONTROLS_COLOR" => "000000", 
            "CONTROLS_OVER_COLOR" => "000000", 
            "SCREEN_COLOR" => "000000", 
            "AUTOSTART" => "N", 
            "REPEAT" => "N", 
            "VOLUME" => "90", 
            "DISPLAY_CLICK" => "play", 
            "MUTE" => "N", 
            "HIGH_QUALITY" => "Y", 
            "ADVANCED_MODE_SETTINGS" => "N", 
            "BUFFER_LENGTH" => "10", 
            "DOWNLOAD_LINK_TARGET" => "_self" 
         )
      );?>
    • In the component template, set up the media player connection instead of the movie property. Find the strings for property display:
      30         <?foreach($arResult["DISPLAY_PROPERTIES"] as $pid=>$arProperty):?>
       31
       32                 <?=$arProperty["NAME"]?>: 
       33                 <?if(is_array($arProperty["DISPLAY_VALUE"])):?>
       34                         <?=implode(" / ", $arProperty["DISPLAY_VALUE"]);?>
       35                 <?else:?>
       36                         <?=$arProperty["DISPLAY_VALUE"];?>
       37                 <?endif?>
       38                 <br />
       39         <?endforeach;?>
    • Insert checking and replacement. The result:
      <?foreach($arResult["DISPLAY_PROPERTIES"] as $pid=>$arProperty):?>
      <?if ($arProperty["CODE"]=='movie' && $arProperty["DISPLAY_VALUE"]) {?>
      
      <?$APPLICATION->IncludeComponent(
         "bitrix:player",
         "",
         Array(
            "PLAYER_TYPE" => "auto", 
            "USE_PLAYLIST" => "N", 
            "PATH" => CFile::GetPath($arProperty["VALUE"]),
            "WIDTH" => "400", 
            "HEIGHT" => "300", 
            "FULLSCREEN" => "Y", 
            "SKIN_PATH" => "/bitrix/components/bitrix/player/mediaplayer/skins", 
            "SKIN" => "bitrix.swf", 
            "CONTROLBAR" => "bottom", 
            "WMODE" => "transparent", 
            "HIDE_MENU" => "N", 
            "SHOW_CONTROLS" => "Y", 
            "SHOW_STOP" => "N", 
            "SHOW_DIGITS" => "Y", 
            "CONTROLS_BGCOLOR" => "FFFFFF", 
            "CONTROLS_COLOR" => "000000", 
            "CONTROLS_OVER_COLOR" => "000000", 
            "SCREEN_COLOR" => "000000", 
            "AUTOSTART" => "N", 
            "REPEAT" => "N", 
            "VOLUME" => "90", 
            "DISPLAY_CLICK" => "play", 
            "MUTE" => "N", 
            "HIGH_QUALITY" => "Y", 
            "ADVANCED_MODE_SETTINGS" => "N", 
            "BUFFER_LENGTH" => "10", 
            "DOWNLOAD_LINK_TARGET" => "_self" 
         ),
         $component   
      );?> 
      <? } else {?>
            <?=$arProperty["NAME"]?>: 
            <?if(is_array($arProperty["DISPLAY_VALUE"])):?>
               <?=implode(" / ", $arProperty["DISPLAY_VALUE"]);?>
            <?else:?>
               <?=$arProperty["DISPLAY_VALUE"];?>
            <?endif?>
      <?}?>
            <br />
         <?endforeach;?>

    Note: Please pay attention to the following:

    • The system call CFile::GetPath is used to obtain the path to the file from the ID.
    • When connecting components, the fourth parameter $component is indicated so that its parameters cannot be changed from the public part

    Solution Using result_modifier.php

    If you want to continue using the updated component the task should be solved using result_modifier.php.

    • Create the file result_modifier.php with the code:
      <?
      // transfer property value using the link:
      $arProperty = &$arResult['DISPLAY_PROPERTIES'][$arParams['PROPERTY_VIDEO']];
      
      if ($arProperty['DISPLAY_VALUE']) // verify whether the property is set
      {
         global $APPLICATION;
         ob_start(); // activate buffering to catch component retrieval
         $APPLICATION->IncludeComponent(
            "bitrix:player",
            "",
            Array(
               "PLAYER_TYPE" => "auto", 
               "USE_PLAYLIST" => "N", 
               "PATH" => CFile::GetPath($arProperty["VALUE"]),
               "WIDTH" => "400", 
               "HEIGHT" => "300", 
               "FULLSCREEN" => "Y", 
               "SKIN_PATH" => "/bitrix/components/bitrix/player/mediaplayer/skins", 
               "SKIN" => "bitrix.swf", 
               "CONTROLBAR" => "bottom", 
               "WMODE" => "transparent", 
               "HIDE_MENU" => "N", 
               "SHOW_CONTROLS" => "Y", 
               "SHOW_STOP" => "N", 
               "SHOW_DIGITS" => "Y", 
               "CONTROLS_BGCOLOR" => "FFFFFF", 
               "CONTROLS_COLOR" => "000000", 
               "CONTROLS_OVER_COLOR" => "000000", 
               "SCREEN_COLOR" => "000000", 
               "AUTOSTART" => "N", 
               "REPEAT" => "N", 
               "VOLUME" => "90", 
               "DISPLAY_CLICK" => "play", 
               "MUTE" => "N", 
               "HIGH_QUALITY" => "Y", 
               "ADVANCED_MODE_SETTINGS" => "N", 
               "BUFFER_LENGTH" => "10", 
               "DOWNLOAD_LINK_TARGET" => "_self" 
            )
         ); 
         $arProperty['DISPLAY_VALUE'] = ob_get_contents(); // substitute $arResult
         ob_clean(); // clean our buffer so that the player do not appear twice
         ob_end_clean(); // close the buffer
      }
      ?>

      The symbol code of the property can be made the parameter of the component to avoid the strict connection to a specific infoblock. To do so, the file .parameters.php of the News details component located in the copied component template must be adjusted.

    • Change the code of the file .parameters.php:
      <?
      if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
      $arProps = array(); 
      $rs=CIBlockProperty::GetList(array(),array("IBLOCK_ID"=>$arCurrentValues['IBLOCK_ID'],"ACTIVE"=>"Y"));
      while($f = $rs->Fetch())
         $arProps[$f['CODE']] = $f['NAME'];
      
      $arTemplateParameters = array(
         "DISPLAY_DATE" => Array(
            "NAME" => GetMessage("T_IBLOCK_DESC_NEWS_DATE"),
            "TYPE" => "CHECKBOX",
            "DEFAULT" => "Y",
         ),
         "DISPLAY_NAME" => Array(
            "NAME" => GetMessage("T_IBLOCK_DESC_NEWS_NAME"),
            "TYPE" => "CHECKBOX",
            "DEFAULT" => "Y",
         ),
         "DISPLAY_PICTURE" => Array(
            "NAME" => GetMessage("T_IBLOCK_DESC_NEWS_PICTURE"),
            "TYPE" => "CHECKBOX",
            "DEFAULT" => "Y",
         ),
         "DISPLAY_PREVIEW_TEXT" => Array(
            "NAME" => GetMessage("T_IBLOCK_DESC_NEWS_TEXT"),
            "TYPE" => "CHECKBOX",
            "DEFAULT" => "Y",
         ),
         "PROPERTY_VIDEO" => Array(
            "NAME" => "Property where the video is stored",
            "TYPE" => "LIST",
            "VALUES" => $arProps
         ),
      );
      ?>

    As a result, a new parameter will appear in the component settings.

    Do not forget to indicate a property where the video is stored in the component connection parameters. Otherwise, it will not be displayed.

    Component Customization

    Standard component customization — means copying a standard component to the own namespace and changing its operation logic to change/add a functionality.

    Most tasks in Bitrix Framework are implemented through components, and in the component template you use the $arResult arrays that constitute the work result of the component (data) and $arParams that are input parameters.

    The following steps must be taken in order to customize a standard component:

    • Create a new component namespace in the folder /bitrix/components/; for example, create the catalog /bitrix/components/my_components/.
    • A folder with component you wish to change must be copied to the created folder (copy is to be made from the folder /bitrix/components/bitrix/).
    • Change the component according to the tasks at hand.
      • Change component description to your description in the files .description.php and /lang/en/.description.php;
      • Correct the files .parameters.php and component.php by modifying (adding necessary) functionality using API product;
    • Edit the component template according to the tasks at hand.
    • Clear the cache of the visual editor. As a result, the visual editor will display the customized component.

      Note: Cache of the visual editor is updated in the tab Components:

    Important! While customizing a component, please remember that all of the keys in $MESS containing the name, description, and parameters of a component and also the identifiers of component branches in the component tree of the visual editor must be unique throughout the entire product.

    It is preferable to refrain from component customization unless it is really necessary. In this case:

    • No updates are lost;
    • It is easier to solve problems through helpdesk (helpdesk does not solve the problems occurring in operation of a customized code unless an error in API is clearly identified);
    • AJAX is implemented in composite components as a standard.

    Simple Example of a Component Customization

    If there are too many elements, the component news.list may significantly slow down the generation of a page. The component must be optimized. You may consider deleting the link to the detailed news text in the preview description as one of the optimization options (a link with the name of the news will remain).

    • Copy the component to the namespace (/bitrix/components/my_components/).
    • Delete the following lines in the code of the component copied (/bitrix/components/my_components/news.list/component.php):
      "DETAIL_TEXT",
      "DETAIL_TEXT_TYPE",

      and

      if($bGetProperty)
      	$arSelect[]="PROPERTY_*";
    • Save the changes made.
    • Apply your own component instead of the standard on.

    As a result, there will be more database queries but the page will be formed faster.

    Modifying a Simple Component as a Part of a Composite One

    When working with a composite component, one or more simple components may be modified, leaving the remaining simple components unchanged.

    For example, if you want to increase the length of a group description to be displayed from 50 to 100 characters in the component socialnetwork.user_groups, which forms part of the composite component socialnetwork_group and displays a list of groups, then perform the following.

    • Copy the template of the composite component.

      Now, the template of the composite component is located in the website template. If we go there, we will see lots of files in the folder /bitrix/templates/<website template>/components/bitrix/socialnetwork_group/.default.

      Each file is invoked on a specific page of the social network and connects the simple components that are required.

      Now, the file that connects this component must be found and changed. In this case, the file is index.php.
      The rest of the files in the template of the composite component located in the website template can be deleted. The composite component will connect these files from the core. This means that they will be updated.

    • Now in the remaining file, we replace
      $APPLICATION->IncludeComponent(
                  "bitrix:socialnetwork.user_groups",

      with

      $APPLICATION->IncludeComponent(
                  "custom:socialnetwork.user_groups",
    • Copy the folder /bitrix/components/bitrix/socialnetwork.user_groups to /bitrix/components/custom/socialnetwork.user_groups.
    • In the file /bitrix/components/custom/socialnetwork.user_groups/component.php replace
      "GROUP_DESCRIPTION" => SubStr($arGroups["GROUP_DESCRIPTION"], 0, 50)."...",

      with

      "GROUP_DESCRIPTION" => SubStr($arGroups["GROUP_DESCRIPTION"], 0, 100)."...",

    Now, all of the functional capacity of the social network remains standard, except for the component socialnetwork.user_groups.

    Creating Components

    As a matter of fact, today own component may have to be written only when an absolutely new functionality for the website is needed. Since the set of standard components is quite extensive, in most cases just expanding the functionality of the already available components will suffice and there is no need to write new components.

    However, sooner or later a developer must learn to create their own components.

    Standard Sequence of Operations

    • In a web project, the possible types of own components are identified and described during the drawing up of the terms of reference and design.
    • The namespace of own components is determined, for example, by using the project name. The system components of Bitrix Framework are located in the namespace bitrix, which is where the project components can be located, for example in the namespace citybank.

      Attention! The names of the components to be created must not be the same as the names of standard components.

    • It is determined that a standard component may serve as a basis for creating an own component. The code of standard components contains lots of examples of typical and correct use of API and programming techniques, which is why they are considered a good starting point.
    • An interface is designed for each component 2.0. It should be decided as to which component parameters must be available to the website administrator for editing. For example, for the component displaying a weather forecast, the property Web Service Address and Web Service Connection Timeout may be put in the administrator’s settings, etc.
    • It is decided as to which section of the component tree in the visual editor this component should be located.
    • Component programming. Special attention shall be paid to set up the autocache of the component and its operating profile properly. It must not execute database queries in caching mode, must execute a minimum amount of database queries in case of cache aging, store only necessary data in cache, use a minimum possible volume of RAM (it must not sort arrays of tens or hundreds of megabytes, etc.).

    Component Creation Procedure

    Make the required php code into a separate file in order to use it later as an invoked file. However, the component must also be connected to the system using a description file identifiable by the core of Bitrix Framework. As a result, a user can see the icon with the component name in the visual editor and can set up the component properties.

    Remember that a component consists of a php code with terminated functionality made into a separate file, a file of component registration in the system and its parameter descriptions, and also localization files.

    • Component registration
      • Detachment of a required php code into a separate file.
      • Create the description file .description.php
      • Locate files in a folder in the own namespace.
    • Setting up parameters in the component code
    • Localization
      • Prepare files with text constants for the component and registration file: /lang/en/<component_name>/<component_name>.php and /lang/en/<component_name>/.description.php
      • make changes in the code of both files of the component in order to use these constants (the localization file is connected using the function IncludeTemplateLangFile).

    Attention! All keys in $MESS containing the name, description, and parameters of the component and also the identifiers of component branches in the component tree of the visual editor must be unique for the entire product.

    Additional Methods

    Additional Methods Available in Components and Templates

    Additional methods from the class CComponentEngine can be used in components and templates.

    string CComponentEngine::MakePathFromTemplate($pageTemplate, $arParams);

    where::

    $pageTemplate — a template of the type /catalog/#IBLOCK_ID#/section/#SECTION_ID#.php or catalog.php?BID=#IBLOCK_ID#&SID=#SECTION_ID#,

    $arParams — an associative array of reparametrization where the parameter name is the key and the parameter value is the value. It returns a path based on the path template $pageTemplate and reparametrization.

    Example:

    $url = CComponentEngine::MakePathFromTemplate
    ("/catalog/#IBLOCK_ID#/section/#SECTION_ID#.php", 
            array( 
                 "IBLOCK_ID" => 21, 
                 "SECTION_ID" => 452  
                 ) 
    );

    Organizing an Explicit Connection among the Components on the One Page of a Composite Component

    Explicit connection among the components can be organized through return values and incoming parameters of these components.

    If data from the component comp1 must be transferred to the component comp2, in the end of the component code comp1 must be written: return data;

    The component comp1 must be connected as follows:

    $result = $APPLICATION->IncludeComponent(comp1, ...);

    Now the data are located in the variable $result , and they can be transferred as incoming parameters into another component comp2.

    Redefinition of Incoming Variables

    Each component has a set of variables in which it receives codes or other attributes of requested data from the outside. For example, the component bitrix:catalog.section has variables IBLOCK_ID and SECTION_ID in which it receives and processes codes of the catalog and the product group, accordingly.

    All components that form part of a composite component must have a single set of variables. For example, the composite component bitrix:catalog and all simple components (bitrix:catalog.list, bitrix:catalog.section etc.), under its control work with the variables IBLOCK_ID, SECTION_ID, ELEMENT_ID, and others.

    If the developer wants to redefine the component variables when placing a composite component on a page, the developer must set up the parameter VARIABLE_ALIASES among the incoming parameters of the component.

    When connecting a component in the SEF mode, this parameter must look as follows:

    "VARIABLE_ALIASES" => array( 
          "list" => array(),
          "section" => array(
                            "IBLOCK_ID" => "BID",
                            "SECTION_ID" => "ID"
                            ),
                            "element" => array(
                            "SECTION_ID" => "SID",
                            "ELEMENT_ID" => "ID"
                            ),
    )

    Here, array codes are consistent with the codes in the path template array. For each path, their own redefinitions of variables can be set up.

    When connecting a component not in the SEF mode, this parameter must be:

    "VARIABLE_ALIASES" => array(
                               "IBLOCK_ID" => "BID",
                               "SECTION_ID" => "GID",
                               "ELEMENT_ID" => "ID",
    )

    Example No. 1:

    Let us assume that the component bitrix:catalog connected in the file /fld/cat.php must work with the paths:

    /catalog/index.php – for a list of catalogs,

    /catalog/section/group_code.php?ID=catalogue_code – for a group of goods,

    /catalog/element/goods_code.php?ID=group_code – for detailed information about an item of goods.

    The following parameters must be set up in the incoming parameters for component connection:

    "SEF_MODE" => "Y",    
    "SEF_FOLDER" => "/catalog/",
    "SEF_URL_TEMPLATES" => array(
                        "list" => "index.php",
                        "section" => "section/#SECTION_ID#.php?ID=#IBLOCK_ID#",
                        "element" => "element/#ELEMENT_ID#.php?ID=#SECTION_ID#"    
                                ),
    "VARIABLE_ALIASES" => array(
                         "list" => array(),
                         "section" => array(
                                       "IBLOCK_ID" => "ID"),
                         "element" => array(
                                       "SECTION_ID" => "ID",),    
    

    Example No. 2:

    Let us assume that the component bitrix:catalog connected in the file /fld/cat.php must work with the paths

    /fld/cat.php – for a list of catalogs,

    /fld/cat.php?BID=catalogue_code&SID=group_code – for a group of goods,

    /fld/cat.php?ID=goods_code&SID=group_code – for detailed information about an item of goods.

    The following parameters must be set up in the incoming parameters for a component connection:

    "SEF_MODE" => "N",
    "VARIABLE_ALIASES" => array(
                               "IBLOCK_ID" => "BID",
                               "SECTION_ID" => "SID",
                               "ELEMENT_ID" => "ID",
                               ),

    User-Defined Templating Engines

    Components can work with any templating engines that can be connected from PHP. In order to add a new templating motor onto a website it is necessary to determine (or expand) the global variable $arCustomTemplateEngines in the file /bitrix/php_interface/init.php. This variable contains an associative array where each element is similar to:

       "templator_code" => array(
          "templateExt" => array("extension1"[, "extension2"...]),
          "function" => "motor_connection_function_name"
       )

    where:

    • templator_code — an arbitrary word that is unique for the website,
    • extensionN — the extension of the file that must be processed by this templating motor,
    • motor_connection_function_name — the name of the function that can be invoked if the component template has the indicated extension. The function can be located in the same file /bitrix/php_interface/init.php.

    For example, if the use of Smarty is required on the website in addition to the standard templating motor (PHP), the following code must be added to the file /bitrix/php_interface/init.php:

       global $arCustomTemplateEngines;
       $arCustomTemplateEngines = array(
          "smarty" => array(
             "templateExt" => array("tpl"),
             "function" => "SmartyEngine"
          ),
       );

    As a result, when a .tpl template is connected, the function SmartyEngine will start up instead of the standard motor PHP. SmartyEngine must connect theSmarty motor.

    The syntaxis of motor connection functions is as follows:

       function  motor_connection_function_name ($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)

    where:

    • $templateFile – path to the template file from the website root,
    • $arResult – array of results of the component operation,
    • $arParams – array of incoming parameters of the component,
    • $arLangMessages – array of language messages (translations) of the template,
    • $templateFolder – path to the template folder from the website root (if the template is not located in the folder, this variable is empty),
    • $parentTemplateFolder — path from the website root to the composite component folder as a part of which this component is connected (if the component is connected independently, this variable is empty),
    • $template – template object.

    The code of the templating motor connection function depends on the motor to be connected.

    Complete Example of Smarty Motor Connection Smarty

    The following code must be added to the file /bitrix/php_interface/init.php:

    global $arCustomTemplateEngines;
    $arCustomTemplateEngines = array(
       "smarty" => array(
          "templateExt" => array("tpl"),
          "function" => "SmartyEngine"
       )
    );
    
    function SmartyEngine($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)
    {
       if (!defined("SMARTY_DIR"))
          define("SMARTY_DIR", "/libs/");
    
       require_once('/libs/Smarty.class.php');
    
       $smarty = new Smarty;
    
       $smarty->compile_dir = "/templates_c/";
       $smarty->config_dir = "/configs/";
       $smarty->template_dir = "/templates/";
       $smarty->cache_dir = "/cache/";
    
       $smarty->compile_check = true;
       $smarty->debugging = false;
    
       $smarty->assign("arResult", $arResult);
       $smarty->assign("arParams", $arParams);
       $smarty->assign("MESS", $arLangMessages);
       $smarty->assign("templateFolder", $templateFolder);
       $smarty->assign("parentTemplateFolder", $parentTemplateFolder);
    
       $smarty->display($_SERVER["DOCUMENT_ROOT"].$templateFile);
    }

    The line <absolute path to Smarty motor> must be replaced everywhere with the absolute path to Smarty motor. More detailed information about the installation of the motor on a website is provided in the Smarty help system.

    In the sample code, the Smarty motor is registered in the array $arCustomTemplateEngines. Parameters of the motor are initialized in the SmartyEngine function in accordance with system requirements (see the Smarty documentation). Then, the variables of component operation results, incoming parameters, language messages, etc. are transferred to Smarty. And, in the end, the Smarty template processing and displaying method is invoked.

    Complete Example of XML/XSLT Motor Connection

    The following code must be added to the fileл /bitrix/php_interface/init.php:

    global $arCustomTemplateEngines;
    $arCustomTemplateEngines = array(
       "xslt" => array(
          "templateExt" => array("xsl"),
          "function" => "XSLTEngine"
       ),
    );
    
    function CreateXMLFromArray($xDoc, $xNode, $ar)
    {
       foreach($ar as $key=>$val)
       {
          if(!is_string($key) || strlen($key)<=0)
             $key = "value";
    
          $xElement = $xDoc->createElement($key);
          if(is_array($val))
          {
             CreateXMLFromArray($xDoc, $xElement, $val);
          }
          else
          {
             $xElement->appendChild($xDoc->createTextNode(iconv(SITE_CHARSET, "utf-8", $val)));
          }
          $xNode->appendChild($xElement);
       }
       return $xNode;
    }
    
    function XSLTEngine($templateFile, $arResult, $arParams, $arLangMessages, $templateFolder, $parentTemplateFolder, $template)
    {
       $arResult["PARAMS"] = array(
          "templateFolder" => $templateFolder,
          "parentTemplateFolder" => $parentTemplateFolder,
          "arParams" => $arParams,
          "arLangMessages" => $arLangMessages
       );
    
       $xDoc = new DOMDocument("1.0", SITE_CHARSET);
       $xRoot = $xDoc->createElement('result');
       CreateXMLFromArray($xDoc, $xRoot, $arResult);
       $xDoc->appendChild($xRoot);
    
       $xXsl = new DOMDocument();
       $xXsl->load($_SERVER["DOCUMENT_ROOT"].$templateFile);
    
       $xProc = new XSLTProcessor;
       $xProc->importStyleSheet($xXsl);
    
       echo $xProc->transformToXML($xDoc);
    }

    Operation of a Composite Component in SEF Mode

    The SEF generating function is embedded in the composite components. These components always have an input parameter SEF_MODE that admits the values Y and N. If SEF_MODE is equal to N the component works with physical links and transfers all of the parameters through the standard parameters of HTTP query. For example:

    /fld/cat.php?IBLOCK_ID=12&SECTION_ID=371

    If the SEF_MODE parameter is equal to Y then the component generates and processes links based on templates. For example, the component can “understand” and process the link:

    /catalog/section/371.php?IBLOCK_ID=12 even if the component itself is located in the file /fld/cat.php.

    If the SEF_MODE parameter is equal to Y, the component must also have the SEF_FOLDER, parameter that must contain a path to the folder with which the component works. This path may either coincide with the physical path or not. For example, the parameters SEF_MODE = Y and SEF_FOLDER=/catalog/ may be set up in the component bitrix:catalog connected in the file /fld/cat.php. In this case, the component will respond to queries using the path /catalog/…. By default, the editor must set up the current physical path to the editable file.

    A composite component that can work in an SEF mode must have a set of path templates by default. For example, in the composite component bitrix:catalog the following array can be defined:

    $arDefaultUrlTemplatesSEF = array( 
          "list" => "index.php", 
          "section" => "section.php?IBLOCK_ID=#IBLOCK_ID#&SECTION_ID=#SECTION_ID#",
          "element" => "element.php?ELEMENT_ID=#ELEMENT_ID#" 
      );

    These path templates can be redefined using the input parameter of the composite component SEF_URL_TEMPLATES that contains a new array of all path templates or a part of them.

    When saving a page with a component operating in an SEF mode, the editor creates or updates an entry in the urlrewrite system. For example, when saving the file /fld/cat.php containing the component bitrix:catalog switched to an SEF mode with the parameter SEF_FOLDER=/catalog/, the urlrewrite system creates or updates an entry similar to:

    array( 
     "CONDITION" => "#^/catalog/#", 
     "ID" => "bitrix:catalog", 
     "PATH" => "/fld/cat.php" 
      ),
    • The value of the parameter SEF_FOLDER is written in CONDITION between the symbols #^ and #;
    • The name of the component is written in ID;
    • The physical path to the file saved is written in PATH.

    If an entry with such PATH and ID already exists, it gets updated and, if not, it is added.

    In run-time, upon receiving a query for a physically nonexistent page, the urlrewrite mechanism searches for the appropriate entry according to CONDITION and transfers control to the page PATH.

    The component located on the PATH page identifies the requested page based on path templates and recovers variables hidden in the path.

    Attention! The mandatory requirement for the set of path templates for this component is the uniqueness of each path template, except parameters and variables. It must be verified when saving the page in the visual editor.

    For example, a set of path templates:

    "section" => "section/#SECTION_ID#.php?IBLOCK_ID=#IBLOCK_ID#", 
    "element" => "element/#ELEMENT_ID#.php"

    is acceptable, and a set of path templates:

    "section" => "#SECTION_ID#.php?IBLOCK_ID=#IBLOCK_ID#", 
    "element" => "#ELEMENT_ID#.php"

    is not acceptable.

    Ways of Data Transmission among Components

    Ways of Data Transmission among Components:

    1. Global variables. For example:
      $GLOBALS['mycomponent_variable'] = $arResult["ID"];

      In addition to GLOBALS you can also use $_SESSION provided that:

      • The volume of data is not big;
      • Immediately after transmission, the data will be deleted from $_SESSION, otherwise, they will be “alive” so long as the session is active.
    2. Wrapper class, for example:
      Class GarbageStorage{
         private static $storage = array();
         public static function set($name, $value){ self::$storage[$name] = $value;}
         public static function get($name){ return self::$storage[$name];}
      }

      Accordingly, the use:

      GarbageStorage::set('MyCustomID', $arResult["ID"]); #set the value
      GarbageStorage::get('MyCustomID'); #obtain the value

    Choose the way depending on the components, on what, exactly, you want to transmit to another component, and whether or not there are necessary data available in non-cached files (speaking of component_epilog.php). The use of the wrapper class is more difficult but far more correct.

    A Simple Example of a Component Creation

    As an example, we will create a component which displays current date and time. In this case, the date and time format is set in the component settings. In real life, creating such a component makes no sense, but we do it here in order to understand how a component is developed. The procedure is similar for more complex cases.

    Preliminary Actions

    Preparing php Code of the Component

    The first thing we have to do is write a php code that performs what we need.

    <?
    echo date('Y-m-d');
    ?>

    However, this code just displays the date and there is no option to choose another format. We’d better put the data display format into the variable:

    <?
    $format = 'Y-m-d';
    echo date($format);
    ?>

    And, as a final touch, we have to separate logics and representation:

    <?
    // parameters
    $format = 'Y-m-d';
    // logics
    $d = date($format);
    // representation
    echo $d;
    ?>

    Creating a Structure of Component Folders and Files

    Now we have to create an own namespace, for example: dv. To do this, we have to create a folder /bitrix/components/dv. Inside, we create a component folder — date.current. And inside this folder, in its turn, we create two mandatory files and a folder to store templates titled templates. The folder templates must contain the folder .default with the file template.php inside.

    We obtain the following structure in the folder /bitrix/components/dv/date.current:

    • component.php
    • .description.php
    • templates/.default/template.php

    Implementing the Component without Input Parameters

    For now, we create the component without the option to set up the input parameter – data format.

    File contents:

    • component.php
      <? if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
      $arResult['DATE'] = date('Y-m-d');
      $this->IncludeComponentTemplate();
      ?>
    • .description.php
      <? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die(); $arComponentDescription = array(
      "NAME" => GetMessage(“Current date”),
      “DESCRIPTION” => GetMessage(“Display current date”),
      );
      ?>
    • templates/.default/template.php
      <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();?>

    As you might have noted, each component file contains the string if (!defined(“B_PROLOG_INCLUDED”) || B_PROLOG_INCLUDED!==true) die(); in the beginning. This is required so that these files cannot be invoked directly from the browser window.

    The component in its simplest form is ready. It can be invoked in page codes using the structure:

    <? $APPLICATION->IncludeComponent(
    “dv:date.current”,
    “.default”,
    Array(
    ),
    false
    );?>

    Implementing the Component with Input Parameters

    Now, let us make it possible to add the component to a page from the visual editor and set up the date format through component parameters.

    In order to make our component appear in the visual editor we have to expand the component description file.

    .description.php:

    <? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die(); 
    $arComponentDescription = array(
    "NAME" => GetMessage("Current date"),
    "DESCRIPTION" => GetMessage(“Display current date"),
    "PATH" => array(
    "ID" => "dv_components",
    "CHILD" => array(
    "ID" => "curdate",
    "NAME" => "Current date"
    )
    ),
    "ICON" => "/images/icon.gif",
    );
    ?>

    We have added the PATH array description element in order to place the component in the component tree. Thus, our component will be shown in a separate folder. Alternatively, a component icon may be set, and it will be shown in the tree and in the visual editor.

    Let us take a closer look at the component settings. Assuming that the date template option will be set by a string, we create the file .parameters.php as follows:

    <? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
     $arComponentParameters = array(
    "GROUPS" => array(),
    “PARAMETERS” => array(
    “TEMPLATE_FOR_DATE” => array(
    “PARENT” => “BASE”,
    “NAME” => “Template for date”,
    “TYPE” => “STRING”,
    “MULTIPLE” => “N”,
    “DEFAULT” => “Y-m-d”,
    “REFRESH” => “Y”,
    ),
    ),
    );
    ?>

    And change the file containing the component logics so that it could use the parameter we set in component.php:

    <? if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    $arResult['DATE'] = date($arParams["TEMPLATE_FOR_DATE"]);
    $this->IncludeComponentTemplate();
    ?>

    So, What Have We Done?

    We have created a component in its simplest. We have not taken into account multiple languages, the option to create help for the component, and the option of component caching.

    The majority of custom components for Bitrix Framework are created by changing the components supplied with the products. That is why it is very important to review standard components before starting programming new ones. The task you are to work on most likely has already been resolved by the developers of Bitrix, Inc.

    Component Creation Example

    Let us consider an example of a component creation for messages to the administrator about an error.

    Using this component we can implement a functionality that would permit users to notify content managers about errors found on a website. The error will be sent as an email notice. User’s work algorithm with the component is very simple: on seeing an error on the website, the user highlights the text, presses Ctrl+Enter , and obtains the form:

    The user has to complete the form, and the message is sent to a responsible person.

    Creating a Mail Template

    Since an error notice will be sent by email, a new email template must be created.

    • Go to the page Settings > System settings > Email Events > Email event types.
    • Complete the form:

    • Go to the page Settings > System settings > Email Events >E-Mail templates.
    • Click Add template on the context panel to open template setup form.
    • Set up a template for the email event created:

    Component Creation

    Create a folder feedback.error in an own namespace with the following structure:

    • folder images
      • file feedback.gif
    • folder templates
      • folder .default
        • file script.js
        • file template.php
    • file .description.php
    • file .parameters.php
    • file component.php

    The file feedback.gif is the icon that will be shown in the visual editor.

    The code of the script.js file is as follows:

    BX.bind(document, "keypress", SendError);
    
    function SendError(event, formElem)
    {
    		event = event || window.event;
    
    		if((event.ctrlKey) && ((event.keyCode == 0xA)||(event.keyCode == 0xD)))
    		{
    			var Dialog = new BX.CDialog({
    								title: "An error is found on the website!!",
    								head: "Error description",
    								content: 	'<form method="POST" id="help_form">
    											<textarea name="error_desc" style="height: 78px; width: 374px;"></textarea>
    											<input type="hidden" name="error_message"value="'+getSelectedText()+'">
    											<input type="hidden" name="error_url" value="'+window.location+'">
    											<input type="hidden" name="error_referer" value="'+document.referrer+'">
    											<input type="hidden" name="error_useragent" value="'+navigator.userAgent+'">
    											<input type="hidden" name="sessid" value="'+BX.bitrix_sessid()+'"></form>',
    								resizable: false,
    								height: '198',
    								width: '400'});
    
    			Dialog.SetButtons([
                {
                    'title': 'Send',
    				'id': 'action_send',
    				'name': 'action_send',
                    'action': function(){
    					BX.ajax.submit(BX("help_form"));
                        this.parentWindow.Close();
                    }
                },
    			{
                    'title': 'Cancel',
    				'id': 'cancel',
    				'name': 'cancel',
                    'action': function(){
                        this.parentWindow.Close();
                    }
                }
    			]);
    			Dialog.Show();
    		}
    }
    function getSelectedText(){
      if (window.getSelection){
        txt = window.getSelection();
      }
      else if (document.getSelection) {
        txt = document.getSelection();
      }
      else if (document.selection){
        txt = document.selection.createRange().text;
      }
      else return;
      return txt;
    }
    

    The code of the template.php file is as follows:

    <?
    CUtil::InitJSCore(array('window', 'ajax'));
    ?>

    The code of the .description.php file is as follows:

    <?
    if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    $arComponentDescription = array(
        "NAME" => "Send Error",
        "DESCRIPTION" => "Send Error",
        "ICON" => "/images/feedback.gif",
        "PATH" => array(
            "ID" => "utility",
        ),
    );
    ?>

    The code of the .parameters.php file is as follows:

    <?
    if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
    
    $arComponentParameters = array();
    ?>

    The code of the component.php file is as follows:

    <?
    if (check_bitrix_sessid() && $_SERVER['REQUEST_METHOD'] == "POST" && !empty($_REQUEST["error_message"]) && !empty($_REQUEST["error_url"]))
    {
    	$arMailFields = Array();
    	$arMailFields["ERROR_MESSAGE"] = trim ($_REQUEST["error_message"]);
    	$arMailFields["ERROR_DESCRIPTION"] = trim ($_REQUEST["error_url"]);
    	$arMailFields["ERROR_URL"] = $_REQUEST["error_desc"];
    	$arMailFields["ERROR_REFERER"] = $_REQUEST["error_referer"];
    	$arMailFields["ERROR_USERAGENT"] = $_REQUEST["error_useragent"];
    
    	CEvent::Send("BX", SITE_ID, $arMailFields);
    }
    $this->IncludeComponentTemplate();
    ?>

    Now let us take a closer look at the contents of the file feedback.errortemplates.defaultscript.js which is of interest for developers.

    Declaration of the handler function that is displayed onto the <body>:

    function SendError(event, formElem)

    Ctrl+Enter wait:

    if((event.ctrlKey) && ((event.keyCode == 0xA)||(event.keyCode == 0xD)))

    Setting up the parameters of a new window and its contents:

    var Dialog = new BX.CDialog({
                title: "An error is found on the website!!",
                head: "Error description",
                content:    '<form method="POST" id="help_form" action="/bitrix/templates/.default/send_error.php">
                                     <textarea name="error_desc" style="height: 78px; width: 374px;"></textarea>
                                     <input type="hidden" name="error_message"value="'+getSelectedText()+'">
                                     <input type="hidden" name="error_url" value="'+window.location+'">
                                     <input type="hidden" name="sessid" value="'+BX.bitrix_sessid()+'"></form>',
                resizable: false,
                height: '198',
                width: '400'});

    Determining a set of buttons:

    Dialog.SetButtons([
    {
       'title': 'Send',
       'id': 'action_send',
       'name': 'action_send',
       'action': function(){
          BX.ajax.submit(BX("help_form"));
          this.parentWindow.Close();
       }
    },
    {
       'title': 'Cancel',
       'id': 'cancel',
       'name': 'cancel',
       'action': function(){
          this.parentWindow.Close();
       }
    },
    ]);

    Window opening:

    Dialog.Show();

    The function getSelectedText() receives the text marked with the mouse. And then the letter is sent in the text of the file component.php:

    if (check_bitrix_sessid() && $_SERVER['REQUEST_METHOD'] == "POST" && !empty($_REQUEST["error_message"]) && !empty($_REQUEST["error_url"]))
    {
       $arMailFields = Array();
       $arMailFields["ERROR_MESSAGE"] = trim ($_REQUEST["error_message"]);
       $arMailFields["ERROR_DESCRIPTION"] = trim ($_REQUEST["error_desc"]);
       $arMailFields["ERROR_URL"] = trim ($_REQUEST["error_url"]);
       CEvent::Send("BX", SITE_ID, $arMailFields);
    };

    TinyMCE Visual Editor Integration Component

    Let us create a component integrating the popular editor TinyMCE into Bitrix Framework.

    Download the latest version of the editor from the manufacturer’s website.

    In an own namespace, set up a structure of folders and files:

    • /bitrix/components/tools/;
      • /bitrix/components/tools/editor.tiny.mce/;
        • /bitrix/components/tools/editor.tiny.mce/templates/;
          • /bitrix/components/tools/editor.tiny.mce/templates/.default/;
        • /bitrix/components/tools/editor.tiny.mce/tiny_mce/ — a folder for editor installation package;
        • component.php — component logics;
        • .parameters.php — a file to describe input parameters.

    Copy the downloaded installation package into the folder /tiny_mce.

    Add the following code in the file component.php:

    <?if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    	$APPLICATION->AddHeadScript($this->__path .'/tiny_mce/tiny_mce.js');
    
    	$sNameTextAria   =  (isset($arParams['TEXTARIA_NAME'])   == false) ? 'content'   : $arParams['TEXTARIA_NAME'];
    	$sIdTextAria   	 =  (isset($arParams['TEXTARIA_ID'])     == false) ? '' 		 : $arParams['TEXTARIA_ID'];
                 if ('' == trim($sIdTextAria))
                 $sIdTextAria = 'content';
    	$sEditorID 		 =  (isset($arParams['INIT_ID'])  	     == false) ? 'textareas' : $arParams['INIT_ID'];
    	$iTextariaWidth  =  (isset($arParams['TEXTARIA_WIDTH'])  == false) ? '100%'      : $arParams['TEXTARIA_WIDTH'];
    	$iTextariaHeight =  (isset($arParams['TEXTARIA_HEIGHT']) == false) ? '300'       : $arParams['TEXTARIA_HEIGHT'];
    	$sText 			 =  (isset($arParams['TEXT']) 			 == false) ? ''       	 : $arParams['TEXT'];
    	?>
    
    <script type="text/javascript">
    
    <? 
    if($arParams['TYPE_EDITOR'] == 'TYPE_1')
    {
    	?>
    	tinyMCE.init(
    		{
    			language : 'ru',
    			mode 	 : "textareas",
    			//elements : "<?=$sEditorID?>",
    			editor_selector : "<?=$sEditorID?>",
    			theme    : "advanced",
    			plugins  : "safari, spellchecker, upload.images.komka, wordcount, fullscreen",
    			theme_advanced_buttons1 : "formatselect,fontselect,fontsizeselect,bold,italic,underline,link,justifyleft,justifycenter,
                                           justifyright,pasteword,pastetext,images,|,bullist,numlist,|,undo,redo,|,spellchecker,fullscreen",
    			theme_advanced_buttons2 : "",
    			theme_advanced_buttons3 : "",
    			theme_advanced_toolbar_location   : "top",
    			theme_advanced_toolbar_align      : "left",
    			theme_advanced_statusbar_location : "bottom",
    			theme_advanced_resizing           : false,
    			content_css                       : "<?=$this->__path?>/example.css",
    			height : "<?=$iTextariaHeight?>",
    			spellchecker_languages : '+Русский=ru,English=en',
    			spellchecker_word_separator_chars : '\s!"#$%&()*+,-./:;<=>?@[]^_{|}'
    		}
    	);
    	<? 
    }
    elseif($arParams['TYPE_EDITOR'] == 'TYPE_2')
    {
    	?>
    		tinyMCE.init({
    				language : 'ru',
    				mode 	 : "textareas",
    				editor_selector : "<?=$sEditorID?>",
    				theme    : "advanced",
    				plugins : "safari,spellchecker,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,
                               iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,
                               fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,imagemanager,filemanager",
    				theme_advanced_buttons1 : "save,newdocument,|,bold,italic,underline,strikethrough,|,justifyleft,
                                               justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect",
    				theme_advanced_buttons2 : "cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,
                                               numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,
                                               insertdate,inserttime,preview,|,forecolor,backcolor",
    				theme_advanced_buttons3 : "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,
                                               emotions,iespell,media,advhr,|,print,|,ltr,rtl,|,fullscreen",
    				theme_advanced_buttons4 : "insertlayer,moveforward,movebackward,absolute,|,styleprops,
                                               spellchecker,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,blockquote,
                                               pagebreak,|,insertfile,insertimage",
    				theme_advanced_toolbar_location : "top",
    				theme_advanced_toolbar_align : "left",
    				theme_advanced_statusbar_location : "bottom",
    				theme_advanced_resizing : true,
    				content_css : "<?=$this->__path?>/example.css",
    				height : "<?=$iTextariaHeight?>",
    				template_external_list_url : "js/template_list.js",
    				external_link_list_url : "js/link_list.js",
    				external_image_list_url : "js/image_list.js",
    				media_external_list_url : "js/media_list.js",
    				template_replace_values : {username : "Some User", staffid : "991234"}
    			}
    		);
    	<? 
    }
    ?>
    
    </script>
    <textarea id="<?=$sIdTextAria?>" class="<?=$sEditorID?>"  name="<?=$sNameTextAria?>" style="width:<?=$iTextariaWidth?>"><?=$sText?></textarea>
    <? $this->IncludeComponentTemplate();?>
    

    The file .parameters.php

    <? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true) die();
    
    $arComponentParameters = array(
    	"PARAMETERS" => array(
    		"TYPE_EDITOR" => Array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Editor mode",
    	         "TYPE" => "LIST",
    	         "VALUES" => array('TYPE_1' => 'Simplified editor', 'TYPE_2' => 'Full editor'),
    		),
    
    		'INIT_ID' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Editor ID (unique)",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => '',
    		),
    		
    		'TEXT' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Content to be inserted to the editor",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => $_POST['content'],
    		),
    		
    		'TEXTARIA_NAME' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "TEXTARIA field name",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => 'content',
    		),
    		
    		'TEXTARIA_ID' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "TEXTARIA ID field",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => 'content',
    		),
    		
    		'TEXTARIA_WIDTH' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Editor width",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => '100%',
    		),
    
    		'TEXTARIA_HEIGHT' => array(
    	         "PARENT" => "SETTINGS",
    	         "NAME" => "Editor height",
    	         "TYPE" => "STRING",
    	         "DEFAULT" => '300',
    		),
    	)
    );
    /*
    				array(
    					'TEXT' 			  => $_POST['content'], # contents, in html
    					'TEXTARIA_NAME'   => 'content',   		# field name
    					'TEXTARIA_ID'     => 'content',   		# field ID
    					'INIT_ID'	      => 'textareas', 		# editor ID
    					'TEXTARIA_WIDTH'  => '100%', 	  		
    					'TEXTARIA_HEIGHT' => '300' 		 
    				)
    */
    ?>

    Several important points in the code of the component.php must be clarified. Connection of the editor proper:

    <?  $APPLICATION->AddHeadScript($this->__path .’/tiny_mce/tiny_mce.js’); ?>

    Connection of styles put separately in the component folder for convenience; this setting is made in js in the initialization code:

    content_css : ‘<?=$this->__path?>/example.css’,

    Attention! If there are two or more editors on a page we have to identify them by the class name editor_selector : ‘<?=$sEditorID?>’.

    The text area proper for which all the work is being done:

    <textarea id=’<?=$sIdTextAria?>’  name=’<?=$sNameTextAria?>’ style=’width:<?=$iTextariaWidth?>’><?=$sText?></textarea>

    How to Use

    We get connected as follows:

    <? echo $_POST['content'] ?>
    <? echo $_POST['content2'] ?>
    <form action="" method="post" name="">
    <? $APPLICATION->IncludeComponent("tools:editor.tiny.mce", ".default", array(
    "TEXT" => $_POST["content"], // contents from the request which must be inserted
    "TEXTARIA_NAME" => "content", // field name
    "TEXTARIA_ID" => "content",         // field id
    "TEXTARIA_WIDTH" => "100%",  // width
    "TEXTARIA_HEIGHT" => "300",    // height
     
    "INIT_ID" => "ID" // ID of the proper editor
    ),
    false
    );
    ?>
    <input value="submit" name="sub" type="submit" />
    </form>

    Caching in own components

    Note: In Bitrix Framework caching time is recorded in seconds.

    What is the use of caching in own components

    Making direct database queries through API, obtaining information, formatting it in the component template, and displaying it to the user may seem to be the simplest solution.

    However, the issue is the performance of the web project in case many users work with it at the same time. If it takes the component 0.1 sec. to respond without cache and executing, say, 100 database queries, then if 100 users work simultaneously the database server load will increase, and so will the component response time up to, for example, 5-10 sec.

    An equally important point to keep in mind is the speed of response of a component when receiving data from cache. If it takes the component 2 sec. to respond to each user without cache, then, with cache, it will take the component 2 sec. to respond to one user and 0.1 sec. – to the remaining 100 users over the next, say, 30 minutes.

    When using cache in own components 2.0:

    • Web project performance and load tolerance drastically increase because the database load drops to a minimum, and the web solution will be able to serve, for example, not just 50,000 users per day but 1,000,000 and more.
    • Web pages are loaded to the user’s browser much faster (in tenths of a second) because their structural information is saved on the server and is not taken from the database.

    Caching Time

    The time required for caching depends on the type of caching. If the caching Auto+Management is used, the information will be supplied from cache until it is changed in the database and cache resets automatically. The caching time for this mode must be long, e.g., 1 year.

    If Auto caching is used, it is recommended to set up the longest cache interval permitted with due regard to business logic. The caching time for this mode depends on the frequency of the information update. For some components, the time must be set to 24 hours. For frequently updated components, a controlled cache is recommended; alternatively, a value of, for example, 10 minutes shall be set.

    Embedded Cache Support

    2.0 components come with an embedded support of a typical cache algorithm. The component structure using embedded cache support will be similar to:

    // Verification and initialization of input parameters
    if ($arParams["ID"] <= 0)
       $arParams["ID"] = 10;
    
    // If no valid cache is available (i.e. data must be requested
    // and valid cache must be created)
    if ($this->StartResultCache())
    {
       // Data query and completion of $arResult
       $arResult = array(
          "ID" => rand(1, 100)
       );
    
       for ($i = 0; $i < 5; $i++)
          $arResult["FIELDS"][] = rand(1, 100);
    
       // If any condition is met, there is no need
       // to cache data
       if ($arParams["ID"] < 10)
          $this->AbortResultCache();
    
       // Connect output template
       $this->IncludeComponentTemplate();
    }
    
    // Set up page header using deferred
    // function
    $APPLICATION->SetTitle($arResult["ID"]); 

    Comments to the Code

    The method CBitrixComponent::StartResultCache()has the following description: bool $this->StartResultCache($cacheTime = False, $additionalCacheID = False, $cachePath = False)

    where:

    • $cacheTime — caching time (if FalseIntVal($arParams["CACHE_TIME"]) is used for substitution);
    • $additionalCacheID — additional parameters on which cache depends, apart from the current website’s SITE_ID,component name, file path, and input parameters;
    • $cachePath — path to cache file (if False"/".SITE_ID. is used for substitution).

    If a valid cache is available, the method displays its contents, completes $arResult, and returns False. If there is no valid cache, the method returns True.

    If cache depends not only on the website, input parameters, component name, and a path to the current website, but also on other parameters, these parameters must be sent to the method as a second parameter in the form of a string. For example, if cache also depends on the user groups to which a current visitor belongs, the condition should be written as follows:

    if ($this->StartResultCache(false, $USER->GetGroups()))
    {
       // There is no valid cache. We have to choose data from the base to $arResult
    }

    If following data selection (if no valid cache is available), it becomes evident that there is no need to cache data, the method
    $this->AbortResultCache(); must be invoked. E.g., if it turns out that there is no news with such ID, caching should be interrupted by displaying a message that there is no such news. If caching is not interrupted, some intruders may clog up with cache all disc space allocated to the website by invoking the page with arbitrary (including non-existent) IDs.

    The method $this->IncludeComponentTemplate(); connects the component template and saves to the cache file the output and array of results $arResult. All changes to $arResult and output will not be saved to cache after the template connection method is invoked.

    If during the component code execution we have not entered into the body of the condition if ($this->StartResultCache()), it means that there is a valid cache for this component, page, and input parameters. After this method is invoked, HTML from cache is displayed and we have a completed array $arResult. Here, we can do something. For example, set up the page header using deferred functions.

    If during the execution of certain conditions the component cache must be cleared (e.g., the component “knows” that the data have been changed), the method $this->ClearResultCache($additionalCacheID = False, $cachePath = False) can be used. Parameters of this method are consistent with the same-name parameters of the StartResultCache method.

    Complex Caching

    If the component requires any special caching that cannot be executed using embedded cache support, the standard class CPHPCache can be used. The component structure using the class CPHPCache will be more or less as follows:

    // Verification and initialization of the input parameters
    if ($arParams["ID"] <= 0)
       $arParams["ID"] = 10;
    
    $arParams["CACHE_TIME"] = IntVal($arParams["CACHE_TIME"]);
    $CACHE_ID = SITE_ID."|".$APPLICATION->GetCurPage()."|";
    // Cache only depends on prepared parameters without "~"
    foreach ($this->arParams as $k => $v)
       if (strncmp("~", $k, 1))
          $CACHE_ID .= ",".$k."=".$v;
    $CACHE_ID .= "|".$USER->GetGroups();
    
    $cache = new CPageCache;
    if ($cache->StartDataCache($arParams["CACHE_TIME"], $CACHE_ID, "/".SITE_ID.$this->GetRelativePath()))
    {
       // Request of data and formation of the array $arResult
       $arResult = array("a" => 1, "b" => 2);
    
       // Component template connection
       $this->IncludeComponentTemplate();
    
       $templateCachedData = $this->GetTemplateCachedData();
    
       $cache->EndDataCache(
          array(
             "arResult" => $arResult,
        "templateCachedData" => $templateCachedData
          )
       );
    }
    else
    {
       extract($cache->GetVars());
       $this->SetTemplateCachedData($templateCachedData);
    }

    Comments to the Code

    Cache must only depend on the prepared parameters, i.e. on the parameters that are properly initialized and reduced to a required type (e.g., using IntVal()) etc. The array $arParams contains both prepared parameters and initial parameters (with the same key but with the prefix «~»). If the cache depends on unprepared parameters, intruders will be able to clog up all the disk space allocated to the website with cache by calling a page with IDs equal to «8a», «8b», … (which give 8 after IntVal()).

    The method $this->IncludeComponentTemplate() does not request data from the database. However, it is better to also include it into the cached area because this method performs certain disk operations.

    Before calling the method for cache completion and cache saving (the method EndDataCache), it is necessary to request parameters from the template. These parameters must be used even if the template itself is not connected and data are taken from cache. In the current version, such parameters include css styles of the template that are connected by deferred functions and thus do not go to cache. The structure of the data returned by the template is not documented and has no meaning for the component. These are just data that must be placed to cache and then taken from cache and returned to the template.

    In order to return to the template, the data previously saved in cache according to the template’s “wish”, the methods $this->SetTemplateCachedData($templateCachedData); or CBitrixComponentTemplate::ApplyCachedData($templateCachedData); can be used. One of these methods must be invoked in the component area which is executed in case there is a valid cache available. It must receive (in parameters) the data that the template “asked” to save.

    Some Recommendations

    If the component uses standard caching but no template is connected (because it is not necessary), the following shall be used:

    $this->EndResultCache();

    A possible solution where the component template is removed from the cached area. Other components can be connected in the template itself.

    $cache_id = serialize(array($arParams, ($arParams['CACHE_GROUPS']==='N'? false: $USER->GetGroups()))); 
    $obCache = new CPHPCache; 
    if ($obCache->InitCache($arParams['CACHE_TIME'], $cache_id, '/')) 
    { 
       $vars = $obCache->GetVars(); 
       $arResult = $vars['arResult']; 
    } 
    elseif ($obCache->StartDataCache()) 
    { 
    
       // code
    
       $obCache->EndDataCache(array( 
          'arResult' => $arResult, 
       )); 
    } 
    

    If the code is written properly and template.php нcontains no “heavy” code, this option might work well enough.

    CUSTOM Parameter Type

    The CUSTOM parameter type gives total freedom of customization to the developer. For example, there is a system or a third party component. Depending on the template there is a need to add specific settings to the component.

    It can be done as follows:

    Example Description
    JS_FILE The file containing JS code that is responsible for displaying the custom option
    JS_EVENT Callback function that will be called after loading JS_FILE
    JS_DATA Additional data transmitted to JS_EVENT

    Example JS_DATA:

    {
    data:JS_DATA, //JS_DATA from .parameters.php
    oCont: td,    /* the container where custom control panel can be located with the parameter */
    oInput: input,//input in which the parameter value will be transmitted to server during saving
    popertyID:"MAP_DATA",//parameter name
    propertyParams: { /*...*/ },//The object with the same contents as the parameter array in .parameters.php
    fChange:function(){ /*...*/ },//callback for call during parameter change
    getElements:function(){ /*...*/ }//returns the object with all parameters of the component
    }

    Implementation in a Standard Component

    Let us consider an example of using CUSTOM parameter type in the standard component map.google.view.

    We can see the following in the file .parameters.php:

    $arComponentParameters = array(
    //...
    'MAP_DATA' => array(
                'NAME' => GetMessage('MYMS_PARAM_DATA'),
                'TYPE' => 'CUSTOM',
                'JS_FILE' => '/bitrix/components/bitrix/map.google.view/settings/settings.js',
                'JS_EVENT' => 'OnGoogleMapSettingsEdit',
                'JS_DATA' => LANGUAGE_ID.'||'.GetMessage('MYMS_PARAM_DATA_SET'),
                'DEFAULT' => serialize(array(
                    'google_lat' => GetMessage('MYMS_PARAM_DATA_DEFAULT_LAT'),
                    'google_lon' => GetMessage('MYMS_PARAM_DATA_DEFAULT_LON'),
                    'google_scale' => 13
                )),
                'PARENT' => 'BASE',
            )
    //...
    );

    In the file /bitrix/components/bitrix/map.google.view/settings/settings.js:

    function JCEditorOpener(arParams)
    {
        this.jsOptions = arParams.data.split('||');
        this.arParams = arParams;
    
        var obButton = document.createElement('BUTTON');//creating a button
        this.arParams.oCont.appendChild(obButton);// adding to container
       
        obButton.innerHTML = this.jsOptions[1];//text from JS_DATA
       
        obButton.onclick = BX.delegate(this.btnClick, this);//specify callback functions
        this.saveData = BX.delegate(this.__saveData, this);
    }

    Clicking the button opens the dialog generated in /bitrix/components/bitrix/map.google.view/settings/settings.php. The current value of MAP_DTA is transmitted in the query to settings.php.

    Header

    obJSPopup->ShowTitlebar();
    $obJSPopup->StartDescription('bx-edit-menu');
    <p><b><? echo GetMessage('MYMV_SET_POPUP_WINDOW_TITLE')?></b></p>
    <p class="note"><? echo GetMessage('MYMV_SET_POPUP_WINDOW_DESCRIPTION')?></p>

    Content Block

    $obJSPopup->StartContent();

    Buttons Block

    $obJSPopup->StartButtons();

    Save Button

    <input type="submit" value="<?echo GetMessage('MYMV_SET_SUBMIT')?/>" onclick="return jsGoogleCE.__saveChanges();"/>
    $obJSPopup->ShowStandardButtons(array('cancel'));//cancel button
    $obJSPopup->EndButtons();

    В __saveChanges() the data are serialized in a string and written into oInput. The serialization function in js to the php format can be looked up in bitrix/components/bitrix/map.google.view/settings/settings_lod.js. In the component the de-serialization is performed from $arParam[~MAP_DATA].

    Localization

    The language file is located in lang/en/.parameters.php. When using the CUSTOM parameter type, do not forget to add messages to this file.

    More examples

    How Can CAPTCHA Be Displayed in an Own Component?

    An example of using CAPTCHA on a page.

    <?
    require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
    $APPLICATION->SetTitle("Title");
    ?>
    <?
    if (isset($submit)) {
      echo 'сабмит прошел...<br>';
      echo $myname.'<br>';
      echo $cap.'<br>';
       if (!$GLOBALS["APPLICATION"]->CaptchaCheckCode($cap, $captcha_sid))
          {
             $error=true;
             echo 'error captcha
    '; } } ?> <form id="linkForm" name="mailForm" action="test.php" method="post"> <table cellspacing="3" cellpadding="0" width="100%" bgcolor="#eeeeee" border="0"> <tbody> <tr><td valign="top" align="right">Name *</td><td><input size="40" value="" name="myname" /></td></tr> <tr><td valign="top" align="right">CAPTCHA *</td><td><? $capCode = $GLOBALS["APPLICATION"]->CaptchaGetCode(); ?> <input type="hidden" name="captcha_sid" value="<?= htmlspecialchars($capCode) ?>"> <img src="/bitrix/tools/captcha.php?captcha_sid=<?= htmlspecialchars($capCode) ?>" width="180" height="40"><br> <input size="40" value="" name="cap" /></td></tr> <tr><td valign="top" align="right"> </td> <td><input type="submit" value="Sent" name="submit" />  <input type="reset" value="Сбросить" name="Reset" /></td></tr> </tbody> </table> </form><?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>

    How to Make a Component Invisible on the Home Page and Visible on Other Pages?

    Solution:

    if ($curPage = $APPLICATION->GetCurPage())
    {
       if (($curPage != "/index.php"))
            {
                ....
            }
    } 

    Errors When Working with Components

    Cannot Find Component Access Code

    This error is quite common when you try to edit component parameters in the page editing mode. Although the code contains the line $APPLICATION->IncludeComponent() (component access), sometimes the error Cannot find component access code appears anyway. Unfortunately, there is no one-size-fits-all solution for this problem.

    Possible solutions:

    • Include two strings to .htaccess:

      For non-UTF:

          php_value mbstring.func_overload 0
          php_value mbstring.internal_encoding latin1

      For UTF:

          php_value mbstring.func_overload 2
          php_value mbstring.internal_encoding UTF-8
    • The error possibly appears due to the incorrect placement of html tags (e.g., one of the tags is closed in a wrong place);
    • Remove all of the html comments from the page;
    • Frame the component access code with the symbols <? ?> (thus separating it from another php code);
    • Place the structure <?/**/?> before component access;
    • Remove several similar components close to the component that fails to work.

    Events

    Sometimes, there is a need to affect an API function performance. But, if you change it, these changes will be lost upon a next update. For such cases, an event system was developed. When executing some API functions, other specific functions, the so-called event handlers are called in determined points.

    Note: Event handlers must be treated carefully. Due to a significant event model-related content available in Bitrix Framework, elusive errors in developer code can occur. They can significantly inhibit developer’s work efficiency.

    The specific event handler functions must be called at individual points (and upon which triggered events) — must be indicated by calling the function, that registers the handlers. Presently, there are two available: BitrixMainEventManager::addEventHandler и BitrixMainEventManager::registerEventHandler. The set of events for each module is described in the documentation dedicated to each module. Here’s, for example, a link to main module events.

    registerEventHandler — function for registering the handlers, located in modules and used for interaction between system modules. This function must be called once when installing the module, after that, handler function will be automatically called at a specific moment, preliminarily connecting the module itself.

    Deleted via BitrixMainEventManager::unRegisterEventHandler when deleting the module.

    Example

    $eventManager = BitrixMainEventManager::getInstance();
    
    // compression module handler functions are connected twice - at the start and at the end of each page
    $eventManager->registerEventHandler("main", "OnPageStart", "compression", "CCompress", "OnPageStart", 1);
    $eventManager->registerEventHandler("main", "OnAfterEpilog", "compression", "CCompress", "OnAfterEpilog");
    
    // module installer registers an empty handler
    // advertisement module will be directly connecting at each page when the event OnBeforeProlog triggers
    // allows to execute its API functions without preliminary connecting in the page body
    $eventManager->registerEventHandler("main", "OnBeforeProlog", "advertising");

    Each module can provide interface to other modules for indirect communication — in a form of set of events. Such interaction allows making modules maximally independent from each other. Module knows nothing about the other module operation, but will interact with it via event interface.

    AddEventHandler — function is designed to register arbitrary handlers that are not located in modules. It must be called before event triggering at the pages, where event handler is processed. For example, when event must be processed on all pages, where it occurs, the function can be called in /bitrix/php_interface/init.php.

    Example

    // handler registering in /bitrix/php_interface/init.php
    $eventManager = BitrixMainEventManager::getInstance();
    $eventManager->addEventHandlerCompatible("main", "OnBeforeUserLogin", "MyOnBeforeUserLoginHandler");
    
    function MyOnBeforeUserLoginHandler($arFields)
    {
       if(strtolower($arFields["LOGIN"])=="guest")
       {
           global $APPLICATION;
           $APPLICATION->throwException("User with Guest login name cannot be authorized.");
           return false;
       }
    }

    Anonymous functions are suitable for «quick and painful solution» approach. To avoid overloading the code by various functions or classes, proceed as follows:

    use BitrixMainEventManager;
    
    $eventManager = EventManager::getInstance();
    
    $eventManager->addEventHandlerCompatible("main", "OnAfterUserLogin", function(&$fields) {
    // Мой код
    });

    Differences in functions

    Actions that you will perform via the events must be physically written and must be registered to be performing upon triggering of necessary events.

    registerEventHandler registers in the database, and AddEventHandler registers in the init.php file. It means that using the first function leads to additional load on the database. It is best used in situations, where executed actions must be registered once and for all specifically in database.

    Usually, events are divided into those occurring at a specific location and designation into the following groups:

    • Intended for cancelling further method execution. For example, event OnBeforeUserDelete allows cancelling user deleting upon specified conditions (available associated objects of critical significance), event OnBeforeUserLogin — restrict authorization for a user;
    • Allowed to be executed in specific methods, upon completed method execution. For example, OnAfterUserLogin — after the login name and password check, event OnUserDelete — before direct deletion of user from database; allows deleting associated objects;
    • Occurring during executing a page, to enable custom code into specific locations on the page. For example, OnBeforeProlog, (see details at page execution sequence).

    Note for developers:

    When it is required to expand an event log with new event types (for example, upon actions with iblocks), use the event handler OnEventLogGetAuditTypes. Array that it will return is added to the standard array in the admin section.

    List and description of events, available to individual modules is located in Documentation for developers.

    Events

    Event system has been changed for the core D7. Requirements for data belonging to a code triggering the event are leveled down. An example of an event sending:

    $event = new BitrixMainEvent("main", "OnPageStart");
    $event->send();

    If necessary, for a sending party there is a possibility to receive the result of an event handled by accepting parties.

    foreach ($event->getResults() as $eventResult)
    {
        switch($eventResult->getType())
        {
            case BitrixMainEventResult::ERROR:
                // error handling
                break;
            case BitrixMainEventResult::SUCCESS:
                // successful 
                $handlerRes = $eventResult->getParameters(); // getting the result, returned by the event handler
                break;
            case BitrixMainEventResult::UNDEFINED:
                /* the handler returned undefined result instead of a BitrixMainEventResult class object 
                its result is still available via getParameters
                */
                break;
        }
    }

    In order to reduce the code quantity BitrixMainEvent class successors may be created for specific event types. For example, BitrixMainEntityEvent makes it more convenient to send events connected with the modification of entities.

    Modules

    Bitrix Framework has a module structure. Each module is responsible for managing certain website elements and parameters – website information contents and structure, forums, advertising, mailings, distribution of rights among user groups, collection of visiting statistics, evaluation of advertising campaign efficiency, etc.

    Module — is a data model and API for access to these data. Static methods of module classes can be accessed in components, templates, and other modules. In addition, class instances can be created inside Bitrix Framework.

    System modules mostly work independently from one another. However, in a number of cases the functionality of certain modules depends on the capabilities of other modules. For example:

    • The module Commercial Catalog expands the capacity of the Information Block module and permits you to set up goods prices depending on various conditions and apply a surcharge and discounts to goods, etc.
    • The Workflow module permits to organize consecutive team work with contents of the modules Information Block and Site Explorer.

    After the system is installed, the list of modules used can be viewed on the page Module Management (Settings > System settings > Modules) in the administrative section of the system:

    It is recommended to delete unused modules in order to save disc space. There is always a possibility to reinstall any module if necessary. During the deinstallation of certain modules, the system offers to save the data accumulated by the module (module tables). If you intend to use these data later on, do not forget to mark up this option when deleting a module.

    The level of users’ rights to access system modules is managed separately for each module on its setup page. The general parameters of module operation are managed on the same page.

    The setup page of a specific module may have a different amount of tabs and fields, depending on module functionality. It can be accessed as follows:

    • Using the administrative menu: Settings > System settings > Module settings > module_name;
    • Using the button Settings, located on the administrative panel. This button permits access to the settings of the module which pages (forms) are currently open in the main work area.

    Note: Before using the API of a module it is necessary to make sure it is installed and connect it using the following structure:

    <?
       if(CModule::IncludeModule("******"))
       { 
    	//module functions and classes can be used here
       } 
       ?>

    where **** — module identifier.

    Modules and Components

    Modules in Bitrix Framework represent models and controllers of a low level (in terms of MVC) and components – controllers of high level which include representations based on the hierarchy of the website file structure. As a rule, all functional capacity of any website is implemented using standard modules, but components have to be customized (or own components have to be written) in order to generate and display pages; these components have to be connected on the relevant website pages.

    Module Development

    Bitrix Framework permits developing user modules.

    File Structure

    Module files are located in the module ID folder. Folder structure:

    • /bitrix/modules/module ID/ — module root catalogue
    • /admin/ — catalogue with administrative scripts of the module;
      • menu.php — file containing administrative menu of the module;
    • /classes/ — scripts with module classes;
      • /general/ — module classes that do not depend on the database used;
      • /mysql/ — module classes intended for work only with MySQL;
      • /mssql/ — module classes intended for work only with MS SQL;
      • oracle/ — module class intended to work only with Oracle;
    • /lang/language ID/ — catalogue with language files of module scripts;
    • lib/ — catalogue with files (API: classes, logic) for the new core D7 (may not be available when module does not have its own methods);
    • /install/ — catalog with files used for module installation and deinstallation;
      • /admin/ — catalog with scripts connecting administrative scripts of the module (access scripts);
      • /js/ — catalog with js scripts of the module; these are copied to /bitrix/js/module_ID/;
      • /db/ — catalogue with SQL scripts for installation/deinstallation of database;
        • /mysql/ — SQL scripts for installation/deinstallation of tables in MySQL;
        • /mssql/ — SQL scripts for installation/deinstallation of tables in MS SQL;
        • oracle/ — SQL scripts for installation/deinstallation of tables in Oracle;
      • /images/ — catalog with images used by the module; after the module is installed they must be copied into the catalog /bitrix/images/module ID/;
      • /templates/ — catalog with components 1.0 of the module. (The catalog is saved only to ensure version compatibility);
        • /module ID/ — catalog containing main files of the component;
        • /lang/language ID/module ID/ — language files of the module components are located in this catalog;
      • /components/namespace/component name/ — catalog containing components 2.0 of the module;
      • /themes/module_name/ — contains css and images for the styles of the administrative panel, if the module needs them (outdated, used before version 12.0);
      • /panel/module_name/ — contains css and images for the styles of the administrative panel, if the module needs them.
      • index.php — file containing module description;
      • version.php — file with module version. Version cannot be null.
    • include.php — this file is connected when the module is connected in a code; all files containing libraries of functions and module classes must be located here;
    • default_option.php — contains an array named $ID module_default_option where default values are set for module parameters.

      Note: If the module name contains a dot (e.g., mycompany.forum) in the name of a variable, the dot will be replaced with the underscore character.

    • options.php — this file is connected on the setup page of module parameters in the administrative menu Settings;
    • prolog.php — this file can be connected in all administrative scripts of the module. Normally the constant ADMIN_MODULE_NAME (module identifier) which is used in the control panel is determined here.
    • .settings.php — module settings file, describing module settings that can be read via BitrixMainConfigConfiguration::getInstance($module).

    Each module must be properly described in the system. That way, the system will “know” how to work with such a module. Improperly described modules can cause the complete or partial unserviceability of the system (e.g., the update system might fail).

    The main file used by the system in order to manipulate the module is /bitrix/modules/module ID/install/index.php. (in this case, module ID — full code of partner module that is set in the following format: partner_code.module_code.) The main purpose of this file is the placement of a class in it with a name that coincides with the module ID. (module ID is used here in the format partner_code.module_code, Because, period sign is unavailable for the class name.)

    Module parameters are available for change in the administrative interface on the page Module Settings (Settings > System settings > Module settings). When choosing a module on this page, the system connects the file /bitrix/modules/module ID/options.php intended for controlling module parameters, setting up rights to the module, etc.

    Module parameters are stored in the database.

    When receiving module parameters, a default value can be used that is set in the file /bitrix/modules/module ID/default_option.php. In this file, the array $ID module_default_option is defined which stores the default values.

    Administrative Scripts — are the scripts used by the module in the administrative part of the system. They must be located in the catalog /bitrix/modules/module ID/admin/.

    It must be taken into account that administrative scripts cannot be accessed directly in the browser (like any scripts of the /bitrix/modules/). That is why additional homonymous access scripts are used in order to access administrative scripts. Access scripts are located in the catalog /bitrix/admin/. As a rule, they only contain the connection of the administrative script of the same name:

    When installing the module, access scripts must be copied from the catalog /bitrix/modules/module ID/install/admin/ to the catalog /bitrix/admin/. Upon deinstallation of the module access scripts must be deleted from this catalog.

    Note: It must be taken into account that access scripts of all installed modules are located in the same catalog /bitrix/admin/; that is why it is recommended that their names start with a prefix characteristic only for the relevant module to avoid duplication.

    Each administrative script must have a defined constant ADMIN_MODULE_NAME, before connecting a visual part of administrative prologue. This constant is required to generate an icon above the page header. The constant is also required to jump quickly to the module settings when clicking the Settings button. It is usually set in prolog.php.

    Language files must be located in the catalog /bitrix/modules/module ID/lang/language ID/.
    The particularity of their location inside this catalog is that they must be located strictly following the same path from the catalog /bitrix/modules/module ID/ as the homonymous files in which they are connected. In this case, language files can only be connected using the function IncludeModuleLangFile.

    For example, for the script /bitrix/modules/module ID/admin/body/my_script.php the language file must be located here: /bitrix/modules/module ID/lang/language ID/admin/body/my_script.php.

    The menu of the administrative part is displayed by the standard function CMain::GetMenuHtmlEx.

    The menu template is stored in the file /bitrix/modules/main/interface/.left.menu_template.php

    The main file collecting menu options is /bitrix/modules/main/interface/.left.menu.php. Here, all the files contained in /bitrix/modules/module ID/admin/menu.php are examined. Each such file contains a definition of the array $aModuleMenuLinks containing the menu options of the relevant module. All of these arrays will be later unified into a standard array $arMenuSections that contains information about all the menu options.

    If you are writing your own module, you can use /bitrix/modules/ID_module/admin/menu.php to add arbitrary options to the administrative menu.

    Modules can interact among them in two ways: explicitly (by direct query) and inwardly (through event system).

    Event — is an arbitrary action at the time of execution of which (before or after) all the handlers of such an event are collected and executed one by one.

    The entity of an event permits making modules independent from one another to a maximum degree. The module “knows” nothing about the particulars of functioning of another module, but it can interact with it through the interface of events.

    The events operating procedure is as follows. The module initializing an event must perform the following operations at the place within the code where this event occurs:

    The perfect example of such an interaction is the interaction of system modules with the Search module. This module has no information about the data of other modules, their storage and processing particulars. It only provides an interface for data indexing. Any system model to be indexed registers a handler for the event OnReindex. upon installation. Each such handler, in its turn, returns data for the indexing to be used by the Search module for filling its base.

    The module is installed in the administrative interface on the page Settings > System settings > Modules by clicking the button Install. In this case, the method of the DoInstall class will be called with a name coinciding with the module ID. This class must be described in the file /bitrix/modules/ID модуля/install/index.php.

    The module is uninstalled by clicking the button Remove. In this case, the method of the DoUninstall class will be called with a name coinciding with the module ID. This class must be described in the file /bitrix/modules/module ID/install/index.php.

    During the module uninstallation, the following steps must be performed without fail:

    Creation of a new module or change of operation of a standard module is rarely required in Bitrix Framework since the majority of tasks are solved using components and their customization.

    Attention! Module customization is a modification of the system kernel with all the consequences. There is a risk that the system will become inoperable after an update or you may lose the right to refer to the technical support service.

    That is why module customization is an operation that is not recommended and almost prohibited. However, there is a technical possibility to do it, and in this chapter we will study an example of a simple customization.

    Attention! Prior to modifying module operation (i.e. modifying system kernel) you must be sure that there is no other way to resolve your task. All changes that you add will be eliminated upon the next update of the product, and you will have to make them again.

    Let us solve the task of downloading contacts from a corporate portal to Outlook 2003. Please remember that Bitrix24 Self-hosted in a standard package is intended for interaction with Outlook 2007.

    The file /bitrix/modules/intranet/classes/general/ws_contacts.php is responsible for forming and preparing data for Outlook.

    We solve the first problem using the function __getRow($arRes, $listName, &$last_change). Commenting on the strings of image attribute setup:

    We solve the second problem using the function GetListItemChangesSinceToken($listName, $viewFields = '', $query = '', $rowLimit = 0, $changeToken = ''). In the cycle while while ($arUser = $obUsers->NavNext()) we comment on all strings starting with $arUser['WORK_COMPANY']
    (i.e. where the value of this attribute changes).

    Note: In the function GetList the attribute diagram is formed. If before the line: return array('GetListResult' => $data); the array $data is displayed, we will have a chance to see the downloading diagram.

    Programming in Bitrix Framework is relatively easy. It is a normal PHP-based programming using API provided by Bitrix Framework.

    Before starting work in Bitrix Framework you have to understand the main rules that will help you to avoid many mistakes:

    There are some situations, when a specific code must be executed quickly, calling the Bitrix Framework API functions without creating new pages at the site. In this case, a simple and convenient tool is available — PHP command line. It allows to launch arbitrary PHP code with function calls.

    This tool is located in the site’s Control Panel at the following path: Settings > Tools > PHP command line and has the address: /bitrix/admin/php_command_line.php.

    Here’s how the result of executed code looks like, using the class functions CUser of the Main modules:

    Using the tab with «+» you can create new tabs and save them most frequently used PHP code inside them. To rename the tab, use //title:*** at the start of your code.

    init.php — is an optional file within the Bitrix Framework file structure. It is automatically connected in the prologue.

    The file may contain the initialization of event handlers and connection of additional functions that are common for all websites. In this case, it shall be located in the path /bitrix/php_interface/init.php. Each particular website may have its own similar file. In this case, it shall be located in the path /bitrix/php_interface/website ID/init.php. If both files are there, the system will connect them both, but the file /bitrix/php_interface/init.php will be the first.

    Note: The file /bitrix/php_interface/website ID/init.php is not connected in the administrative section because there is no notion of a website. Please also take into account that SITE_ID is equal to the current language and, consequently, a wrong file can get connected.

    A code in init.php should be located according to logical grouping by files and classes.

    An error in the file init.php results in the complete inoperability of the website and it is impossible to correct anything without access to the file through ftp/ssh. This may occur in the following cases:

    If the access is available only through Web, one of the simplest ways is to place all of your code in an external file and connect it like this:

    With such an approach you will be able to safely edit and make errors in the file functions.php without fear of getting an inoperable website that cannot be recovered without ftp.

    A project developer has two ways to use already created solutions: own module or the file init.php. Both options have their advantages and disadvantages.

    init.php. When you have classes that are common for several websites (in case of multiple websites or on the same server), the files containing classes are located in the same folder for the sake of convenience. Later on, symbolic links are created.

    In case of multiple websites, there is one more way: to use already created folders. For example, folders with templates.

    Both in the second and the first ways the code include $_SERVER["DOCUMENT_ROOT"]."/bitrix/templates/..." will lead to the same place for all websites where the files that are common for all projects can be located. In addition, own folder may be created for own classes, and connect classes from there by accessing /bitrix/templates/my_classes/....

    Module. The use of init.php is preferable if you create projects that are sure to be located on the same server throughout the entire existence of such projects, and you want to keep the expenses associated with the support of custom libraries to a minimum. These libraries are better supported by the same person. However, if you plan to use these solutions for other projects, a separate module should be created.

    The module suits more for distributed APIs. In this case, their interface will also have to be supported along with response format and other parameters. Otherwise, an occasional update on one of the websites using the module may be fatal in case of change of interfaces or API response formats. On the one hand, you win; on the other hand, you must create guarantees for all of the websites that use the module API.

    This chapter describes methods and tools for organizing development both by a group and an individual developer.

    To make developer’s life more convenient, main user project files were moved from the folder /bitrix into the folder /local starting starting from the code D7 main module version 14.0.1. Essentially, a single folder /bitrix is enough to be added into exceptions.

    #Руководства

    • 7 июн 2018

    • 7

    Узнаём возможности фреймворка 1С-Битрикс, делаем первые шаги к его изучению и разбираемся, почему так много специалистов выбирают эту CMS.

    Вячеслав Фирсунин

    Веб-разработчик, запустил более 20 крупных проектов, включая онлайн-франчайзинг «Позитроника», сайт фабрики «Яршинторг» и многие другие.

    ВРЕМЯ ПРОСМОТРА

    1 ч. 16 мин.

    • 55% компаний в России используют 1С‑Битрикс — услуги разработчиков на этой платформе востребованы.
    • Быстрое решение типовых задач и уже готовые решения от Битрикса. Например, онлайн-кассы легко интегрируются с платёжными системами и сервисами доставки.
    • Много возможностей. Битрикс включает в себя блоги, форумы, каталоги разных видов, интеграцию с социальными сетями и многое другое. Также есть библиотеки, которые позволяют додумывать и создавать свои расширения.
    • Сжатые сроки изучения. Чтобы начать работать с Битриксом, достаточно за 2,5–3 месяца пройти теорию — и можно сразу практиковаться, работая с реальными сайтами и наращивая свои знания.
    • Универсальность. На Битриксе создаются и сайты-визитки, и крупные интернет-магазины.
    • Документация на русском языке. Она есть в открытом доступе, её легко изучать.
    • Большое комьюнити специалистов. Новичку всегда легко найти ответы на свои вопросы или дополнительную информацию, а значит, легче развиваться.

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

    Фреймворк — структура, в которой отдельными модулями выделены, например, визуальная составляющая и функциональный код. Когда есть фреймворк, можно не писать сайт с нуля, а управлять им с помощью уже готовых блоков. Этот продукт представляет собой CMS — систему управления контентом на сайте.

    Все сайты на Битриксе состоят из трёх компонентов:

    • Модель — функции и классы, которые обращаются к базе данных.
    • Представление — шаблон, с помощью которого на страницы выводится то содержимое, которое мы хотим увидеть, когда обращаемся к базам данных.
    • Контроллер — сама страница сайта с компонентами.

    Скриншот: Skillbox Media

    Поработаем с демосайтом 1С-Битрикс. Перейдите по ссылке bitrixlabs.ru, нажмите «Создать демосайт», укажите «Интернет-магазин» и установите. После окончания установки увидите вот такую страницу. Это ваш первый интернет-магазин на 1С-Битрикс.

    Скриншот: Skillbox Media

    Здесь редактируется любой элемент. Например, наведите курсор на логотип и нажмите на «Изменить область».

    Скриншот: Skillbox Media

    В открывшемся окне замените логотип. Таким образом можно отредактировать все элементы на странице.

    Скриншот: Skillbox Media

    Система устроена так, что редактировать сайт может администратор или сам владелец бизнеса.

    Цель разработчика на 1С-Битрикс — настроить работу сайта так, чтобы администратору сайта или владельцу бизнеса было легко вносить небольшие изменения: редактировать цены и описания товаров, размещать контент, управлять внешним видом и так далее.

    Познакомимся с системой изнутри. На панели сверху выберите раздел «Администрирование».

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

    Скриншот: Skillbox Media

    Этот раздел состоит из нескольких десятков элементов. Рассмотрим наиболее важные.

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

    Скриншот: Skillbox Media

    Здесь расположены товары и категории.

    Скриншот: Skillbox Media

    Чтобы перейти в конкретный товар, выберите категорию и нажмите на название. У товаров есть различные свойства, они уже прописаны в системе.

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

    Скриншот: Skillbox Media

    В демоверсии интернет-магазина на 1С-Битрикс в «Маркетинг» загружены все основные модули для решения маркетинговых задач: пульс конверсии,
    А/В-тестирование, email-маркетинг и так далее.

    Скриншот: Skillbox Media

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

    Этот раздел доступен только для интернет-магазинов. Здесь есть вся информация по заказам, оплатам, поставкам, налогам и многое другое. А ещё — уже настроенная онлайн-касса.

    Скриншот: Skillbox Media

    Опросы, рассылки, соцсети, техподдержка, блоги — всё, что только можно, интегрируется с сайтом. Каждый пункт меню имеет свои настройки.

    Скриншот: Skillbox Media

    Тут легко посмотреть и проанализировать данные о посещаемости сайта и эффективности рекламных кампаний. На практике аналитикой в 1С-Битрикс практически не пользуются, потому что Google Analytics имеет больше возможностей.

    Скриншот: Skillbox Media

    Здесь находится каталог решений для 1С-Битрикс: различные модули, инструменты, готовые интернет-магазины. Разработчик может создать какое-либо решение для Битрикса и выставить его на продажу через маркетплейс.

    Скриншот: Skillbox Media

    Здесь представлен полный список настроек сайта: валюты, серверы, масштабирование, производительность, поиск по сайту и многое другое.

    Скриншот: Skillbox Media

    В категории настроек «Пользователи» настраиваются уровни доступа для разработчиков, контент-менеджеров и других специалистов, работающих над сайтом.

    Благодаря встроенным решениям работу с Битриксом легко освоить. Рассмотрим несколько простых действий с системой.

    Перейдите во вкладку «Сайт → Создать страницу».

    Скриншот: Skillbox Media

    Введите заголовок страницы и отметьте нужные пункты:

    • Перейти к редактированию страницы — откроется окно редактора.
    • Добавить пункт меню — новая страница появится в меню.
    • Ограничить доступ к странице — страница будет создана, но не опубликована.

    Скриншот: Skillbox Media

    В следующем окне добавляются описание страницы и ключевые слова, Title и файлы robots — эти данные влияют на поисковую оптимизацию сайта.

    Скриншот: Skillbox Media

    Далее откроется окно редактирования страницы. Здесь размещают контент — текст, видео, фотографии, а также встроенные решения, сервисы и инструменты.

    Скриншот: Skillbox Media

    Чтобы открыть встроенные компоненты и сниппеты Битрикса, нажмите на указатель сбоку. Когда панель сниппетов скрыта, указатель находится там, куда направлена стрелка на скриншоте.

    Скриншот: Skillbox Media

    Разработчик будет работать с кодом PHP. Чтобы перейти в код, нажмите на иконку слева и выберите соответствующий вид редактора, как показано на скриншоте.

    Мы разобрали базовые навыки работы с 1С‑Битрикс. Теперь рассмотрим, какие ошибки чаще всего допускают новички и почему.

    • Переоценивать книги. Книга не даст обратной связи, не ответит на вопросы, не решит проблему.
    • Думать, что одно решение подходит под разные задачи. Не все решения Битрикса стоит принимать как верные, всегда нужно учитывать нюансы конкретного интернет-магазина. В документации указано не всё, понимание этих нюансов приходит только с опытом.
    • Терять время на неэффективное обучение. Чтобы стать хорошим разработчиком без посторонней помощи, потребуется 6–7 лет и очень много удачи.

    Понравилась статья? Поделить с друзьями:
  • Руководство нижегородской академии мвд россии фото
  • Лекарство артрофиш инструкция по применению цена отзывы
  • Феррум лек раствор инструкция по применению цена
  • Dbs hc 3208 2d usb инструкция
  • Фитолон кламин арго инструкция по применению цена отзывы