Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Data Liberation] Tracking issue #1894

Open
17 of 47 tasks
adamziel opened this issue Oct 14, 2024 · 2 comments · Fixed by #1960
Open
17 of 47 tasks

[Data Liberation] Tracking issue #1894

adamziel opened this issue Oct 14, 2024 · 2 comments · Fixed by #1960
Labels
[Aspect] Data Liberation [Type] Project [Type] Tracking Tactical breakdown of efforts across the codebase and/or tied to Overview issues.

Comments

@adamziel
Copy link
Collaborator

adamziel commented Oct 14, 2024

Let's use this issue to track Data Liberation: Let's Build WordPress-first Data Migration Tools

Technical plumbing

Preliminary roadmap by use-case

  • WXR importer
    • Fork https://github.com/humanmade/WordPress-Importer. Give attribution to the original team, ping them and start a conversation
    • Port it to WP_XML_Tag_Processor
    • Start using that fork for importing WXR files in Playground
    • Rewrite the imported site URLs
    • Use AsyncHTTP\Client for fetching assets
    • Make it resumable if it fails halfway through
    • Report progress information to the user
    • Surface errors to the user, ask how to handle them
    • Publish it as a standalone plugin to start gathering feedback and bug reports
    • Include extension points to enable custom treatment of any block attribute, database row etc. See one of the GitHub discussions referenced in [Data liberation] wp_rewrite_urls() #1893
  • Static block markup editor
    1. Build a simple plugin to import and export .html files representing specific WordPress pages from GitHub.
    2. Ship a Blueprint that loads Playground Docs into Playground
    3. We need to have a real use-case for interacting with data liberation on a daily basis and this is one. It's a super low-friction way of maintaining the Playground documentation and WordPress-on-GitHub-pages in general. (cc @bph @akirk)
  • Reliable Playground ZIP export / import
    1. Fork the Sandbox Site plugin
    2. Improve the SQL export to make it streamable and ensure there are absolutely no issues with escaping
    3. Rewrite the exported and imported site URLs
    4. Include extension points to enable custom treatment of any block attribute, database row etc. See one of the GitHub discussions referenced in Kickoff Data Liberation: Let's Build WordPress-first Data Migration Tools #1888
    5. Consider shipping .sql files with the export to potentially enable importing the resulting .zip in a regular MySQL-based server environment
    6. ...anything else actually?
  • "Duplicate Playground" feature
    1. Iteration 1: Pipe the ZIP export to ZIP import
    2. Iteration 2: Mount /wordpress-new in the duplicated Playground instance, run the PHP export/import code to migrate the site from /wordpress there
    3. Iteration 3: Keep track of progress, make it resumable regardless of when the process is interrupted. This would enable exporting really big sites
  • Direct WordPress <-> WordPress transfer
    1. Conceptually, this is like running Duplicate Playground over the internet
    2. Important to keep track of progress and resources versions using a vector clock
    3. Export / Import UI with scope (users? posts? etc.), error info (image.jpg couldn't be fetched after 3 retries), and error resolution mechanism (specify a different url? upload that image? retry 4th time?)
  • Live WordPress <-> WordPress data sync
    1. Run the WordPress <-> WordPress transfer in a continuous way.
    2. This is not about collaborative editing in the block editor, although there is likely an overlap around data synchronization.

Here's a few more use-cases we'll likely tackle along the way, but they're not key milestones on their own:

  • Markdown workflow for editing existing documentation sites from GitHub
    • Markdown importer
    • Markdown exporter – migrate @dmsnell's Markdown <-> Block markup TypeScript converter from https://github.com/dmsnell/blocky-formats to PHP
    • Discuss using Playground to edit Playground docs, Gutenberg docs, and potentially all WordPress docs
    • Discuss using it as a drop-in static site generator replacement (e.g. Jekyll)
@adamziel adamziel added [Aspect] Data Liberation [Type] Project [Type] Tracking Tactical breakdown of efforts across the codebase and/or tied to Overview issues. labels Oct 14, 2024
@bgrgicak bgrgicak moved this from Inbox to In progress in Playground Board Oct 15, 2024
@adamziel adamziel moved this from In progress to Project: In Progress in Playground Board Oct 16, 2024
adamziel added a commit that referenced this issue Oct 28, 2024
A part of #1894.
Follows up on
#1893.

This PR brings in a few more PHP APIs that were initially explored
outside of Playground so that they can be incubated in Playground. See
the linked descriptions for more details about each API:

* XML Processor from
WordPress/wordpress-develop#6713
* Stream chain from adamziel/wxr-normalize#1
* A draft of a WXR URL Rewriter class capable of rewriting URLs in WXR
files

## Testing instructions

* Confirm the PHPUnit tests pass in CI
* Confirm the test suite looks reasonabel
* That's it for now! It's all new code that's not actually used anywhere
in Playground yet. I just want to merge it to keep iterating and
improving.
adamziel added a commit that referenced this issue Oct 30, 2024
A part of #1894.

Adds https://github.com/WordPress/blueprints-library as a git submodule
to the data-liberation package to enable easy code reuse between the
projects. I'm not yet sure, but perhaps moving all the PHP libraries to
the blueprints-library would make sense? TBD

No testing instructions. This is just a new submodule. No code changes
are involved.
adamziel added a commit that referenced this issue Oct 30, 2024
A part of #1894.

Adds https://github.com/WordPress/blueprints-library as a git submodule
to the data-liberation package to enable easy code reuse between the
projects. I'm not yet sure, but perhaps moving all the PHP libraries to
the blueprints-library would make sense? TBD

No testing instructions. This is just a new submodule. No code changes
are involved.
adamziel added a commit that referenced this issue Oct 31, 2024
…essor (#1960)

Merge `WP_XML_Tag_Processor` and `WP_XML_Processor` into a single
`WP_XML_Processor` class. This reduces abstractions, enables keeping
more properties as private, and simplifies the code.

Related to #1894
and WordPress/wordpress-develop#6713

 ## Testing instructions

Confirm the CI tests pass.
@github-project-automation github-project-automation bot moved this from Project: In Progress to Done in Playground Board Oct 31, 2024
@brandonpayton
Copy link
Member

@adamziel I think this may have been accidentally closed when #1960 was merged because it was "Related to" this one. There are a good number of tasks left unfinished, and this closing looks automated rather than intentional.

I'll reopen, and you can close again if it was intentional.

@adamziel
Copy link
Collaborator Author

adamziel commented Nov 2, 2024

Let's also review Automattic's VIP WXR importer for going from WXR reading to importing:

https://github.com/search?q=repo%3AAutomattic%2Fvip-go-mu-plugins%20wxr&type=code

adamziel added a commit that referenced this issue Nov 2, 2024
This PR introduces the `WP_WXR_Reader` class for parsing WordPress
eXtended RSS (WXR) files, along with supporting improvements to the XML
processing infrastructure.

**Note: `WP_WXR_Reader` is just a reader. It won't actually import the
data into WordPress** – that part is coming soon.

A part of #1894

## Motivation

There is no WordPress importer that would check all these boxes:

* Supports 100GB+ WXR files without running out of memory
* Can pause and resume along the way 
* Can resume even after a fatal error
* Can run without libxml and mbstring
* Is really fast

`WP_WXR_Reader` is a step in that direction. It cannot pause and resume
yet, but the next few PRs will add that feature.

## Implementation

`WP_WXR_Reader` uses the `WP_XML_Processor` to find XML tags
representing meaningful WordPress entities. The reader knows the WXR
schema and only looks for relevant elements. For example, it knows that
posts are stored in `rss > channel > item` and comments are stored in
`rss > channel > item > `wp:comment`.

The `$wxr->next_entity()` method stream-parses the next entity from the
WXR document and exposes it to the API consumer via
`$wxr->get_entity_type()` and `$wxr->get_entity_date()`. The next call
to `$wxr->next_entity()` remembers where the parsing has stopped and
parses the next entity after that point.

```php
$fp = fopen('my-wxr-file.xml', 'r');

$wxr_reader = WP_WXR_Reader::from_stream();
while(true) {
    if($wxr_reader->next_entity()) {
        switch ( $wxr_reader->get_entity_type() ) {
            case 'post':
                // ... process post ...
                break;

            case 'comment':
                // ... process comment ...
                break;

            case 'site_option':
                // ... process site option ...
                break;

            // ... process other entity types ...
        }
        continue;
    }

    // Next entity not found – we ran out of data to process.
    // Let's feed another chunk of bytes to the reader.

    if(feof($fp)) {
        break;
    }

    $chunk = fread($fp, 8192);
    if(false === $chunk) {
        $wxr_reader->input_finished();
        continue;
    }
    $wxr_reader->append_bytes($chunk);
}
```

Similarly to `WP_XML_Processor`, the `WP_WXR_Reader` enters a paused
state when it doesn't have enough XML bytes to parse the entire entity.

The _next_entity() -> fread -> break_ usage pattern may seem a bit
tedious. This is expected. Even if the WXR parsing part of the
`WP_WXR_Reader` offers a high-level API, working with byte streams
requires reasoning on a much lower level. The `StreamChain` class
shipped in this repository will make the API consumption easier with its
transformation–oriented API for chaining data processors.

### Supported WordPress entities

* posts – sourced from `<item>` tags
* comments – sourced from `<wp:comment>` tags
* comment meta – sourced from `<wp:commentmeta>` tags
* users – sourced from `<wp:author>` tags
* post meta – sourced from `<wp:postmeta>` tags
* terms – sourced from `<wp:term>` tags
* tags – sourced from `<wp:tag>` tags
* categories – sourced from `<wp:category>` tags

## Caveats

### Extensibility

`WP_WXR_Reader` ignores any XML elements it doesn't recognize. The WXR
format is extensible so in the future the reader may start supporting
registration of custom handlers for unknown tags in the future.

### Nested entities intertwined with data

`WP_WXR_Reader` flushes the current entity whenever another entity
starts. The upside is simplicity and a tiny memory footprint. The
downside is that it's possible to craft a WXR document where some
information would be lost. For example:

```xml
<rss>
	<channel>
		<item>
		  <title>Page with comments</title>
		  <link>http://wpthemetestdata.wordpress.com/about/page-with-comments/</link>
		  <wp:postmeta>
		    <wp:meta_key>_wp_page_template</wp:meta_key>
		    <wp:meta_value><![CDATA[default]]></wp:meta_value>
		  </wp:postmeta>
		  <wp:post_id>146</wp:post_id>
		</item>
	</channel>
</rss>
```
`WP_WXR_Reader` would accumulate post data until the `wp:post_meta` tag.
Then it would emit a `post` entity and accumulate the meta information
until the `</wp:postmeta>` closer. Then it would advance to
`<wp:post_id>` and **ignore it**.

This is not a problem in all the `.wxr` files I saw. Still, it is
important to note this limitation. It is possible there is a `.wxr`
generator somewhere out there that intertwines post fields with post
meta and comments. If this ever comes up, we could:

* Emit the `post` entity first, then all the nested entities, and then
emit a special `post_update` entity.
* Do multiple passes over the WXR file – one for each level of nesting,
e.g. 1. Insert posts, 2. Insert Comments, 3. Insert comment meta

Buffering all the post meta and comments seems like a bad idea – there
might be gigabytes of data.

## Future Plans

The next phase will add pause/resume functionality to handle timeout
scenarios:

- Save parser state after each entity or every `n` entities to speed it
up. Then also save the `n` for a quick rewind after resuming.
- Resume parsing from saved state.

## Testing Instructions

Read the tests and ponder whether they make sense. Confirm the PHPUnit
test suite passed on CI. The test suite includes coverage for various
WXR formats and streaming behaviors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Aspect] Data Liberation [Type] Project [Type] Tracking Tactical breakdown of efforts across the codebase and/or tied to Overview issues.
Projects
Status: Inbox
2 participants