Как настроить пути к фотографиям товара от корня сайта

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

/#PRODUCT_CODE#.jpg - основное фото товара

/#PRODUCT_CODE#-preview.jpg - анонсовое фото товара для списка

/#PRODUCT_CODE#-X.jpg - X-овое дополнительное фото товара

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

Вводные данные: 

#PRODUCT_CODE# - символьный код товара. Он уникальный в рамках каталога товаров

Сайт развернут на типовом битриксовом веб-окружении BitrixVM актуальной версии.

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

1. В файле /etc/nginx/bx/conf/bitrix_general.conf находим location вида:

 location ~* \.(css|js|gif|png|jpg|jpeg|ico|ogg|ttf|woff|eot|otf|svg|woff2)$ {
 error_page 404 /404.html;
 expires 30d;
 }
 

2. Для изображений JPG выносим обработку отдельно и отключаем 404-ю ошибку:

location  ~* \.(css|js|gif|png|ico|ogg|ttf|woff|eot|otf|svg|woff2)$ {
  error_page 404 /404.html;
  expires 30d;
}

location ~* \.(jpg|jpeg)$ {
  error_page 404 =200 /imgget.php?request_uri=$request_uri;
  expires 30d;
}

В результате, в php коде используя $_GET["request_uri"] получаем путь к изображению, с которым уже можно дальше работать.

Далее, в файле-обработчике imgget.php делаем саму обработку получения изображений:

define("STOP_STATISTICS", true);
define('NO_AGENT_CHECK', true);

use Bitrix\Main\Application;
use \Bitrix\Main\Data\Cache;

require($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php');

function GetImagesByCode($code){
	$IBLOCK_ID = 2;
	$cache = Cache::createInstance();
	$cache_id=md5(serialize(['function'=>__FUNCTION__,'CODE'=>$code,'IBLOCK_ID'=>$IBLOCK_ID]));
	$cache_time = 60*60*24*7;
	$cache_dir = 'static_prod_images';

	if(isset($_GET['clear_cache'])){
		$cache_time = 0;
		$cache->clean($cache_id,$cache_dir);
	}

	$result = [];

	if($cache->initCache($cache_time,$cache_id,$cache_dir)){
		$result = $cache->getVars();
	} elseif ($cache->startDataCache() && \Bitrix\Main\Loader::includeModule('iblock')){
		$arOrder = array("SORT" => "ASC");
		$arFilter = array('IBLOCK_ID'=>$IBLOCK_ID,'ACTIVE'=>'Y','CODE'=>$code);
		$arSelectFields = array("ID", "ACTIVE", "NAME",'PREVIEW_PICTURE','DETAIL_PICTURE','IBLOCK_ID');
		$rsElements = \CIBlockElement::GetList($arOrder, $arFilter, FALSE, FALSE, $arSelectFields);
		if ($arElement = $rsElements->GetNext())
		{
			$arMorePhotos = [];
			$rs = CIBlockElement::GetProperty($arElement['IBLOCK_ID'],$arElement['ID'],"sort","asc",
                    ['CODE'=>'MORE_PHOTO']);
			while ($ob = $rs->GetNext()){
				$arMorePhotos[] = [
					'ID'=>intval($ob['VALUE']),
					'SRC'=>\CFile::GetPath(intval($ob['VALUE'])),
				];
			}

			$result = [
				'PREVIEW_PICTURE'=>(intval($arElement['PREVIEW_PICTURE'])>0) 
                    ? [
                        'ID'=>intval($arElement['PREVIEW_PICTURE']),
                        'SRC'=>\CFile::GetPath(intval($arElement['PREVIEW_PICTURE']))
                    ] 
                    : false,
				'DETAIL_PICTURE'=>(intval($arElement['DETAIL_PICTURE'])>0) 
                    ? [
                        'ID'=>intval($arElement['DETAIL_PICTURE']),
                        'SRC'=>\CFile::GetPath(intval($arElement['DETAIL_PICTURE']))
                    ] 
                    : false,
				'MORE_PHOTO'=>(!empty($arMorePhotos)>0) ? $arMorePhotos : false,
			];
		}
		$cache->endDataCache($result);
	}

	return $result;
}

function GetImagesSrcSet($image)
	{
		$sizes = [
				'0'    => [100, 100, 50],
				'1920' => [300, 400, 80],
				'960'  => [200, 267, 80],
				'480'  => [200, 267, 80],
			];

		$result = [];
		foreach ($sizes as $key => $arSize)
		{
			if (isset($image['ID']))
			{
				$renderImg = \CFile::ResizeImageGet(
                    $image['ID'], 
                    array("width" => $arSize[0], "height" => $arSize[1]), 
                    BX_RESIZE_IMAGE_PROPORTIONAL, 
                    false, 
                    false, 
                    false, 
                    $arSize[2]
                );
				if (!isset($renderImg['src']) || strlen($renderImg['src']) <= 0)
				{
					if (isset($image['SRC']))
					{
						$renderImg = $image['SRC'];
					} else
					{
						$renderImg = \CFile::GetPath($image['ID']);
					}
				} else
				{
					$renderImg = $renderImg['src'];
				}

				$result[$key] = $renderImg;
			}
		}

		$result['set'] = implode(', ', [
			$result['1920'] . ' 1920w',
			$result['960'] . ' 960w',
			$result['480'] . ' 480w',
		]);

		return $result;
	}

$requestedDir = current(explode('?',$_REQUEST['request_uri']));
$mode = 'full';
$arCodes = false;
$Scale = false;

if(preg_match('|-x([\d]+)\.jpg$|',$requestedDir,$matches)){
	// Если в шаблоне зашито масштабирование фото
	$Scale = $matches[1];
	$requestedDir = preg_replace('|-x([\d]+)\.jpg$|','.jpg',$requestedDir);
}

if(preg_match('|-preview\.jpg$|',$requestedDir)){
	// если это ссылка на превью фото
	$mode = 'preview';
	$requestedDir = str_replace('-preview','',$requestedDir);
} elseif (preg_match('|-([\d]+)\.jpg$|',$requestedDir,$matches)){
	// если указан порядковый номер
	$arCodes = [
		[
			'num'=>false,
			'code'=>current(explode('.jpg',substr($requestedDir,1))),
			'mode'=>'full'
		],
		[
			'num'=>$matches[1],
			'code'=>preg_replace('|-([\d]+)\.jpg$|','',substr($requestedDir,1)),
			'mode'=>'additional'
		]
	];
}
$src = false;
$photo_id = false;
if(empty($arCodes)){
	$code = current(explode('.jpg',substr($requestedDir,1)));
	$CodePhotos = GetImagesByCode($code);
	if(!empty($CodePhotos)){
		if($mode=='full'){
			if($CodePhotos['DETAIL_PICTURE']){
				$src = $CodePhotos['DETAIL_PICTURE']['SRC'];
				$photo_id = $CodePhotos['DETAIL_PICTURE']['ID'];
			} elseif($CodePhotos['PREVIEW_PICTURE']){
				$src = $CodePhotos['PREVIEW_PICTURE']['SRC'];
				$photo_id = $CodePhotos['PREVIEW_PICTURE']['ID'];
			} elseif (!empty($CodePhotos['MORE_PHOTO'])){
				$src = $CodePhotos['MORE_PHOTO'][0]['SRC'];
				$photo_id = $CodePhotos['MORE_PHOTO'][0]['ID'];
			} else {
				$src = SITE_TEMPLATE_PATH.'/img/no_photo.jpg';
			}
		} elseif($mode == 'preview') {
			if($CodePhotos['PREVIEW_PICTURE']){
				$src = $CodePhotos['PREVIEW_PICTURE']['SRC'];
				$photo_id = $CodePhotos['PREVIEW_PICTURE']['ID'];
			} elseif($CodePhotos['DETAIL_PICTURE']){
				// TODO: реализовать ресайз изображения
				$src = $CodePhotos['DETAIL_PICTURE']['SRC'];
				$photo_id = $CodePhotos['DETAIL_PICTURE']['ID'];
			} elseif (!empty($CodePhotos['MORE_PHOTO'])){
				$src = $CodePhotos['MORE_PHOTO'][0]['SRC'];
				$photo_id = $CodePhotos['MORE_PHOTO'][0]['ID'];
			} else {
				$src = SITE_TEMPLATE_PATH.'/img/no_photo.jpg';
			}
		}
	}
} else {
	foreach ($arCodes as $arCode){
                // может быть порядковый номер, а может быть часть символьного кода товара
		$mode = $arCode['mode'];
		$CodePhotos = GetImagesByCode($arCode['code']);
		if(!empty($CodePhotos)){
			if($mode=='full'){
				if($CodePhotos['DETAIL_PICTURE']){
					$src = $CodePhotos['DETAIL_PICTURE']['SRC'];
					$photo_id = $CodePhotos['DETAIL_PICTURE']['ID'];
				} elseif($CodePhotos['PREVIEW_PICTURE']){
					$src = $CodePhotos['PREVIEW_PICTURE']['SRC'];
					$photo_id = $CodePhotos['PREVIEW_PICTURE']['ID'];
				} elseif (!empty($CodePhotos['MORE_PHOTO'])){
					$src = $CodePhotos['MORE_PHOTO'][0]['SRC'];
					$photo_id = $CodePhotos['MORE_PHOTO'][0]['ID'];
				} else {
					$src = SITE_TEMPLATE_PATH.'/img/no_photo.jpg';
				}
			} elseif($mode == 'additional') {
				if (!empty($CodePhotos['MORE_PHOTO']) 
                    && !empty($CodePhotos['MORE_PHOTO'][(intval($arCode['num'])-1)])){
					    $src = $CodePhotos['MORE_PHOTO'][(intval($arCode['num'])-1)]['SRC'];
					    $photo_id = $CodePhotos['MORE_PHOTO'][(intval($arCode['num'])-1)]['ID'];
				} 
			}
		}
	}
}

if($src){

	if($Scale){
		$srcSet = GetImagesSrcSet(['ID'=>$photo_id,'SRC'=>$src]);
		if(isset($srcSet[$Scale])){
			$src = $srcSet[$Scale];
		} else {
			// тут делаю сжатие в квадрат, но можно внести правки в шаблон и вытягивать нужные размеры ...
			$renderImg = \CFile::ResizeImageGet(
                    $photo_id, 
                    array("width" => $Scale, "height" => $Scale), 
                    BX_RESIZE_IMAGE_PROPORTIONAL, 
                    false, 
                    false, 
                    false
            );
			if (!isset($renderImg['src']) || strlen($renderImg['src']) <= 0)
			{
				$renderImg = $src;
			} else
			{
				$renderImg = $renderImg['src'];
			}
			$src = $renderImg;
		}
	}

	header("Content-type: image/jpeg");
	echo file_get_contents(Application::getDocumentRoot().$src);
	exit();
} else {
	Bitrix\Iblock\Component\Tools::process404(
		'',
		true, 
		true, 
		true, 
		false
	);
}

Вот таким нехитрым способом получили пути к изображениям в нужном нам формате!

Количество показов: 5
28.08.2020

Возврат к списку

Разработка сайта

Подайте заявку на разработку сайта на базе готового решения от компании 1С-Битрикс или одного из партнеров компании. Максимально подробно опишите, чему будет посвящен сайт, если это интернет-магазин - что он будет продавать, нужна ли мультиязычность, будут ли разные типы цен (розница, опт, крупный опт), будет ли интеграция с 1С, будет ли выгрузка товаров на различные торговые площадки...

Сопровождение сайта

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

Работы по сайту

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