Данный пост будет о том, как с помощью класса Imagick было создано изображение с основной фотографией товара и наложенных на данной фотографии основных характеристиках товара.
Итак, стояла задача: разработать механизм, позволяющий перебирать все товара на сайте, брать основное изображение товара, размещать миниатюры изображений товара в другом цвете, выводить цену (менеджер перед запуском обработчика выбирает нужный тип цены), артикул и доступные размеры.
Для формирования изображения был выбран 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); }
Таким образом, в указанной менеджером папке на сайте будут размещены изображения с товарами и актуальной инфорвацией о товарах и их торговых предложениях.
Разработка сайта
Подайте заявку на разработку сайта на базе готового решения от компании 1С-Битрикс или одного из партнеров компании. Максимально подробно опишите, чему будет посвящен сайт, если это интернет-магазин - что он будет продавать, нужна ли мультиязычность, будут ли разные типы цен (розница, опт, крупный опт), будет ли интеграция с 1С, будет ли выгрузка товаров на различные торговые площадки...
Сопровождение сайта
Вы можете подать заявку на сопровождение вашего сайта на базе 1С-Битрикс. Сопровождение включает в себя: проверка актуальности обновлений сайта, проверка актуальности резервной копии, консультации по сайту. Опишите в заявке, какие еще объемы планируются на сопровождении и на какой срок вы планируете заключить договор на сопровождение - мы подберем подходящий вам бюджет на сопровождение
Работы по сайту
Вы можете подать заявку на выполнение определенного объема работ по сайту. Опишите в заявке объем работ. Это может быть разработка какого-то нового функционала, доработки по имеющемуся функционалу, доработки под требования сео-специалистов. На основании заявки вам будет сформирован бюджет работ, а также названы сроки на выполнение тех или иных работ.