Последняя часть цикла — о мелочах, которые не роняют сайт, а годами копят мусор и битые ссылки: куда импорт кладёт картинки, что происходит с товаром, который пропал из выгрузки, и почему адрес товара на тестовой копии не совпадает с рабочим. В конце — чек-лист импорта.
Это шестая, заключительная часть цикла «Массовый импорт в 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 есть таймаут на запрос: импорт — это сотни длинных запросов подряд.
Импорт плодит дубли, мусор в медиатеке или битые адреса?
Пришлите сайт — пройду по этому чек-листу и покажу, что чинить первым.
// загружаю комментарии…