Перейти к содержимому

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

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

Механика

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

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

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

Отсюда меняющийся адрес изображения на витрине. Имя файла на диске платформа задаёт сама, поэтому после полной выгрузки ссылка другая, а для поисковика и товарной площадки это новая картинка.

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

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

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

Описание файла - тоже отдельное поле значения, а не часть самой картинки. Обмен кладёт туда имя файла из выгрузки, и написанный менеджером текст затирается вместе с атрибутом alt на витрине.

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

Шаги

  1. Понять, чем занято поле: детальная картинка держит номер файла, галерея - значения свойства.
  2. Снять привязку товара до сеанса: номера файлов, их описания и порядок значений свойства.
  3. Провести обмен и сравнить номера заново, чтобы увидеть, что именно платформа заменила.
  4. Снять в обработчике события те поля изображений, которые ведёт сайт, а не учётная система.
  5. Посчитать файлы без ссылок и убирать их отдельным регулярным проходом, а не руками.

Код

Смотрим, что записано у товара:

$res = CIBlockElement::GetList([], ['IBLOCK_ID' => 5, 'ID' => $elementId], false, false,
['ID', 'DETAIL_PICTURE', 'PREVIEW_PICTURE', 'PROPERTY_MORE_PHOTO']);
$row = $res->GetNext();
printf("детальная=%s доп=%s\n", $row['DETAIL_PICTURE'], $row['PROPERTY_MORE_PHOTO_VALUE']);
// в полях лежат номера файлов из b_file, а не пути к изображениям
// PROPERTY_MORE_PHOTO_VALUE отдаёт номера значений множественного свойства

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

Разворачиваем номер в файл:

$file = \CFile::GetFileArray($row['DETAIL_PICTURE']);
printf("%s / %s / %d байт\n", $file['SRC'], $file['ORIGINAL_NAME'], $file['FILE_SIZE']);
// метод вернёт false, если запись осталась, а файла на диске уже нет

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

Читаем значения галереи вместе с описаниями:

$props = CIBlockElement::GetProperty(5, $elementId, ['SORT' => 'ASC'], ['CODE' => 'MORE_PHOTO']);
while ($p = $props->Fetch()) { printf("файл=%s описание=%s\n", $p['VALUE'], $p['DESCRIPTION']); }
// порядок сортировки обязателен: false в третьем аргументе ломает фильтрацию

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

Добавляем свой снимок в галерею:

use Bitrix\Iblock\ORM\PropertyValue;
$photoId = \CFile::SaveFile(\CFile::MakeFileArray($path), 'iblock');
\CFile::ResizeImage($photoId, ['width' => 300, 'height' => 300], BX_RESIZE_IMAGE_EXACT, true);
$element->addTo('MORE_PHOTO', new PropertyValue($photoId, 'снимок менеджера'));
$element->save();
// файловое свойство принимает объект PropertyValue, а не номер числом
// ORM не уменьшает изображение сама: ресайз вызывают сразу после сохранения

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

Снимаем поля картинок из набора обмена:

AddEventHandler('iblock', 'OnBeforeIBlockElementUpdate', 'protectPictures');
function protectPictures(&$fields) {
if (($_REQUEST['mode'] ?? '') !== 'import') { return; } // ручную правку не трогаем
unset($fields['DETAIL_PICTURE'], $fields['PREVIEW_PICTURE']);
unset($fields['PROPERTY_VALUES']['MORE_PHOTO']); // и галерею целиком
}

Снятое из набора поле обмен не переписывает, и прежний номер файла остаётся в базе нетронутым. Тот же обработчик вешают на событие добавления элемента, иначе новые товары приедут с картинками.

Очищаем галерею перед своей записью:

CIBlockElement::SetPropertyValuesEx($elementId, 5, ['MORE_PHOTO' => false]);
CIBlock::clearIblockTagCache(5); // сам метод тегированный кеш не сбрасывает
// пустой массив множественное свойство не очистит: только false
// коды свойств здесь регистрозависимы, в отличие от выборки списком

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

Смотрим прирост файлов по дням сеансов:

SELECT DATE(TIMESTAMP_X) d, COUNT(*) n, ROUND(SUM(FILE_SIZE)/1048576) mb
FROM b_file WHERE MODULE_ID = 'iblock' GROUP BY d ORDER BY d DESC LIMIT 10;
-- ровный прирост в дни обмена означает, что каждый сеанс заводит файлы заново

Ровные всплески по расписанию обмена - признак повторной заливки тех же изображений. Число товаров при этом не меняется, и разница целиком складывается из отвязанных файлов.

Убираем найденных сирот:

$res = \CFile::GetList([], ['MODULE_ID' => 'iblock']); // фильтр по модулю и подкаталогу
while ($f = $res->Fetch()) {
if (isLinked($f['ID'])) { continue; } // своя проверка ссылок по всем свойствам
printf("%s %s\n", $f['ID'], \CFile::FormatSize($f['FILE_SIZE']));
\CFile::Delete($f['ID']); // удаляет и запись в b_file, и файл на диске
}
// вызов необратим: сначала прогон со снятым удалением, только потом боевой

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

Ограничения

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

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

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

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

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

Типичные проблемы

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

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

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

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

После каждой выгрузки фотографии товара дублируются.

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

Адрес детальной картинки меняется после каждого полного обмена.

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

В описании картинки вместо текста менеджера имя файла.

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

Каталог загрузок растёт быстрее каталога товаров.

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

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

Можно ли не выгружать картинки из 1С, а добавлять их вручную?

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

Почему полная выгрузка не обновляет картинки?

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

Как запретить изменение описания детальной картинки при обмене?

Описание приезжает вместе с самой картинкой, поэтому снимают всё поле целиком. Если картинки обновлять нужно, а описания нет, текст возвращают своим проходом после сеанса.

Куда деваются старые файлы после замены картинки?

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

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

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

Смежное

Первоисточник