Итак, стояла задача: разработать механизм, позволяющий перебирать все товара на сайте, брать основное изображение товара, размещать миниатюры изображений товара в другом цвете, выводить цену (менеджер перед запуском обработчика выбирает нужный тип цены), артикул и доступные размеры.
Для формирования изображения был выбран php-класс Imagick.
Весь процесс разбивается на несколько этапов:
Подготовительный этап
Для начала выводим менеджеру форму с опциональными полями: инфоблок, тип цены, параметры результирующего изображения
Дальше, нужно написать скрипт, который будет перебирать все товары из каталога. Для этого, нужно по нажатию на кнопку запуска обработчика, произвести первый запрос к базе данных с учетом разбиения выборки постранично (количество единовременно загружаемых товаров соответствует возможностям сервера. Моя реализация построена исходя из предположения о том, что за один шаг обрабатывается один товар), а затем - уже настроить пошаговую обработку всех товаров.
Обработчик на языке JavaScript выглядит примерно так:
var maxRows = false;
var Form;
$(document).on('click','button#broshureStart',function () {
Form = $(this).parents('form');
var num = 1;
var wait = BX.showWait('loading');
var persentsContainer = Form.find('p.persents');
function work_with_row(num, d){
var Persents = (parseInt(num))*100 / parseInt(maxRows);
persentsContainer.find('span.pv').html(Math.round(Persents));
persentsContainer.find('span.rv').html(parseInt(num));
$.ajax({
type: "POST",
url: location.href,
data: {num: num, action: 'createProducts',sessid: Form.find('input[name="sessid"]').val() },
dataType: "json",
success: function (data) {
d && d.resolve();
},
onfailure: function(){
d && d.resolve();
}
});
}
$.ajax({
type: "POST",
url: location.href,
data: {num: num, action: 'createProducts',sessid: Form.find('input[name="sessid"]').val() },
dataType: "json"
}).then(function (data) {
if(!maxRows && data.CNT!==undefined){
maxRows = data.CNT;
var deferreds = [];
var i=10;
persentsContainer.find('span.maxrows').html(maxRows);
persentsContainer.show();
for(var index = num+1; index<=maxRows; index++){
(function (index) {
var d = new $.Deferred();
window.setTimeout(function() { work_with_row(index,d) }, 1000*index+(i++));
deferreds.push(d);
})(index);
}
$.when.apply($, deferreds).done(function () {
$('p.persents').html('Обработка завершена');
BX.closeWait('loading',wait);
});
}
}, function (reason) {
console.debug(reason);
});
return false;
});
Таким образом, будут отправлены запросы для обработки всех товаров в каталоге.
Скрипт обработки ajax-запроса
Для обработки запросов на этой же странице в самом начале добавляем обработчик ajax-запросов:
$POST_RIGHT = $APPLICATION->GetGroupRight("catalog");
// если нет прав - отправим к форме авторизации с сообщением об ошибке
if ($POST_RIGHT == "D")
$APPLICATION->AuthForm(GetMessage("ACCESS_DENIED"));
$context = Application::getInstance()->getContext();
$request = $context->getRequest();
if ($request->isPost() && $request->get('action') && $POST_RIGHT == "W" && check_bitrix_sessid())
{
$arResult = array();
$arPostData = $request->getPostList()->toArray();
if($request->get('action')=='createProducts'){
$pageNum = $request->get('num');
if(intval($pageNum)<1) $pageNum = 1;
$processor = new \Pai\Broshure\CProcessor($arSettings);
$processor->setNPage($pageNum);
$processor->GetIblockElementData();
$arResult = $processor->ProcessElementData();
$arResult['CNT'] = $processor->ReturnElementsCnt();
$arResult['CNT'] = $processor->ReturnElementsCnt();
if($request->isAjaxRequest()){
$APPLICATION->RestartBuffer();
header('Content-type: application/json');
echo \Bitrix\Main\Web\Json::encode($arResult);
exit();
}
}
Таким образом, при первом запросе к серверу будет получено общее количество элементов инфоблока и можно будет запустить пошаговый обработчик всех товаров.
Создание изображения с информацией о товаре
Сам обработчик товаров представляет собой класс с основными методами. Рассмотрим их:
Выборка товаров из каталога
protected function GetIblockElementItems(
$arParams = array('filter' => array(), 'select' => false, 'sort' => array('name' => 'asc'),
'page_params' => false, 'group' => false),
$life_time = 3600)
{
if (!isset($arParams['filter']) || empty($arParams['filter'])) return false;
$arFilter = $arParams['filter'];
$arSelect = (isset($arParams['select'])) ? $arParams['select'] : false;
$arSort = (isset($arParams['sort'])) ? $arParams['sort'] : array('name' => 'asc');
$pageParams = (isset($arParams['page_params'])) ? $arParams['page_params'] : false;
$groupParams = (isset($arParams['group'])) ? $arParams['group'] : false;
$return = false;
$cache_params = array();
foreach (array_keys($arParams) as $array_key)
{
if (is_array($arParams[$array_key]))
{
foreach ($arParams[$array_key] as $key => $value)
{
$cache_params[$array_key . '-' . $key] = $value;
}
} elseif (is_bool($arParams[$array_key]))
{
$cache_params[$array_key] = $arParams[$array_key] ? 1 : 0;
}
}
$cache_id = md5(serialize($cache_params));
$cache_dir = __CLASS__ . '/' . __FUNCTION__;
$cache = \Bitrix\Main\Data\Cache::createInstance();
if ($life_time < 0)
{
$cache->clean($cache_id, $cache_dir);
}
if ($cache->initCache($life_time, $cache_id, $cache_dir))
{
$return = $cache->getVars();
} elseif ($cache->startDataCache() && \Bitrix\Main\Loader::includeModule('iblock'))
{
if(
is_array($arSelect)
&& in_array('IBLOCK_ID',$arSelect)
&& in_array('ID',$arSelect)
){
$filterProperties = array();
$filterFields = array();
foreach ($arSelect as $select)
{
if(strpos($select,'PROPERTY_')!==false){
$filterProperties[] = str_replace('PROPERTY_','',$select);
} else {
$filterFields[] = $select;
}
}
$rsItems = \CIBlockElement::GetList($arSort, $arFilter, $groupParams, $pageParams, $filterFields);
while ($arElement = $rsItems->GetNextElement())
{
$arFields = $arElement->GetFields();
if(!empty($filterProperties)){
foreach ($filterProperties as $arFilterCode){
if(!isset($arFields['PROPERTIES'])) $arFields['PROPERTIES'] = array();
$arFields['PROPERTIES'] = array_merge($arFields['PROPERTIES'],$arElement->GetProperties(false,array(
'CODE'=>$arFilterCode
)));
// $arFields['PROPERTIES'][$arFilterCode] = ;
}
// $arFields['PROPERTIES'] = $arElement->GetProperties(false,array('CODE'=>$filterProperties));
}
$return[] = $arFields;
}
} elseif (empty($arSelect)){
$rsItems = \CIBlockElement::GetList($arSort, $arFilter, $groupParams, $pageParams, $arSelect);
while ($arElement = $rsItems->GetNextElement())
{
$arFields = $arElement->GetFields();
$arFields['PROPERTIES'] = $arElement->GetProperties();
$return[] = $arFields;
}
} else {
$rsItems = \CIBlockElement::GetList($arSort, $arFilter, $groupParams, $pageParams, $arSelect);
while ($arElement = $rsItems->GetNext())
{
$return[] = $arElement;
}
}
if (!empty($return))
{
foreach ($return as $key => $arItem)
{
if (!empty($arItem['PROPERTIES']))
{
foreach ($arItem['PROPERTIES'] as $pCode => $arProperty)
{
$return[$key]['PROPERTIES'][$pCode] = \CIBlockFormatProperties::GetDisplayValue(
array('ID' => $arItem['ID'], 'NAME' => $arItem['NAME']), $arProperty, '');
}
}
}
}
if(isset($pageParams) && !empty($pageParams) && $pageParams['nPageSize']==1 && !empty($return[0])){
$return = $return[0];
}
$return = array(
'ITEMS'=>$return,
'MAX_CNT'=>$rsItems->NavPageCount
);
$cache->endDataCache($return);
}
$this->settings['ELEMENTS_CNT'] = $return['MAX_CNT'];
return $return['ITEMS'];
}
Получение информации об одном товаре
protected function GetIblockElementItems(
$arParams = array('filter' => array(), 'select' => false, 'sort' => array('name' => 'asc'),
'page_params' => false, 'group' => false),
$life_time = 3600)
{
if (!isset($arParams['filter']) || empty($arParams['filter'])) return false;
$arFilter = $arParams['filter'];
$arSelect = (isset($arParams['select'])) ? $arParams['select'] : false;
$arSort = (isset($arParams['sort'])) ? $arParams['sort'] : array('name' => 'asc');
$pageParams = (isset($arParams['page_params'])) ? $arParams['page_params'] : false;
$groupParams = (isset($arParams['group'])) ? $arParams['group'] : false;
$return = false;
$cache_params = array();
foreach (array_keys($arParams) as $array_key)
{
if (is_array($arParams[$array_key]))
{
foreach ($arParams[$array_key] as $key => $value)
{
$cache_params[$array_key . '-' . $key] = $value;
}
} elseif (is_bool($arParams[$array_key]))
{
$cache_params[$array_key] = $arParams[$array_key] ? 1 : 0;
}
}
$cache_id = md5(serialize($cache_params));
$cache_dir = __CLASS__ . '/' . __FUNCTION__;
$cache = \Bitrix\Main\Data\Cache::createInstance();
if ($life_time < 0)
{
$cache->clean($cache_id, $cache_dir);
}
if ($cache->initCache($life_time, $cache_id, $cache_dir))
{
$return = $cache->getVars();
} elseif ($cache->startDataCache() && \Bitrix\Main\Loader::includeModule('iblock'))
{
if(
is_array($arSelect)
&& in_array('IBLOCK_ID',$arSelect)
&& in_array('ID',$arSelect)
){
$filterProperties = array();
$filterFields = array();
foreach ($arSelect as $select)
{
if(strpos($select,'PROPERTY_')!==false){
$filterProperties[] = str_replace('PROPERTY_','',$select);
} else {
$filterFields[] = $select;
}
}
$rsItems = \CIBlockElement::GetList($arSort, $arFilter, $groupParams, $pageParams, $filterFields);
while ($arElement = $rsItems->GetNextElement())
{
$arFields = $arElement->GetFields();
if(!empty($filterProperties)){
foreach ($filterProperties as $arFilterCode){
if(!isset($arFields['PROPERTIES'])) $arFields['PROPERTIES'] = array();
$arFields['PROPERTIES'] = array_merge($arFields['PROPERTIES'],$arElement->GetProperties(false,array(
'CODE'=>$arFilterCode
)));
}
}
$return[] = $arFields;
}
} elseif (empty($arSelect)){
$rsItems = \CIBlockElement::GetList($arSort, $arFilter, $groupParams, $pageParams, $arSelect);
while ($arElement = $rsItems->GetNextElement())
{
$arFields = $arElement->GetFields();
$arFields['PROPERTIES'] = $arElement->GetProperties();
$return[] = $arFields;
}
} else {
$rsItems = \CIBlockElement::GetList($arSort, $arFilter, $groupParams, $pageParams, $arSelect);
while ($arElement = $rsItems->GetNext())
{
$return[] = $arElement;
}
}
if (!empty($return))
{
foreach ($return as $key => $arItem)
{
if (!empty($arItem['PROPERTIES']))
{
foreach ($arItem['PROPERTIES'] as $pCode => $arProperty)
{
$return[$key]['PROPERTIES'][$pCode] = \CIBlockFormatProperties::GetDisplayValue(
array('ID' => $arItem['ID'], 'NAME' => $arItem['NAME']), $arProperty, '');
}
}
}
}
if(isset($pageParams) && !empty($pageParams) && $pageParams['nPageSize']==1 && !empty($return[0])){
$return = $return[0];
}
$return = array(
'ITEMS'=>$return,
'MAX_CNT'=>$rsItems->NavPageCount
);
$cache->endDataCache($return);
}
$this->settings['ELEMENTS_CNT'] = $return['MAX_CNT'];
return $return['ITEMS'];
}
Создание объекта IMagick
protected function ReadImage(){
try{
$this->_Imagick = new \Imagick();
$this->_Imagick->readImage($this->_absPath);
} catch (Exception $e) {
\CAdminMessage::ShowMessage($e->getMessage()); die();
}
}
Создание результирующего изображения
public function CreateResultImage($width,$height){
// для начала заменим размеры изображения
$blur = max(
$this->_Imagick->getImageWidth() / $width,
$this->_Imagick->getImageHeight()/$height);
if($blur!==1){
$this->_Imagick->resizeImage($width,$height,null,$blur);
}
}
Создание миниатюр изображений в других цветах:
public function AddSamplesToMasterPhoto($arImages,$sampleWidth){
$arThumbs = array();
$labels = array();
$this->imgWidthStart = $sampleWidth* .1;
foreach ($arImages as $arImage)
{
$thumb = new \Imagick(Application::getDocumentRoot().$arImage['SRC']);
$thumb->adaptiveResizeImage($sampleWidth*.9, 0);
$arThumbs[] = $thumb;
$labels[] = $this->DrawImageColor($arImage,$sampleWidth*.9);
}
$maxHeight = 0;
$maxLabelHeight = 0;
foreach ($arThumbs as $num => $imagick)
{
$imgHeight = $imagick->getImageHeight();
$maxHeight = max($maxHeight,$imgHeight);
$maxLabelHeight = max($maxLabelHeight, $labels[$num]->getImageHeight());
}
// местоположение семплов должно быть на треть их высоты от края
$offsetY = $this->_Imagick->getImageHeight() - $maxHeight*2;
$labelOffsetY = $offsetY + $maxHeight*1.1;
$offsetX = $sampleWidth*.1;
foreach ($arThumbs as $num=>$imagick)
{
$this->_Imagick->compositeImage($imagick,\Imagick::COMPOSITE_OVER, $offsetX, $offsetY);
$this->_Imagick->compositeImage($labels[$num],\Imagick::COMPOSITE_OVER, $offsetX, $labelOffsetY);
$offsetX += $sampleWidth*1.05;
}
$this->imgLastPos = $offsetY;
}
Функция наложения текста на изображении
protected function DrawText($text,$size,$color,$bg=false,$maxWidth = false){
$draw = new \ImagickDraw();
$draw->setFillColor($color);
$draw->setFontSize($size);
$metrics = $this->_Imagick->queryFontMetrics($draw, $text);
$image = new \Imagick();
if($bg){
$image->newImage($metrics['textWidth'], $metrics['textHeight'], $bg);
} else {
$image->newImage($metrics['textWidth'], $metrics['textHeight'], new \ImagickPixel('transparent'));
}
$draw->annotation(0, $metrics['ascender'], $text);
$image->drawImage($draw);
if($maxWidth && $metrics['textWidth'] > $maxWidth){
$image->adaptiveResizeImage($maxWidth,0);
}
return $image;
}
В данной функции кроме непосрдественного вывода текста на изображении, реализован механизм отрисовки фоновой подложки под изображением. Если цвет фона задан - будет выведен он.
Сохранение полученных результатов
public function SaveResultImage($folder,$fileName){
if(!file_exists(Application::getDocumentRoot().$folder)){
mkdir(Application::getDocumentRoot().$folder);
}
$this->_Imagick->setImageFormat("jpg");
$this->_Imagick->writeImage(Application::getDocumentRoot().$folder.'/'.$fileName);
}
Таким образом, в указанной менеджером папке на сайте будут размещены изображения с товарами и актуальной инфорвацией о товарах и их торговых предложениях.
