Title: LUX Content Migration
Author: LUX Digest
Published: <strong>September 3, 2026</strong>
Last modified: September 3, 2026

---

Search plugins

![](https://ps.w.org/lux-content-migration/assets/icon-256x256.png?rev=3679234)

# LUX Content Migration

 By [LUX Digest](https://profiles.wordpress.org/luxsnap/)

[Download](https://downloads.wordpress.org/plugin/lux-content-migration.3.0.6.zip)

 * [Details](https://wordpress.org/plugins/lux-content-migration/#description)
 * [Reviews](https://wordpress.org/plugins/lux-content-migration/#reviews)
 *  [Installation](https://wordpress.org/plugins/lux-content-migration/#installation)
 * [Development](https://wordpress.org/plugins/lux-content-migration/#developers)

 [Support](https://wordpress.org/support/plugin/lux-content-migration/)

## Description

LUX Content Migration is installed on **both** sides of a WordPress-to-WordPress
migration:

 * On the **old (source) site**, it builds a self-contained export file in this 
   plugin’s own format (.wpmpx), with an automatic split into numbered parts if 
   your hosting’s upload limit is small.
 * On the **new (destination) site**, it imports that .wpmpx file category by category,
   bringing over posts, pages, comments (including reply/parent structure), and 
   authors (automatically matched by username when the same login exists on the 
   destination site).

This plugin is fully functional with no limits on the number of posts, pages, or
sites – there is no license key, no usage cap, and no locked feature anywhere in
this codebase.

#### What this plugin does

 * Export from the source site (including automatic splitting into parts if needed)
   and import into the destination site, both unlimited.
 * Comments (including reply/parent structure), author linking, and custom field/
   ACF relational ID reconnection.
 * Dry-run preview — see exactly how many posts, comments, and images an import 
   will involve before you run it, without creating anything.
 * Rollback — undo a finished category or page import, deleting the posts it created(
   downloaded images are left in place since other posts may still reference them).
 * Images referenced in migrated content keep pointing at the old site’s own URLs
   rather than being re-downloaded – so the old site needs to stay online for those
   images to keep displaying. A separate add-on plugin (distributed outside WordPress.
   org, see the plugin’s own site for details) adds actual image rehosting, redirect/
   404 management, background processing, and full media embedding on top of this
   plugin via standard WordPress filters/actions – this base plugin works completely
   on its own without it.

#### Security notes

 * Uploaded .wpmpx files are verified by a fixed signature and checksum before being
   parsed, and legacy WXR/XML uploads are parsed with a hardened XML reader (any`
   <!DOCTYPE>` declaration is stripped before parsing and external entity/network
   loading is disabled) to prevent XXE-style attacks from a malicious export file.
 * File uploads are validated against the request’s own upload metadata via `is_uploaded_file()`
   and moved through the WordPress Filesystem API into a non-listable, non-executable
   private folder that is cleared automatically after each file is processed.

### External services

This plugin does not connect to any external service. All processing (reading the
export file, creating posts/pages/comments/authors, custom field reconnection) happens
entirely on your own WordPress site’s server.

## Installation

 1. Install and activate LUX Content Migration on **both** the new site and the old
    site.
 2. On first visit to “LUX Content Migration” in the admin menu, choose whether this
    site is the migration destination (new site) or source (old site).
 3. On the source site: go to LUX Content Migration  Export, choose what to export,
    and download the resulting .wpmpx file (it may be split into numbered parts).
 4. On the destination site: go to LUX Content Migration  Import, upload that .wpmpx
    file (select all parts together if it was split), and import it category by category.

## FAQ

### Do I need to install this on the old site too?

Yes – install and activate it on both the old (source) site and the new (destination)
site. The source site builds the .wpmpx export file that the destination site then
imports.

### Will my images move too?

The URLs referencing images in your migrated posts/pages are kept exactly as they
were on the old site – this plugin does not download or re-host image files. As 
long as the old site stays online, those images continue to display normally on 
the new site.

### Does this plugin have any limits on the number of posts, pages, or sites?

No. Every feature in this plugin – export, import, comments, author linking, custom
field/ACF reconnection, dry-run preview, rollback – works with no count limit, no
time limit, and no license key required.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“LUX Content Migration” is open source software. The following people have contributed
to this plugin.

Contributors

 *   [ LUX Digest ](https://profiles.wordpress.org/luxsnap/)

[Translate “LUX Content Migration” into your language.](https://translate.wordpress.org/projects/wp-plugins/lux-content-migration)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/lux-content-migration/),
check out the [SVN repository](https://plugins.svn.wordpress.org/lux-content-migration/),
or subscribe to the [development log](https://plugins.trac.wordpress.org/log/lux-content-migration/)
by [RSS](https://plugins.trac.wordpress.org/log/lux-content-migration/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 3.0.6

 * Fix: `fwrite()` return values are now checked when writing the split .wpmpx export
   parts in `WPMP_Exporter::finalize_job()`. A partial write is retried; if the 
   disk genuinely can’t be written to, the export aborts, deletes the partial part
   files, and returns a clear error instead of silently producing a truncated/corrupted
   export archive.
 * Hardening: `$_POST['source_category']` / `$_POST['category_name']` are now passed
   through `sanitize_text_field()` before use in `WPMP_Import_Admin` (category-start,
   rollback, and preview handlers), consistent with the rest of the plugin’s input
   handling.
 * Clarity: the .wpmpx metadata-length header is now packed/unpacked with the explicit
   32-bit big-endian `N` format code (two halves) instead of the 64-bit `J`/`P` 
   codes, to remove any ambiguity about byte order for future readers/reviewers.
   Output bytes are unchanged, so this is fully compatible with .wpmpx files created
   by earlier 3.0.x versions.

#### 3.0.5

 * **Critical fix: with the paid add-on’s full license active, real image/video 
   rehosting silently did nothing** – content kept pointing at the old site’s URLs
   and featured images were always left empty, even though the add-on was correctly
   downloading files and creating attachments on the destination site. Root cause:`
   WPMP_Importer::process_attachment_batch()` passes its `$global` state (the URL-
   rewrite map and the old-attachment-ID  new-attachment-ID map) into `apply_filters('
   lux_content_migration_process_attachment_batch', ... )` – but PHP/WordPress break
   by-reference passing across `apply_filters()`, so the add-on’s own callback (
   which correctly re-reads and re-saves this state on every batch) was having its
   save immediately overwritten by this plugin’s own caller, which was still holding–
   and re-saving – the state exactly as it was _before_ that batch ran. This happened
   on every single batch tick, so the URL map and attachment-ID map could never 
   actually accumulate any entries, no matter how many images the add-on successfully
   rehosted. Fixed by re-reading the freshly-saved state back into this function’s
   reference parameter immediately after a filter callback reports it handled the
   batch, so this plugin’s own follow-up save can no longer clobber it. Free-plugin-
   only fix – no change needed on the add-on side, and no change for anyone running
   the free plugin standalone (which never populates this state in the first place).

#### 3.0.4

 * **Fixed a real bug:** the “내보내기 준비” (Export) screen had a second “📦 전체
   미디어 백업” button since the very first release, but this plugin’s own `ajax_export_start()`
   AJAX handler unconditionally rejected any request that button sent (`embed_files:
   1`), regardless of whether the paid add-on was installed or licensed – so that
   button never actually worked for anyone, including fully-licensed paying customers
   of the add-on. It’s been removed, and the underlying handler now asks the add-
   on (via a new filter, `lux_content_migration_embed_files_available`) whether 
   the request should be allowed before rejecting it – the same request that used
   to always fail now succeeds when the add-on is present and licensed for full 
   access.
 * Replaced that removed button with a single “이미지/영상까지 포함해서 완전히 담기”(
   include images/video) card next to the main export button. When the add-on reports(
   via the new filter above) that this is actually available, it renders the real,
   working checkbox and its own guidance text via two more new hooks (`lux_content_migration_render_embed_files_option`
   action, `lux_content_migration_set_embed_files_preference` action to persist 
   the choice) – otherwise this plugin shows a “log in to your license” or “learn
   about the Pro add-on” link depending on whether the add-on is installed at all(
   reusing the `lux_content_migration_pro_addon_installed`/`lux_content_migration_pro_settings_url`
   filters from 3.0.2).
 * Rewrote the guidance text on the export screen to precisely spell out all four
   real-world combinations (free vs. licensed add-on, crossed with “keeping the 
   old site online” vs. “closing/moving it soon”) instead of the previous one-line
   summary, based directly on user feedback about how confusing the previous wording
   was.
 * `render_screen_switcher()` now also shows a “자동 리디렉트 추천” (automatic redirect
   suggestions) pill on the destination-site role, linking to the same redirect-
   management screen the source role already links to – the add-on renders different
   content there depending on which role visits it. Previously this cross-screen
   link only appeared for the source role.

#### 3.0.3

 * **Screen reorganization (user feedback):** the “설정 / 라이선스” (Settings / 
   License) screen defaulted to a “Pro 애드온” tab (generic promotional copy, or–
   since 3.0.2 – a link to the add-on’s own license screen) shown before the “사이트
   역할” (Site Role) tab, even though site role is this screen’s actual, original
   purpose and the Pro-related content duplicates the add-on’s own “Pro 설정” menu
   item once installed. Removed the tab switcher entirely: this screen (renamed “
   역할 설정” / Role Setting, in both its menu label and page title) now shows the
   site-role card directly, with the Pro add-on card kept as a secondary section
   below it (still needed since a few “Pro 애드온 알아보기” links elsewhere in this
   plugin point here).

#### 3.0.2

 * Fixed the “Pro 애드온” tab on this plugin’s own “설정 / 라이선스” (Settings /
   License) screen always showing the generic “buy now” promotional copy, even when
   the paid add-on was already installed – so a customer who had already purchased
   and installed the add-on, and naturally looked for the license field on this 
   plugin’s own settings screen, would find no way to enter their license key there
   at all (the real license field lives on a separate screen the add-on registers
   for itself, under a different menu label). This screen now checks two new neutral
   filters, `lux_content_migration_pro_addon_installed` and `lux_content_migration_pro_settings_url`(
   both default to false/empty when no add-on is present, so standalone use of this
   free plugin is unaffected), and when an add-on is detected, shows a direct link
   to its actual license/settings screen instead of the promotional copy.

#### 3.0.0

 * Major structural change in response to WordPress.org review feedback (Guideline
   5, Trialware): removed every license-gated feature’s implementation from this
   plugin entirely, rather than merely hiding it behind a check. Image rehosting,
   full media backup (embedding attachment bytes), redirect/404 management, automatic
   redirect-suggestion CSV export, and background/unattended processing are no longer
   present in this codebase in any form – not even as dormant code behind a filter.
   This plugin now always does exactly what it does for free (posts, pages, comments,
   authors, custom fields, dry-run preview, rollback – no limits), with no license
   concept, no license screen, and no external license-verification calls anywhere
   in the code.
 * The removed functionality is available as a separate add-on plugin, distributed
   outside WordPress.org, which extends this plugin via a small set of neutral WordPress
   filters/actions (`lux_content_migration_is_pro`, `lux_content_migration_process_attachment_batch`,`
   lux_content_migration_embed_attachment_file`, `lux_content_migration_export_embed_item`,`
   lux_content_migration_post_imported`, `lux_content_migration_retry_failed_attachment`,`
   lux_content_migration_render_admin_notices`, `lux_content_migration_role_saved`,`
   lux_content_migration_deactivate`, `lux_content_migration_reset_all`, `lux_content_migration_pro_addon_installed`,`
   lux_content_migration_pro_settings_url`, `lux_content_migration_embed_files_available`,`
   lux_content_migration_render_embed_files_option`, `lux_content_migration_set_embed_files_preference`)–
   the same extensibility pattern used by other WordPress.org-hosted freemium plugins
   for their paid add-ons.
 * Removed bundled translation `.po`/`.mo` files and the `load_plugin_textdomain()`
   call – translations for WordPress.org-hosted plugins are handled automatically
   via translate.wordpress.org since WordPress 4.6.
 * Re-added the nonce check on the export-part download endpoint that had been removed
   in an earlier version based on a since-corrected misdiagnosis (the actual cause
   of that download failure was an unrelated double-`sanitize_file_name()` bug, 
   already fixed separately).

#### 3.0.1

 * Fixed a fatal error on “reset everything”: `WPMP_Store::reset_all()` still called`
   WPMP_Importer::disable_background_mode()`, a method that had already been deleted
   from `WPMP_Importer` when background processing moved to the Pro add-on. Replaced
   with a `lux_content_migration_reset_all` action so a Pro add-on can clean up 
   its own background-processing state on reset, without the free plugin needing
   to know anything about it.
 * Fixed the export manifest always reporting `embed_files` as false: the value 
   returned by the `lux_content_migration_export_embed_item` filter was computed
   per-batch but never written back onto the persisted export job, so `finalize_job()`
   could never see it. A Pro add-on embedding attachment bytes into the export file
   is now correctly reflected in the finished manifest.
 * Fixed the plugin package itself containing a duplicated, slightly-stale nested
   copy of its own files from an earlier draft – no functional impact on a correctly-
   installed copy, but cleaned up for a tidy WordPress.org package.
 * Rewrote `uninstall.php`: it still dropped the `wp_redirects`/`wp_redirect_404_log`
   database tables and deleted license/redirect/usage-limit options left over from
   the removed features. This plugin no longer creates any of that data (it belongs
   to the separate Pro add-on now), so uninstalling this plugin no longer touches
   it – each plugin only cleans up what it actually created.

#### 2.8.2

 * Fixed two leftover code comments still referencing the old main file name (`wp-
   content-migration.php`) instead of the current one (`lux-content-migration.php`)–
   harmless (comments aren’t executed), but corrected for accuracy.

#### 2.8.1

 * Fix (WordPress.org Plugin Check automated scan failures): replaced `move_uploaded_file()`(
   a forbidden direct filesystem function) with the WP_Filesystem API’s `move()`
   method for handling uploaded .wpmpx files – the `is_uploaded_file()` security
   check that verifies the file genuinely came from this request’s upload is kept
   in place, since WP_Filesystem doesn’t perform that check itself. Also updated“
   Tested up to” from 7.0 to 7.1 (current WordPress release).

#### 2.7.3

 * Fix: `Contributors` in readme.txt was set to `luxdigest` (the display name) instead
   of the actual WordPress.org login username (`luxsnap`) – required to match the
   account submitting this plugin for review.

#### 2.7.2

 * Updated “Tested up to” from 6.6 to 7.0 (current WordPress release) ahead of WordPress.
   org submission.

#### 2.7.1

 * Renamed the “팀” pricing tier to “에이전시(멀티사이트)” throughout, and corrected
   the license description that still said “슬롯 1개” (one slot) for a single license–
   it’s actually 2 (source + target, a pair), matching the fix made earlier this
   cycle.

#### 2.7.0

 * Added a complete English translation (`languages/wp-content-migration-en_US.po`/`.
   mo`) covering all 407 translatable strings in the plugin – on an English-locale
   WordPress install, every screen now displays in English instead of the Korean
   source text, which matters for WordPress.org review (typically done on an English
   test site) and for this plugin’s primarily English-speaking market. The Korean
   source strings in the code itself are unchanged – this is purely an additional
   translation layer, loaded automatically by WordPress via `load_plugin_textdomain()`
   based on the site’s locale. Verified the compiled `.mo` file parses correctly
   with Python’s own `gettext` module before shipping.

#### 2.6.8

 * Fix (WordPress.org submission blocker): the text domain was still `wp-media-porter`
   throughout the codebase (plugin header, `load_plugin_textdomain()`, and all 477
   translation function calls) even though the plugin was renamed to `wp-content-
   migration` back in 2.1.0 – WordPress.org requires the text domain to exactly 
   match the plugin slug, and a mismatch here would fail automated review. Corrected
   everywhere.
 * Removed the bundled `wp-media-porter-*.mo/.po/.pot` translation files – they 
   referenced the old (now wrong) text domain and, being from before roughly half
   of this plugin’s current screens existed, covered only a fraction of today’s 
   actual strings. Regenerated `languages/wp-content-migration.pot` from scratch
   against the current codebase (407 unique translatable strings) using a custom
   extractor built for this session (no gettext toolchain was available to run `
   wp i18n make-pot`). Translated `.po`/`.mo` files for specific languages (en_US,
   ja, etc.) are not included – see the note below.

#### 2.6.7

 * Reworded the “전체 미디어 백업” caption to lead with its biggest selling point:
   the file itself is a standalone backup – it can sit unused for weeks or months
   and still be imported later to recreate the site, not just a one-time transfer
   artifact. Also renamed “자동 리디렉트 추천”/”백그라운드 무인 처리” to “리디렉트
   관리”/”백그라운드 프로세스” in this caption for a punchier, more marketing-appropriate
   phrasing (the fuller technical description on the Settings/License screen is 
   unchanged).

#### 2.6.6

 * Reworded the caption under “전체 미디어 백업” to stop repeating the “미디어 주소
   자동 적용” line already covered by the paragraph above it, and instead briefly
   point out the two other paid-only features this plugin has (자동 리디렉트 추천,
   백그라운드 무인 처리) that aren’t specific to this button but are worth knowing
   about while looking at a paid-tier option.

#### 2.6.5

 * Fix: “선택한 항목 내보내기” is usable on both free and paid tiers (unlike “전체
   미디어 백업”, which is paid-only) – the caption under it now shows both outcomes(“
   무료: 적용 불가” / “유료: 자동 적용, 원본 사이트가 가져오기 끝날 때까지 켜져 
   있어야 함”) instead of a single line that only described one of them.

#### 2.6.4

 * Simplified the per-button captions under “선택한 항목 내보내기”/”전체 미디어 
   백업” to one line each (“새로운 미디어 주소 적용 불가” / “자동 적용”) – the fuller
   explanation was already given in the paragraph just above, so repeating it under
   each button read as cluttered. Added a “🛒 정식 라이선스 구매하기” button directly
   under the locked PRO option so there’s an immediate path to buy without needing
   to go to Settings first.

#### 2.6.3

 * Reworded the free-tier export copy (“이미지/영상은 주소만 포함됩니다”) – it read
   as if the addresses were being handled/processed somehow, when in fact nothing
   changes: the old site’s URLs are kept as-is with no rewriting at all, so closing
   or moving the source site later breaks every image/video and each one must be
   manually re-pointed. The paid “전체 미디어 백업” copy is now explicit about the
   actual contrast: it automatically re-addresses images/video to the new site.

#### 2.6.2

 * Fix (real gap): the license screen had no way to actually buy a license – it 
   only assumed you already had a key and were entering it. Someone landing here
   on the free tier had no path forward. Added a “🛒 정식 라이선스 구매하기” button(
   linking to wpcm.luxdigest.com’s pricing section) shown prominently when no key
   is saved yet, plus a smaller link for existing customers who need a key for another
   site.

#### 2.6.1

 * Import screen now shows a progress bar (matching the export screen) instead of
   only raw counts, and fixes a real bug where the “처리 중입니다…” message stayed
   on screen even after the job actually finished (counts showed 90/90 and 8/8 complete,
   but the status text and lack of visual completion made it look stuck) – the text
   now switches to “완료되었습니다” and the bar fills to 100% the moment the job’s
   last poll reports done, instead of only silently revealing the finalize button.

#### 2.6.0

 * Fix (the actual, deeper root cause of images not being relinked – upstream of
   the 2.5.2 fix, which only addressed rewriting and never actually fired for most
   images): `collect_needed_attachments()` decided whether an image was “needed”
   by checking if the exact original attachment URL string appeared in post content.
   Since WordPress normally inserts a resized variant into content (not the original),
   this exact-match check failed for the majority of real in-content images – meaning
   they were never even queued for download in the first place, so there was nothing
   for the URL rewriter to work with regardless of how good it was. Detection now
   also matches by filename (independent of any resize suffix), and separately scans
   post content directly for `src=`/`srcset=`/Gutenberg-block image URLs pointing
   at any other host – including images that were never registered as a WordPress
   attachment on the source site at all – and queues those too as on-the-fly entries.
 * Changed `rewrite_urls()` to replace any old-host reference to a given filename(
   with or without a resize suffix) with the new site’s plain original URL, rather
   than trying to reconstruct a matching resize suffix on the new site (which may
   not exist). Guarantees a working image over a possibly-broken exact-size match.

#### 2.5.2

 * Fix (real, likely the main cause of “이미지가 재정의가 안 된다”): `rewrite_urls()`
   only replaced post content URLs that matched an attachment’s original URL _exactly_.
   But WordPress normally inserts a resized variant into post content when you add
   an image via the editor (e.g. `photo-300x200.jpg` for “Medium” size), not the
   raw original filename – so the one case this whole feature exists for (images
   actually appearing in post content) was frequently left pointing at the old site,
   while only exact-match references (like a featured image, which does store the
   original URL) got rewritten correctly. Added a second pass that recognizes the`
   filename-WxHeight.ext` resize pattern and rewrites it to the same size suffix
   on the new site’s uploaded copy (WordPress regenerates the same intermediate 
   sizes automatically on upload, so this size normally exists). The existing srcset-
   stripping safety net still runs afterward for anything neither pass caught.

#### 2.5.1

 * Moved the split-part indicator from the end of the filename to the front: `NEWS-
   LUX-...-074327.wpmpx.part1of2` becomes `part1of2-NEWS-LUX-...-074327.wpmpx`. 
   The old suffix placement buried the only thing that differs between parts at 
   the very end of an already-long, mostly-identical filename, making multiple parts
   hard to tell apart at a glance in a file picker or folder view. The import side
   still recognizes both the new prefix format and the old suffix format, so parts
   already exported with an earlier version can still be uploaded.

#### 2.5.0

 * Renamed the “카테고리별 가져오기”/”페이지 가져오기” section labels to “파일 가져오기”–
   the whole screen is one unified “upload a file, it sorts itself out” action, 
   and naming a sub-step after “category” made it read as a separate, category-specific
   action rather than part of that.
 * Once a section is fully done, it no longer keeps showing the full instructional
   card (table headers, feature-help accordion, per-row status column) as if action
   were still needed – it collapses to a compact “✅ 완료” summary with just a rollback
   link per item. If a file has both finished and unfinished categories, only the
   unfinished ones stay in the working table; finished ones move to the compact 
   summary above it.

#### 2.4.1

 * Category import no longer requires manually picking or typing a target category.
   Leaving both fields blank now auto-matches by exact name (uses an existing category
   with the same name, or creates one) – matches the actual intent of this whole
   screen being “upload a file, it sorts itself out” rather than a manual mapping
   step. Manual selection is still available for when you want a different target.

#### 2.4.0

 * Redesigned the import screen from a fixed two-section layout (“페이지 가져오기”
   + “카테고리별 가져오기”, always both shown regardless of what the file actually
   contained) into a content-adaptive one. Uploading a file now shows a summary 
   of what’s actually inside it (categorized posts, uncategorized posts, pages, 
   standalone media), and only the sections that apply to that file appear – no 
   more an empty/irrelevant “카테고리별 가져오기” table on a pages-only file.
 * Fix (real gap, not previously reachable at all): posts with no category attached
   were invisible to the old catalog table (it only ever grouped posts that had 
   at least one category term), so a `.wpmpx` built from “글 선택” or any export
   containing uncategorized posts had no way to import them. They now get their 
   own “미분류 글 가져오기” section, importable as one flat batch like pages already
   were.
 * Fix (also a real gap): files built with “미디어만 내보내기” (posts/pages both
   empty, media only) had no import path at all – there was no button anywhere that
   would actually bring those attachments in. Added a “미디어 가져오기” section 
   for this case specifically (only shown when the file has no posts or pages at
   all, since media referenced by posts/pages is already picked up automatically
   while importing those).
 * Internal: generalized the “flat” (non-category) import engine – what used to 
   be page-specific job/preview/rollback/record-keeping code is now shared by pages,
   uncategorized posts, and media-only imports, keyed by type.

#### 2.3.0

 * Fix (real cause of the source site never becoming “정식(full)” automatically):
   this client defaulted the source site’s requested role to `export_only`, requiring
   a manual “정식 전환” click before Redirect Manager would ever unlock – but the
   product itself is sold as a pair (source + destination both become full on a 
   single-tier purchase; see the matching marketplace fix). The default requested
   role is now `full` for both site roles – entering a valid key activates both 
   sites automatically, no separate step. A source site that already has an `export_only`
   success recorded from an earlier version still needs one manual “정식 전환” click
   to move off that recorded state, after which this no longer applies.
 * Companion server-side fix required: `decoblocks-marketplace` 5.6.0 changes `SINGLE_MAX_ACTIVATIONS`
   from 1 to 2 to actually grant both slots on a single-tier purchase – this client-
   side default alone does nothing without that.

#### 2.2.4

 * Fix (real, confirmed cause of every export download failing): the download handler
   ran the requested filename through `sanitize_file_name()` a second time. WordPress’s
   own filename sanitizer inserts an underscore when it sees a multi-dot name with
   an unrecognized “middle extension” (exactly what `name.wpmpx.part1of2` looks 
   like to it) as a defense against extension-spoofing attacks – so the requested
   filename silently became `name.wpmpx_.part1of2`, which never matched the real
   file. The actual security boundary here is the whitelist check against the export
   manifest (already present), not sanitization, so the redundant second pass was
   removed entirely.
 * Clarified: the Redirect Manager lock screen and its diagnostic table now explicitly
   distinguish “라이선스 키가 유효함” (the key itself checks out) from “이 사이트가
   정식(role=full)” (this specific site has been granted the full tier) – these 
   are different things, and a site can legitimately show “유효함” while still being
   export_only, which isn’t a bug.

#### 2.2.3

 * The Redirect Manager lock screen now shows the raw license state stored on that
   specific site directly on the page: the key (masked), whether the server’s last
   response said valid/invalid, when it was last checked, and the exact failure 
   reason the server returned. No more guessing whether a key is missing, a request
   failed, or something else – it’s on screen.

#### 2.2.2

 * Fix: clicking a real export download link (added in 2.2.1) could still fail. 
   The download handler required a nonce (`check_admin_referer()`) – if it didn’t
   match for any reason, WordPress shows its own generic “다시 시도해주세요” page
   completely unrelated to this plugin, which just looked like “clicking gives an
   error.” Removed the nonce requirement for this action (capability check alone
   still protects it – it’s a read-only download of the admin’s own data), eliminating
   this failure mode outright. The three distinct failure cases inside `stream_part()`(
   no manifest, filename not recognized, file missing from disk) also no longer 
   share one generic message – each now states exactly what it found, so a future
   failure is diagnosable from the screen alone.

#### 2.2.1

 * Fix (real cause of “정식 전환을 눌러도 그대로다”): clicking “정식 전환” on the
   Redirect Manager screen redirected the result notice to the _Settings_ screen
   instead – and the notice-display code was hard-restricted to only render on Settings
   besides. If you stayed on Redirect Manager (the natural expectation, since that’s
   where you clicked), you’d never see the success or failure message at all – it
   silently rendered on a page you weren’t looking at. Both the redirect target 
   and the notice-display condition now point at Redirect Manager, so the result
   shows exactly where the click happened. Note: if the request itself still fails,
   the reason is now visible (“정식 전환에 실패했습니다 – 남은 활성화 자리가 없을
   수 있습니다”) – on a single-site license already fully activated on another site(
   e.g. the destination site), this is the intended limit, not a bug; a multi-site
   license is required to activate a second site.
 * Changed: removed the export auto-download entirely (iframe/tab/folder-picker 
   attempts across 1.9.5-2.2.0 were all silently blocked by the browser under some
   condition or other, with no visible error – “완료됐다는데 파일이 없다” kept recurring).
   Completed exports now show plain, real download links you click directly – a 
   genuine click can never be silently blocked by a browser, unlike any of the automated
   approaches tried so far.

#### 2.2.0

 * Fix (real root cause of “내보내기했는데 폴더에 파일이 없다” and “리디렉트가 라이선스
   오류에 걸린다”): this hosting’s persistent object cache doesn’t reliably reflect
   option values immediately after `update_option()` – the same class of bug already
   fixed once for `get_site_role()`. It turned out 13 other option reads across 
   the plugin had the identical exposure, including the two most consequential ones:`
   WPMP_License::get_key()`/`get_state()` (the single source of truth for every “
   is this license valid” check in the plugin, so a stale read here could make a
   freshly-verified license look invalid on the very next screen, e.g. Redirect 
   Manager) and `WPMP_Exporter::get_manifest()` (read by the hidden download iframe
   moments after the export job finishes – a stale “no export found” read meant 
   the download silently never happened, even though the UI showed a completion 
   message). Rather than patching each call site individually, added one shared `
   wpmp_get_option_fresh()` helper (cache-bypassing `get_option()`) and moved every
   one of this plugin’s own option reads through it.
 * Simplified: removed the “전체 미디어 백업” local-folder-picker feature (File 
   System Access API) entirely – it only worked in Chromium browsers and had repeatedly
   caused confusing failures (blank tabs, popup blocking, the picker re-prompting
   with no visible progress). Both “선택한 항목 내보내기” and “전체 미디어 백업”
   now behave identically: click, wait for the progress bar, and the finished file(
   s) download straight to the browser’s normal downloads folder – no folder selection
   step for either.

#### 2.1.1

 * Changed the licensing server domain from wpmediaporter.luxdigest.com to wpcm.
   luxdigest.com (`API_BASE` constant, Plugin URI). This is the address this plugin
   contacts to verify a license key – see “External services” above.

#### 2.1.0

 * Renamed the plugin from “WP Media Porter” to “LUX Content Migration” – all user-
   facing text (plugin name, screen headings, notices) updated. Internal technical
   identifiers (class names, option keys, the .wpmpx file extension, the product
   slug used to talk to the licensing server) are unchanged, since those are invisible
   to users and renaming them would require also updating the separate licensing
   server in lockstep for no visible benefit.
 * Fix: “이미지 주소 재적용” now behaves correctly per license tier instead of being
   a plain always-available button regardless of paid status. On a full license,
   image rehosting already happens automatically during import, so the button is
   no longer shown at all. On the free tier, the button is still shown (so people
   understand the feature exists) but clicking it no longer performs the action –
   it explains that image rehosting requires a full license and links to Settings,
   both in the UI and enforced again server-side in the AJAX handler.

#### 2.0.1

 * Fix (likely the real cause of import failures): the upload handler used `wp_handle_upload()`,
   WordPress’s helper for normal Media Library uploads. That function applies filename
   sanitization, extension checks, and upload-directory rules meant for regular 
   media – none of which fit a proprietary format with no registered mime type (`.
   wpmpx`, `.wpmpx.part1of3`), so it could reject or mangle these files even with`
   test_type` disabled. Studying All-in-One WP Migration showed it deliberately 
   does _not_ use `wp_handle_upload()` for its own `.wpress` archives – it simply
   copies PHP’s already-received temp file to its destination. Switched to the same
   approach: `is_uploaded_file()` verification, a sanitized filename, and `move_uploaded_file()`
   into the plugin’s private, externally-blocked folder. Authenticity is still verified
   afterward by the parser’s own magic-byte check.
 * Fix: export opened a new browser tab to trigger the download, which then stayed
   open showing a blank page (downloads don’t navigate, so nothing ever rendered
   there). Downloads are now triggered through a hidden iframe – no new tab appears
   at all, and nothing is left behind for you to close.

#### 2.0.0

 * Rearchitected the .wpmpx file format (v3) after studying how established migration
   plugins actually do this. The previous design base64-encoded every attachment’s
   bytes into JSON text – which inflates size ~33%, costs an encode/decode pass 
   on every file, and forces the JSON parser to handle enormous strings, all of 
   which contributed to the memory failures seen throughout the 1.9.x series. The
   format is now `[magic][8-byte metadata length][gzipped text metadata][raw binary
   section]`: attachment bytes are appended verbatim with no encoding, and each 
   item stores only an offset and size pointing into that section. Export copies
   file bytes 64KB at a time into the binary section during batch collection; import
   seeks directly to each offset and copies the exact byte range straight to disk.
   No base64 anywhere.
 * Removed the two-step export flow entirely – the export screen no longer keeps
   a “완료된 파일” panel with separate 다운로드 buttons and a 조각 정리 button. 
   Clicking export now runs the job and downloads the resulting file(s) directly,
   then returns to a clean screen.
 * Removed the now-unnecessary whole-file fallback parser and legacy base64 code
   paths.
 * Note: .wpmpx files produced by 1.9.x and earlier cannot be read by this version(
   the format changed). Re-export from the source site.

#### 1.9.8

 * Hardening: `parse_wpmpx()`‘s streaming path now also checks `defined('ZLIB_ENCODING_GZIP')`
   before using it, not just `function_exists('inflate_init')` – on PHP 8+, referencing
   an undefined constant is a fatal Error rather than a warning, so this closes 
   a possible (if narrow) crash path on unusual PHP/zlib builds by falling back 
   to the whole-file method automatically instead.
 * Verification: re-checked every file touched this session with three independent
   methods (a string/comment-aware brace scanner, a class-body-statement scanner,
   and a Pygments PHP-tokenizer-based scanner) – no further syntax issues found.
   A real PHP interpreter was not available in this environment to compile-check
   directly; if the import still fails after this version, the on-screen error added
   in 1.9.4 (or the exact wording of whatever screen appears) is needed to diagnose
   further, since static analysis alone has been exhausted.

#### 1.9.7

 * Fix: the auto-download added in 1.9.5 created a hidden `<a download>` element
   and clicked it several async steps (batch polling, finalize) after the original
   button click – browsers commonly treat that as no longer tied to a real user 
   gesture and silently block it, so nothing visibly happened even though the code“
   worked.” Replaced with the standard reliable pattern: a blank tab is opened synchronously
   at the moment of the click (still within the trusted user-gesture window, so 
   never blocked), and once the finished file’s URL is known, that already-open 
   tab’s location is set to it – setting the location of an already-open window 
   isn’t subject to the same popup-blocking rules as opening a new one later.

#### 1.9.6

 * Fix (the actual cause of every import – large or tiny – crashing with a blank“
   심각한 오류” screen, unaffected by any of the 1.9.2-1.9.5 fixes): the 1.9.5 rewrite
   of `parse_wpmpx()` left a leftover fragment of the _previous_ version’s code 
   sitting directly in the class body, outside any function – a bare `return` statement…

## Meta

 *  Version **3.0.6**
 *  Last updated **2 days ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 6.0 or higher **
 *  Tested up to **7.1**
 *  PHP version ** 7.4 or higher **
 * Tags
 * [export](https://wordpress.org/plugins/tags/export/)[import](https://wordpress.org/plugins/tags/import/)
   [media](https://wordpress.org/plugins/tags/media/)[migration](https://wordpress.org/plugins/tags/migration/)
   [redirect](https://wordpress.org/plugins/tags/redirect/)
 *  [Advanced View](https://wordpress.org/plugins/lux-content-migration/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/lux-content-migration/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/lux-content-migration/reviews/)

## Contributors

 *   [ LUX Digest ](https://profiles.wordpress.org/luxsnap/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/lux-content-migration/)