На последних проектах я столкнулся с тем, что к контенту на сайте прилагается значительное количество изображений с различными размерами. Обойтись стандартными полями превью и детального изображения для элементов и разделов инфоблоков с их настройками уменьшения изображения не получилось. Поэтому мы с коллегами пошли по простому пути и стали использовать стандартные свойства типа “файл” как для элементов инфоблоков, так и для пользовательских свойств для разделов инфоблоков. Далее, конечно же, создали обработчики событий для того, чтобы обработать изображения в то состояние, которое требуется для отображения на сайте. Под обработкой изображения я подразумеваю пропорциональный ресайзинг или кроп (ресайзинг по одному размеру и дальнейшая обрезка лишнего), чего можно добиться стандартными средствами Битрикса (методы CFile::ResizeImage и др.). Сделали один проект таким образом… А потом второй, третий… Конечно, такой подход имеет право на жизнь, но он очень неудобен. [spoiler] К разработке своего модуля меня подтолкнул рассказ коллеги об удачном опыте создания кастомного свойства инфоблока. Вдохновившись его примером и документацией я приступил к разработке.
Суть создания кастомных свойств заключается в создании определенных обработчиков событий для событий OnIBlockPropertyBuildList для свойств элементов инфоблока. Пример такого обработчика:
OnUserTypeBuildList для пользовательских свойств. Пример такого обработчика:
public static function getPictureResizerUserTypeDescription()
{
return array(
'USER_TYPE_ID' => 'resize_file',
'CLASS_NAME' => 'CustomPropertiesModule\\CustomProperties\\PictureResizerUserTypeProperty',
'DESCRIPTION' => 'Картинки с ресайзом',
'BASE_TYPE' => 'file',
);
}
В этих обработчиках содержится описание кастомных свойств и стандартные методы, которые могут быть вызваны при работе со свойством. Например, html-представление настроек для свойства элемента инфоблока определяется реализацией метода GetSettingsHTML. Результаты работы этого метода можно увидеть на следующем скриншоте:
Для пользовательских свойств страница настройки выглядит так:
Что еще позволяют делать эти методы? Обрабатывать значения перед сохранением в БД, создавать html-представления в формах редактирования админки и публичной части, обрабатывать значения настроек свойств и др.
Расскажу о некоторых проблемах в разработке. При создании кастомного свойства указывается базовый тип свойства (строка, файл, список и др.). Метод CFileInput::Show позволяет создать стандартное html-представление контрола для загрузки изображений. Если указать базовый тип свойства “файл” для пользовательских типов свойств, то никаких проблем не возникает. Но для свойств элемента инфоблока это стало проблемой, т.к. используя стандартное html-представление крайне трудно без костылей удалить уже загруженное в свойство изображение. В связи с этим я отказался от использования базового типа “файл” и стал использовать “строку”, куда помещаю идентификатор файла в таблице, регистрирующей файлы в БД Битрикс.
Код модуля можно увидеть на GitHub. Помимо стандартной установки модуля доступна установка с использованием Composer, о которой я рассказывал в своем предыдущем посте.
Важно! В модуле используется API ядра D7, поэтому рекомендуется использовать его только для версий Битрикса от 14 и выше. Модуль находится в beta-версии и, если вы решите использовать его в своих проектах, о найденных багах сообщайте, пожалуйста, в Issues репозитория.
Хотелось бы подвести небольшой итог этого поста. На мой взгляд, создание собственных свойств открывает множество возможностей, потому что обработка изображений - это, конечно же, не единственное применение. Созданные командой Битрикса свойства такие как, например, привязка к карте Google Maps - отличный тому пример.
Загальский Андрей, насчет папок и неймспейсов соглашусь, это нужно исправить. А вот код партнера и прочее, на мой взгляд, нужны для модулей, выкладываемых в Маркетплейс. А у меня в данный момент нет намерений выкладывать этот модуль туда.
Пилецкий Антон, ресайзинг - тонкая штука, реализаций может быть много в зависимости от заказчика и проекта. Но используя ресайзинг прямо в шаблоне компонента вы сильно отдаляетесь от паттерна MVC.
if ($USER->IsAdmin()){ if (! IsModuleInstalled(self::MODULE_ID)){ код код код } }
Этот код имеет обязательного характера. Но представьте, что у одной из групп пользователей есть доступ в админку и по какой-либо причине есть доступ к редактированию параметров главного модуля. Думаю, вы понимаете к чему я клоню.
Михаил Осотов написал: используя ресайзинг прямо в шаблоне компонента вы сильно отдаляетесь от паттерна MVC
Дэ?.. Шаблон компонента - это представление, как выводить картинку и каких размеров - это вопрос представления информации. То есть за выводимый размер картинки должен отвечать именно шаблон. Поправьте меня, коллега, если я не прав.
Михаил Осотов написал: А вот код партнера и прочее, на мой взгляд, нужны для модулей, выкладываемых в Маркетплейс. А у меня в данный момент нет намерений выкладывать этот модуль туда.
выкладывать или нет не столь важно. А вот если модуль лежит в namespace Bitrix, так он в итоге системный?!
а то выходит, если вы приведете namespace в нормальный вид то они будут
Загальский Андрей, в папке битрикс можно класть модуль, помнится даже когда то Антон Долганин так делал:) Со всем остальным согласен:) - и ресайз должен быть в шаблоне, потому что он кладётся в кэш, и кастомизировать компонент ради ресайза - очень нехорошо. Либо это должно управлятся из настроек компонента
Микулич Евгений написал: в папке битрикс можно класть модуль
Можно и никто не запрещает, но по мне так это не правильно. А Антон делал, но при этом писал, что считает свои компоненты системными (хотя могу и ошибиться, поиск цитаты займет много времени...). Так и язык не повернется сказать что это не так
Пилецкий Антон написал: То есть за выводимый размер картинки должен отвечать именно шабло
d в шаблоне должны быть уже готовые к выводу данные, шаблон должен исключать из себя всякую логику подготовки данных -только логика вывода!!!
то же самое в компонентах если надо ресайзить картинку (допустим news.list) Вы будете это делать в templates.php?? нет! в result_modifier.php т.е. окочательные данные в САМОМ шаблоне вывода будут уже окончательно готовы!
Черепанов Виталий написал: если надо ресайзить картинку (допустим news.list) Вы будете это делать в templates.php?? нет! в result_modifier.php
Вообще без разницы где происходит ресайз, в template.php или в result_modifier.php, важно что оригинал картинки остается в сохранности. Но работать с картинкой в result_modifier.php идеологически правильней, да.
Гринкевич Дмитрий, Если регистрировать обработчики событий в install/index.php придется пользоваться функцией RegisterModuleDependences, которая делает запись о событии в базу. Это не очень гибко. Нужно следить чтобы при удалении модуля удалялись все добавленные события. А файл install.php выполняется при каждом подключении модуля. Удобнее регистрировать события в нем без добавления записи в базу. При таком подходе не нужно следить за удалением зарегистрированных событий, т.к. событие не записывается в БД и при отладке можно на время отключить обработчик события, закомментировав строку с регистрацией события.
Пилецкий Антон написал: Вообще без разницы где происходит ресайз, в template.php или в result_modifier.php, важно что оригинал картинки остается в сохранности. Но работать с картинкой в result_modifier.php идеологически правильней, да.
На мой взгляд изменение картинки при выполнении шаблона это плохой подход. Представьте что на сайте большая посещаемость и в шаблоне изменился размер картинки, одновременно на страницу где делается ресайз картинки зашло много людей. Не факт что ресайз будет выполнен только один раз, т.к. страница была запрошена одновременно много раз. А если страниц, где в шаблоне ресайзится картинка, тысячи, то нагрузка на сервер будет очень большая. Разумнее хранить оригинал загруженной картинки, а при изменении требований к размерам написать небольшой консольный скрипт который обновит нужные свойства и картинки в них будут перегенерированы.
Гринкевич Дмитрий, Под каждый размер создавать свойство не нужно. Обычно на сайте картинка отображается в нескольких вариантах. Число этих вариантов известно заранее. Допустим в списке статей на главной показывается квадратная картинка, на детальной странице статьи показывается картинка определенной ширины. И есть какой-нибудь третий размер еще на одном типе страниц. В инфоблоке статей создаем 4 свойства типа файл: оригинальная картинка и 3 свойства под каждый размер. При событии на добавление/обновление записи инфоблока подключаем свой обработчик, который из оригинальной картинки (при условии что она добавилась/изменилась) будет генерировать картинку нужного размера и сохранять ее в свойство которое хранит картинку этого размера. В результате получаем картинки всех нужных размеров и можем сразу выводить их в шаблоне. Если вдруг нужно будет изменить размер одной из картинок, можно написать скрипт который перегенерирует картинку используя оригинальную.
Допустим у Вас есть инфоблок с 1000 товарами, в каждом товаре 5 картинок (детальная картинка + 4 дополнительных в множественно свойстве). Причем для главной выводиться только детальная 300х300. Для списка товаров выводятся все 5 картинок 300х300, а в карточке надо вывести 50х50, 250х250 и 800х800 (можно оригинал). Как Ваш метод здесь реализовать и насколько он будет меньше затрачивать ресурсов? Т.е. из 2 свойств у Вас автоматически получаются 8? И если я захочу на главной поменять с 300х300 на 250х250, то мне надо создавать новое свойство и т.д.? Я Вас правильно понял?
Не понял вот этот момент
Овсянников Максим написал: Под каждый размер создавать свойство не нужно.
Овсянников Максим написал: В инфоблоке статей создаем 4 свойства типа файл: оригинальная картинка и 3 свойства под каждый размер.
Тут я ошибся. Под каждый требуемый размер создаем отдельное свойство. Оригинал картинки храним в DETAIL_PICTURE, для каждого размера создаем отдельное свойство в инфоблоке. Свойство не множественное. Пишем обработчик событий OnAfterIBlockElementUpdate и OnAfterIBlockElementAdd Код примерно такой:
function OnAfterIBlockElementAddAndUpdateHandler(&$arFields)
{
if ($arFields["RESULT"] && isset($arFields["DETAIL_PICTURE_ID"])) {
$imageData = $arFields["DETAIL_PICTURE_ID"];
//далее из этого массива получаем картинку в нужном размере используя CFile::ResizeImageGet и сохраняем в нужное свойство
}
}
Т.е. из 2 свойств у Вас автоматически получаются 8? И если я захочу на главной поменять с 300х300 на 250х250, то мне надо создавать новое свойство и т.д.?
Да, из оригинала при создании/обновлении элемента инфоблока генерируются все нужны размеры. Если размер меняется, то нужно либо создать новое свойство , либо обновить существующее.
Овсянников Максим написал: Оригинал картинки храним в DETAIL_PICTURE,
А как же фотогалерея для товара, ммм?...
Овсянников Максим написал: для каждого размера создаем отдельное свойство в инфоблоке
...и захламляем интерфейс менеджера. А если еще вспомнить про печально известное ограничение количества свойств в инфоблоке, связанное с ограничением в MySQL ( row size too large ), то становится совсем грустно. Нужно экономить свойства.
Теперь про нагрузку на сервер. При ресайзе происходит кэширование созданной картинки, и никаких дополнительных ресайзов не будет.
Овсянников Максим написал: А если страниц, где в шаблоне ресайзится картинка, тысячи, то нагрузка на сервер будет очень большая.
Ну и? А скриптом на сервере вы разве не создаете такую же большую нагрузку? В случае со множеством страниц нагрузка на сервер хотя бы размазывается по времени. Если же вам всё же нравится вариант со скриптом, то почему бы просто в этом скрипте не вызвать функции ресайза картинок, с аналогичными шаблонам настройками, без сохранения их в отдельные свойства? После отработки скрипта создадутся закэшированные уменьшенные версии изображений и для их получении в шаблонах не потребуются ресурсы сервера.
1) Если нужна галерея, то такой подход не подойдет. Но при желании можно сделать так, чтобы фото из галереи хранились в другом инфоблоке (например Фото), а выводились и добавлялись на странице редактирования инфоблока товара. Делаем множественное кастомное свойство типа "привязка к элементам инфоблока" и пишем для него свою форму. В ней можно вывести любое число фотографий (из инфоблока Фото) привязанных к товару, а также добавить в инфоблок Фото любое число фотографий, причем эти фотографии будут привязаны к редактируемому товару. В этом посте как раз об этом и говорится. Если проще и быстрее ресайзить фото в шаблоне то ок. Но если есть время, то почему бы не сделать красивое решение которое можно будет потом переносить на другие проекты?
2) Ненужные поля легко скрываются в настройках форма редактирования элемента инфоблока. Менеджер не будет их видеть. Если на проекте есть этап анализа и сбора требований с последующим проектированием, то проблему нехватки свойств можно избежать. Если мы заранее знаем что свойств будет очень много, то придется использовать другой подход. Тут все зависит от проекта.
3) А вы уверены что если одновременно много раз вызвать функцию ресайза, то ресайз будет выполнен только один раз а для остальных картинка будет взята из кеша? Скрипт можно запустить в любое время, например ночью когда нагрузка на сайт минимально и не нагружать сервер.
Группы на сайте создаются не только сотрудниками «1С-Битрикс», но и партнерами компании. Поэтому мнения участников групп могут не совпадать с позицией компании «1С-Битрикс».