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

Это шестая, заключительная часть цикла «Массовый импорт в WordPress».

Картинка ложится в папку по дате родителя

Импорт скачал фото сегодня для записи, созданной в октябре 2025 года, — а файл оказался в uploads/2025/10/, да ещё с суффиксом -5 в имени. Похоже на старый залипший файл, но это не баг: media_handle_sideload() берёт дату для папки из post_date родительской записи. Суффикс — из-за совпадения имени с прежними файлами в той же папке.

Проверять загрузку надо по post_date и post_parent самого вложения, а не по пути к файлу.

Второй аргумент media_handle_sideload — ID родителя. На одном сайте в нём была опечатка в имени переменной, и туда приходил null. Вложения создавались без родителя: по post_parent их не найти, вместе с объектом они не удаляются и копятся в uploads годами.

-- вложения без родителя
SELECT COUNT(*) FROM wp_posts WHERE post_type = 'attachment' AND post_parent = 0;

«Любой статус» не включает корзину

Импорт ищет запись по внешнему идентификатору, чтобы обновить, а не создать дубль. Обычно так:

get_posts(['post_type' => 'katalog', 'post_status' => 'any', 'meta_key' => 'remote_id', 'meta_value' => $rid]);

post_status => 'any' не включает trash и auto-draft. Если удаление у вас идёт через корзину, такой поиск перестаёт находить удалённый объект, и при следующем появлении в выгрузке импорт создаёт новый. Если корзина важна, перечислите статусы явно.

Товар пропал из выгрузки и вернулся

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

  • запоминает слаг в мете _wp_desired_post_slug;
  • меняет слаг на <слаг>__trashed — адрес товара сразу отдаёт 404.

Когда товар возвращается в выгрузку, обмен ищет его в корзине по точному совпадению названия и восстанавливает: wp_untrash_post() возвращает слаг из меты, адрес оживает. Механика рабочая, но держится на названии. Изменилось название хоть на пробел — корзинный товар не найдётся, создастся новый со слагом из нового названия. Старый адрес останется 404 навсегда, а в корзине — копия с картинками.

Это осознанное решение, а не баг: товар, который выпал из выгрузки и вернулся под другим названием, считается другим товаром. Но его надо принимать явно, понимая цену. Замер на одном каталоге: 288 живых товаров, 6 в корзине — ровно 6 мёртвых адресов, все снятые с продажи.

Слаг не меняется при переименовании — и тест расходится с рабочим сайтом

Обновление товара вызывает wp_update_post без post_name, поэтому при переименовании слаг остаётся прежним. Это хорошо для связи с учётной системой (она адресует товар по слагу) и для внешних ссылок. Но слаг становится историческим: модель давно переименовали, а адрес до сих пор собран из её первого названия.

Адреса между тестом и рабочим сайтом не переносить. На тестовой копии товары создавались позже, слаг сгенерирован из более нового названия — у одного и того же товара слаги на тесте и на рабочем сайте разные.

wp_update_post молча игнорирует чужие ключи

В том же обмене обновление товара передавало в wp_update_post ключи description и active вместо post_content и post_status. Ошибки нет, предупреждения нет — WordPress просто не знает таких полей. Создание товара использовало правильные ключи, поэтому новые товары получали описание, а существующие не обновляли ни описание, ни статус никогда.

Чинить такое вслепую нельзя: если учётная система присылает пустое описание, «исправленный» обмен затрёт тексты, которые годами правили в админке. Сначала выяснить, что приходит, потом решать.

Чек-лист импорта в WordPress

  • время записи одной записи измерено по логу, обработчики save_post разложены по времени (первая часть);
  • ни один обработчик не обходит весь каталог на каждом сохранении;
  • внутри цикла, который пишет записи или мету, нет get_posts/WP_Query — общее собрано до цикла (вторая часть);
  • сортировка однозначна: второй ключ ID при одинаковом поле порядка (третья часть);
  • переделка сверена со старой версией побайтово, на рабочем сайте — без записи (четвёртая часть);
  • группы ACF обновляются сырыми значениями с переводом ключей в имена, точечные правки — update_sub_field (пятая часть);
  • у вложений есть родитель, дата папки ожидаема;
  • поиск существующей записи видит корзину, если удаление идёт через неё;
  • решено, что делать с адресом удалённого товара: 404, редирект или 410;
  • ключи в wp_update_post — настоящие поля записи;
  • у пула PHP-FPM есть таймаут на запрос: импорт — это сотни длинных запросов подряд.

Импорт плодит дубли, мусор в медиатеке или битые адреса?

Пришлите сайт — пройду по этому чек-листу и покажу, что чинить первым.

· полезно?
АЧ
Алексей Чикун веб-разработчик, команда TRIOHEAD

Делаю сайты и сервисы целиком: от вёрстки до сервера. Пишу о том, что проверил на своих проектах.

Комментарии

// загружаю комментарии…

Комментарии публикуются после проверки. Ссылки и HTML не принимаются.