# Migro - Content Migration & Deployment

Selective WordPress content migration. Deploy individual posts, pages and their
dependencies between staging and production, in either direction, without
cloning the whole site.

**Current version:** 2.6.2
**Requires:** WordPress 5.6+, PHP 7.4+

> This file is the contributor-facing README for the repository.
> The user-facing WordPress.org listing text lives in the `readme.txt` heredoc
> inside [build-free.sh](build-free.sh) (see "Builds" below). Do not duplicate
> release notes here.

## Scope

Migro moves **content**, never code. Themes, plugins and files outside the media
library are deliberately out of scope. If you need to move a whole site, use a
site-migration tool; Migro is for deploying what changed.

## What it migrates

- Posts, pages and custom post types, with slug, status, dates, excerpt,
  comment/ping status and author attribution
- Categories, tags and custom taxonomies, hierarchy preserved
- Post meta and custom fields, including ACF and ACF field groups
- Featured images, inline media, attached PDFs and video, with hash-based
  deduplication and preservation of the source's `YYYY/MM` upload folder
- Image alt text, kept in sync between the media library and post content
- Page-builder layouts for 8 builders (see `includes/builders/`)
- Front page / posts page / privacy page designations, remapped by sync key

## Direction

Both directions are implemented and are the core of the product:

- **Push** - send local content to the paired remote site
- **Pull** - bring remote content back into the local site

Items are matched across sites by a stored sync key (`_migro_sync_key`), not by
ID or slug, so repeat deployments update the same item instead of duplicating it.

## Architecture

```
wp-content-migration.php        Bootstrap, plugin header, require list
admin/                          Admin pages, wizard, assets
  assets/js/messages.js         The ONLY JS file allowed to hold strings
includes/
  class-connection-manager.php  Pair credentials and transport selection
  class-remote-transport.php    REST transport to the paired site
  class-database-adapter.php    Direct PDO access to the paired database
  class-migration-engine-loader.php
  class-mass-migrate.php        Batch entry point
  class-quick-migrate.php       Single-item entry point (Pro)
  class-sync-key-manager.php    Cross-site identity for migrated items
  class-dry-run.php             Preview diffs without writing
  class-encryption.php          Sodium envelope encryption for credentials
  class-backup-manager.php      Pre-deployment snapshots and restore
  class-caps.php                Capability policy (migrate_cap())
  builders/                     One module per page builder + registry
  ai/                           AI alt text and Yoast generation
  pro/                          Pro-only classes, stripped from the free build
tests/
  Unit/                         phpunit.xml
  Integration/                  phpunit-integration.xml, needs live site pairs
bin/                            i18n, pre-release gate, local deploy
tools/                          WP.org deploy, licence and diagnostic scripts
```

### Transport

A pair can talk over the REST transport or over a direct database connection.
The database path exists for Local-style development pairs where a socket is
available; the REST path is what most real staging/production pairs use.
Application Passwords (WordPress 5.6+) authenticate both.

### Database tables

| Table | Contents |
| --- | --- |
| `{prefix}migro_connections` | Saved pair configuration. Credentials encrypted, never plaintext. |
| `{prefix}migro_logs` | Per-item migration history: source, target, type, status, message. |

### Credential storage

Credentials are encrypted with **Sodium**, using an envelope scheme: a data
encryption key wrapped by a key derived from the site's salts. The raw key is
never stored in the database, and rotating the salts produces a graceful notice
rather than silent corruption. (Releases before 2.2.6 used base64 encoding;
that path only survives as an upgrade shim.)

## Admin UI

Top-level **Migro** menu (not under Tools):

- Batch Migration, Logs, Backups, Settings
- Schedules and AI Enhancements when the relevant Pro classes are loaded
- A hidden setup wizard page for first-run pairing

Editors can migrate by default. The Editor Access setting restricts the whole
menu to administrators. Everything routes through `Migro_Caps::migrate_cap()`.

## Builds

One repository, two builds:

| Script | Output | Notes |
| --- | --- | --- |
| `./build-free.sh` | WordPress.org package | Per-file allowlist. A new shared class without its own `cp` line will fatal the free build. Also generates `readme.txt` from a heredoc and renames the plugin header to "Migro Content Migrator". |
| `./build-pro.sh` | migro.dev package | Includes `includes/pro/`. |

`tools/deploy-free-wporg.sh` rsyncs the free build into the SVN checkout and
publishes. `svn-checkout/` is a working copy, not a source of truth, and is
structurally one tag behind after every deploy.

## Development

### Tests

```bash
php composer.phar install
vendor/bin/phpunit -c phpunit.xml               # unit
vendor/bin/phpunit -c phpunit-integration.xml   # needs configured site pairs
vendor/bin/phpstan analyse                      # level 5 + baseline
vendor/bin/phpcs                                # substance-only ruleset
```

### Internationalization

Read the Internationalization section of [CLAUDE.md](CLAUDE.md) before touching
any user-facing string, and run `bin/i18n.sh` after every string change. JS
strings live only in `admin/assets/js/messages.js`; no other `.js` file may call
`__()`.

### Release gate

`bin/deploy-to-local.sh` -> `bin/pre-release-check.sh` -> stress tests ->
manual verification on real installs. Builds themselves run no tests.

## Debugging

```php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
```

Then read `wp-content/debug.log`. The Logs tab records per-item outcomes for
every deployment.

## Known limitations

- Users and roles are not migrated
- Widgets, menus and theme customizations are not migrated
- Multisite is not supported
- WPBakery and Thrive builder support is experimental and warns in the UI
- Restore is origin-only: a backup can only be restored by the site that
  created it, because it is sealed with that site's salts

## Changelog

See the `== Changelog ==` section of the generated `readme.txt`
(source: [build-free.sh](build-free.sh)) for free-tier release notes, and
`docs/releases/` for the Pro changelogs.

## License

GPL v2 or later. See [LICENSE.txt](LICENSE.txt).
