요약: 워드프레스 글을 CodeIgniter 4로 옮겼는데, 이미지 파일을 못 옮긴 글에는
wp-content/uploads를 가리키는 깨진<img>가 남았다. 가능한 경로는 치환으로 살리고, 끝내 못 살린 것은 DOM 으로 파싱해 이미지·빈 링크·빈 문단까지 제거하는 spark 커맨드를 만들었다. dry-run 이 기본이라 지우기 전에 대상을 확인할 수 있다.
문제: 글은 옮겨졌는데 이미지는 비어 있다
워드프레스 WXR(export XML)을 읽어 게시판·페이지·미디어로 옮기는 wp:import 커맨드를 만들어 쓰고 있었다. 글 본문은 잘 넘어갔지만, 일부 글에는 엑스박스가 떠 있었다. 본문 HTML 이 아직 옛 주소를 가리키고 있었기 때문이다.
<a href="https://old-site.example/wp-content/uploads/2011/06/photo.jpg">
<img src="https://old-site.example/wp-content/uploads/2011/06/photo-300x225.jpg" alt="">
</a>
원인: 치환은 짝이 맞을 때만 된다
이관기는 첨부파일을 public/uploads/imported/ 로 복사하면서 "옛 상대경로 → 새 상대경로" 대응표(pathMap)를 만들고, 본문의 URL 을 이 표로 치환한다. 여기서 이미 몇 가지 함정을 처리해야 했다.
- 도메인 접두사(
https://old-domain/)까지 통째로 바꾸지 않으면 URL 일부만 바뀌어 여전히 깨진다. - 워드프레스가 자동 생성한 리사이즈본(
photo-300x225.jpg)은 WXR 에 별도 첨부파일로 없다. 접미사를 떼고 원본 경로로 다시 찾는다. - 본문의 폴더가 첨부파일의 폴더와 다르면 파일명만으로 찾되, 같은 파일명이 여러 개면 어느 것인지 알 수 없어 건너뛴다.
마지막 규칙이 핵심이다. 모호하면 치환하지 않는 쪽이 맞다. 엉뚱한 사진이 붙는 것보다 낫기 때문이다. 하지만 그 결과, 파일이 서버에 아예 없거나 짝을 못 찾은 이미지는 옛 주소를 단 채 그대로 남는다.

해결: 못 살린 이미지는 DOM 으로 지운다
치환으로 살릴 수 있는 만큼 살린 뒤에도 남은 wp-content/uploads 이미지는 복구할 방법이 없다. 그래서 정리 커맨드를 따로 만들었다. posts.content 와 pages.content 에서 해당 <img> 만 지운다.
정규식 대신 DOMDocument 를 썼다. 이미지만 지우면 다음 잔해가 남기 때문이다.
- 이미지 하나만 감싸던
<a>: 텍스트가 없으면 같이 제거 - 그 결과 텅 빈
<p>: 자식 요소가 없고 텍스트도 없으면 제거
foreach ($images as $img) {
$parent = $img->parentNode;
$parent?->removeChild($img);
// 그 이미지 하나만 감싸고 있던 <a> 는 텅 비었으니 같이 제거
if ($parent instanceof DOMElement && strtolower($parent->tagName) === 'a' && trim($parent->textContent) === '') {
$grandParent = $parent->parentNode;
$grandParent?->removeChild($parent);
$parent = $grandParent;
}
// 그 결과 텅 빈 <p> 도 함께 제거
if ($parent instanceof DOMElement && strtolower($parent->tagName) === 'p' && trim($parent->textContent) === '' && ! $this->hasElementChild($parent)) {
$parent->parentNode?->removeChild($parent);
}
}
loadHTML() 은 조각 HTML 을 받으면 <html><body> 를 덧붙이고 인코딩도 흔들린다. 그래서 <div id="__root__"> 로 감싸고 <?xml encoding="utf-8"?> 를 앞에 붙인 뒤 LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD 로 읽고, 저장할 때는 루트의 자식만 이어 붙였다.
사용법: 기본은 미리보기
본문을 지우는 작업이라 기본 동작은 dry-run 이다. 대상과 개수를 먼저 보고, 맞으면 --apply 를 붙인다.
# 미리보기: 어느 글에서 이미지가 몇 개 지워질지만 출력
php spark content:clean-broken-images
# 실제 반영
php spark content:clean-broken-images --apply
출력은 글 번호와 제목, 제거 개수를 한 줄씩 보여 준다.
[글] #123 제목 앞부분 — 이미지 3개 제거
dry-run: N건 / 이미지 M개 제거 예정. 반영하려면 --apply 를 붙여 실행하세요.
TODO: 실제 운영 DB 에서 정리된 글·이미지 개수를 여기에 적는다.
배운 점
- 이관 스크립트의 치환은 "확실할 때만" 하게 두고, 못 살린 것은 별도 단계로 분리하면 각 단계를 믿을 수 있다.
- HTML 본문을 고칠 때는 정규식보다 DOM 이 안전하다. 이미지를 지운 뒤 남는 빈
<a>,<p>까지 한 번에 다룰 수 있다. - 되돌리기 어려운 DB 일괄 수정은 dry-run 을 기본값으로 두고, 반영은 명시적 옵션으로만 한다. 운영 DB 에 돌리기 전에 백업은 따로 받아 둔다.

댓글 0개