Skip to content

fix(site-exporter): support resilient large transfers - #1689

Open
superdav42 wants to merge 3 commits into
mainfrom
fix/site-export-download-page
Open

fix(site-exporter): support resilient large transfers#1689
superdav42 wants to merge 3 commits into
mainfrom
fix/site-export-download-page

Conversation

@superdav42

@superdav42 superdav42 commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Stream large authenticated site-export ZIP downloads without retaining archive chunks in PHP output buffers.
  • Return metadata for HEAD export-download requests without streaming the archive body.
  • Link the site edit export widget to a site-filtered exports/downloads page.
  • Add a secure Server-side ZIP selector for imports uploaded with File Manager or SFTP, avoiding browser PHP upload limits.

Server-side import workflow

Network administrators can place large ZIP archives in wp-content/uploads/wu-site-exports/ and select them from the import modal.

  • The selector scans only the protected export folder and accepts ZIP filenames only.
  • The import handler resolves the selected file with realpath() and rejects traversal or paths outside that folder.
  • The existing URL and media-library workflows remain available.
  • When Delete ZIP After Import is selected, a successfully imported server-side ZIP is deleted only after its resolved path matches the queued archive.

Files changed

  • inc/site-exporter/class-export-download-handler.php: safe streaming and HEAD response handling.
  • inc/site-exporter/class-site-exporter.php: download-page navigation, exact export filtering, server-side import selection and validation.
  • tests/WP_Ultimo/Site_Exporter_Test.php: export-download and server-side ZIP selection regression coverage.

Verification

  • php -l inc/site-exporter/class-site-exporter.php && php -l tests/WP_Ultimo/Site_Exporter_Test.php
  • vendor/bin/phpcs inc/site-exporter/class-site-exporter.php tests/WP_Ultimo/Site_Exporter_Test.php
  • vendor/bin/phpstan analyse inc/site-exporter/class-site-exporter.php --memory-limit=1G
  • vendor/bin/phpunit --filter Site_Exporter_Test — 28 tests, 76 assertions
  • Commit hook passed staged PHPCS and PHPStan checks.

Summary by CodeRabbit

  • New Features

    • Added site-specific export views with tailored headings, empty states, and navigation to all exports.
    • Added dedicated download links from site management screens.
    • Added support for selecting server-side ZIP files during site and network imports.
  • Bug Fixes

    • Improved large export downloads, including metadata-only responses and safer file handling.
    • Fixed exact site export filtering and secured server-side ZIP validation and cleanup.
    • Improved error handling when import or site commands are unavailable.
  • Tests

    • Added coverage for filtering, download navigation, file streaming, and server-side ZIP availability.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b8388f40-2265-4372-bda9-ad69242be7ef

📥 Commits

Reviewing files that changed from the base of the PR and between f87d22e and 2574994.

📒 Files selected for processing (2)
  • inc/site-exporter/class-export-download-handler.php
  • inc/site-exporter/class-site-exporter.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • inc/site-exporter/class-site-exporter.php

📝 Walkthrough

Walkthrough

The change improves export download streaming, adds site-specific export views and download links, supports server-side ZIP imports, and resolves export and import commands dynamically when dependencies are unavailable.

Changes

Site export flow

Layer / File(s) Summary
Streaming download handling
inc/site-exporter/class-export-download-handler.php, tests/WP_Ultimo/Site_Exporter_Test.php
Download handling prepares the runtime, sends compatible headers, supports HEAD requests, handles read failures, detects client disconnects, and clears nested output buffers.
Site-scoped export views
inc/site-exporter/class-site-exporter.php, tests/WP_Ultimo/Site_Exporter_Test.php
The dashboard filters exports by exact site ID, shows site-specific headings and empty states, and links to the dedicated downloads page.
Server-side ZIP import flow
inc/site-exporter/class-site-exporter.php, tests/WP_Ultimo/Site_Exporter_Test.php
Site and network import flows accept validated server-side ZIP files, preserve selected filenames, generate protected download URLs, and clean up processed files.
Dynamic export and import commands
inc/site-exporter/class-site-exporter.php
Export and import commands resolve dynamically, report missing dependencies, clean pending imports, and execute through call_user_func.
Export and import validation tests
tests/WP_Ultimo/Site_Exporter_Test.php
Tests create and remove server-side ZIP fixtures, verify exact site filtering and download links, clear nested output buffers, and validate protected ZIP paths.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Admin
  participant SiteExporter
  participant ExportFolder
  participant ImportCommand
  Admin->>SiteExporter: Select server-side ZIP
  SiteExporter->>ExportFolder: Validate ZIP path
  ExportFolder-->>SiteExporter: Return validated local file
  SiteExporter->>ImportCommand: Process import metadata
  ImportCommand-->>SiteExporter: Return import result
  SiteExporter->>ExportFolder: Delete processed ZIP
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: resilient streaming for large site-export transfers.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/site-export-download-page

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

Performance Test Results

Performance test results for 603a292 are in 🛎️!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@inc/site-exporter/class-export-download-handler.php`:
- Around line 223-242: Update the download method around the file-streaming
logic to open the file before sending the 200 response and Content-Length
headers, returning an appropriate failure response if fopen fails. In the fread
failure branch, log the read error before breaking from the partial stream,
while preserving the existing cleanup and connection-abort handling.

In `@inc/site-exporter/class-site-exporter.php`:
- Around line 2729-2738: In the export-command availability branch of
wu_exporter_export(), delete the wu_pending_site_export_{$hash} transient before
returning the export-dependency-missing WP_Error. Preserve the existing error
response and only clear the pending export when class_exists($command_class) is
false.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a9baa59-0745-4d3c-941d-1c770c8128ab

📥 Commits

Reviewing files that changed from the base of the PR and between 965e826 and 232266f.

📒 Files selected for processing (3)
  • inc/site-exporter/class-export-download-handler.php
  • inc/site-exporter/class-site-exporter.php
  • tests/WP_Ultimo/Site_Exporter_Test.php

Comment thread inc/site-exporter/class-export-download-handler.php Outdated
Comment thread inc/site-exporter/class-site-exporter.php
@superdav42 superdav42 changed the title fix(site-exporter): repair large export downloads fix(site-exporter): support resilient large transfers Aug 1, 2026
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
tests/WP_Ultimo/Site_Exporter_Test.php (1)

338-358: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Assert that nested buffer contents are discarded.

test_export_download_handler_clears_nested_output_buffers() only checks the final buffer level. Add and assert on sentinel content; then verify that nested content does not reach the baseline buffer after prepare_streaming_environment().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/WP_Ultimo/Site_Exporter_Test.php` around lines 338 - 358, Update
test_export_download_handler_clears_nested_output_buffers() to write distinct
sentinel content into the nested output buffers before invoking
prepare_streaming_environment(). Afterward, capture the baseline buffer contents
and assert the sentinels are absent, while preserving the existing buffer-level
assertion and cleanup.
inc/site-exporter/class-site-exporter.php (2)

726-749: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Filter pending exports for site-scoped downloads.

render_export_import_dashboard() filters completed exports by $site_id, but the Pending Exports table still receives and renders all wu_exporter_get_pending() rows. Since pending site export entries store the target site ID as the first option, filter them by $site_id when it is set, or label the pending tables as network-wide activity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@inc/site-exporter/class-site-exporter.php` around lines 726 - 749, Update
render_wp_export_page() to filter pending_exports by the requested site ID when
$site_id is set, using the target site ID stored as the first option in each
pending export entry, before passing the results to
render_export_import_dashboard(). Preserve all pending entries when no site ID
is provided.

3090-3104: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Delete URL-based network-import ZIPs after import.

Network_Importer::import() can receive delete_file, but it does not delete the ZIP or attachment. handle_network_import() only deletes when server_zip_file resolves to the same server-side ZIP, leaving URL imports with remove_zip enabled with the source file intact. Add a fallback similar to handle_site_import() that resolves $options['zip_url'] to an attachment and deletes the attachment when no server-side ZIP path matches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@inc/site-exporter/class-site-exporter.php` around lines 3090 - 3104, Update
handle_network_import() so that when delete_file is enabled and no matching
server-side ZIP is deleted, resolve options['zip_url'] to its attachment and
delete that attachment as the fallback, mirroring handle_site_import(). Preserve
the existing server_zip_file path comparison and successful-import guard.
🧹 Nitpick comments (2)
tests/WP_Ultimo/Site_Exporter_Test.php (1)

331-332: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the site ID on the download link itself.

assertStringContainsString('site_id=1', $html) searches the entire HTML. The test can pass if the site ID appears in unrelated text or attributes while the anchor points to an unfiltered page.

Inspect the rendered anchor and assert that its href contains the dedicated downloads route and site_id=1.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/WP_Ultimo/Site_Exporter_Test.php` around lines 331 - 332, Update the
assertions in the site exporter test to inspect the rendered download anchor
rather than searching the entire HTML; verify that the anchor’s href uses the
dedicated downloads route and includes site_id=1, while preserving the existing
“Open Downloads Page” assertion.
inc/site-exporter/class-site-exporter.php (1)

2624-2700: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Solid path-traversal protection; consider validating inside get_server_export_url() too.

get_server_export_path() at Lines 2667-2685 correctly layers basename(), sanitize_file_name(), an extension check, and a realpath() prefix check to prevent path traversal. Every current caller invokes get_server_export_path() before get_server_export_url() and only uses the URL after the path check succeeds, so there is no exploitable path today.

get_server_export_url() (Lines 2694-2699) itself builds a URL directly from $file_name without re-validating it. If a future caller invokes get_server_export_url() on its own, an unsanitized $file_name would still produce a URL string (though file_exists() checks downstream would likely catch a wrong path before deletion). Add the same validation used in get_server_export_path() here, or have get_server_export_url() call get_server_export_path() internally, so the guarantee holds independent of call order.

🔒️ Proposed defensive fix
 	private function get_server_export_url(string $file_name): string {
 
+		if (! $this->get_server_export_path($file_name)) {
+			return '';
+		}
+
 		$upload_dir = wp_upload_dir();
 
 		return trailingslashit($upload_dir['baseurl']) . 'wu-site-exports/' . $file_name;
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@inc/site-exporter/class-site-exporter.php` around lines 2624 - 2700, Update
get_server_export_url() to independently validate the supplied file name using
the same basename, sanitize_file_name(), and .zip checks enforced by
get_server_export_path(), or reuse get_server_export_path() before constructing
the URL. Return an empty string or otherwise reject invalid selections, while
preserving the existing canonical uploads URL for validated server-side ZIP
names.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/WP_Ultimo/Site_Exporter_Test.php`:
- Around line 233-242: Update create_server_export_zip() to skip before
constructing ZipArchive when the class is unavailable, then require both
addFromString('manifest.json', '{}') and close() to return true before recording
the path in server_export_files. Skip the test when either operation fails so
only successfully created ZIP fixtures are accepted.

---

Outside diff comments:
In `@inc/site-exporter/class-site-exporter.php`:
- Around line 726-749: Update render_wp_export_page() to filter pending_exports
by the requested site ID when $site_id is set, using the target site ID stored
as the first option in each pending export entry, before passing the results to
render_export_import_dashboard(). Preserve all pending entries when no site ID
is provided.
- Around line 3090-3104: Update handle_network_import() so that when delete_file
is enabled and no matching server-side ZIP is deleted, resolve
options['zip_url'] to its attachment and delete that attachment as the fallback,
mirroring handle_site_import(). Preserve the existing server_zip_file path
comparison and successful-import guard.

In `@tests/WP_Ultimo/Site_Exporter_Test.php`:
- Around line 338-358: Update
test_export_download_handler_clears_nested_output_buffers() to write distinct
sentinel content into the nested output buffers before invoking
prepare_streaming_environment(). Afterward, capture the baseline buffer contents
and assert the sentinels are absent, while preserving the existing buffer-level
assertion and cleanup.

---

Nitpick comments:
In `@inc/site-exporter/class-site-exporter.php`:
- Around line 2624-2700: Update get_server_export_url() to independently
validate the supplied file name using the same basename, sanitize_file_name(),
and .zip checks enforced by get_server_export_path(), or reuse
get_server_export_path() before constructing the URL. Return an empty string or
otherwise reject invalid selections, while preserving the existing canonical
uploads URL for validated server-side ZIP names.

In `@tests/WP_Ultimo/Site_Exporter_Test.php`:
- Around line 331-332: Update the assertions in the site exporter test to
inspect the rendered download anchor rather than searching the entire HTML;
verify that the anchor’s href uses the dedicated downloads route and includes
site_id=1, while preserving the existing “Open Downloads Page” assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d9862e20-fa47-4082-bdbc-c406af95a5ee

📥 Commits

Reviewing files that changed from the base of the PR and between 232266f and f87d22e.

📒 Files selected for processing (2)
  • inc/site-exporter/class-site-exporter.php
  • tests/WP_Ultimo/Site_Exporter_Test.php

Comment on lines +233 to +242
$zip = new \ZipArchive();

if (true !== $zip->open($file_path, \ZipArchive::CREATE)) {
$this->markTestSkipped('Unable to create a server-side ZIP fixture');
}

$zip->addFromString('manifest.json', '{}');
$zip->close();

$this->server_export_files[] = $file_path;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
php -r 'exit(class_exists("ZipArchive") ? 0 : 1);'

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 175


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- candidate file ---\n'
fd 'Site_Exporter_Test\.php$' . || true

printf '\n--- relevant source lines ---\n'
file="$(fd 'Site_Exporter_Test\.php$' . | head -n1 || true)"
if [ -n "$file" ]; then
  sed -n '200,260p' "$file" | nl -ba -v200
fi

printf '\n--- server_export_files usages ---\n'
rg -n "server_export_files|addFromString|close\(\)" tests/WP_Ultimo/Site_Exporter_Test.php || true

printf '\n--- PHP ZipArchive return values probe ---\n'
tmp="$(mktemp)"
php - <<'PHP' "$tmp"
$zip = new ZipArchive();
$file = $argv[1];
echo "open_create=" . var_export($zip->open($file, ZipArchive::CREATE), true) . "\n";
echo "add_empty=" . var_export($zip->addFromString('manifest.json', '{}'), true) . "\n";
echo "close_after_empty=" . var_export($zip->close(), true) . "\n";

if (file_exists($file)) {
    `@unlink`($file);
}
echo "file_after_exists=" . var_export(file_exists($file), true) . "\n";
echo "file_after_is=" . var_export(is_file($file), true) . "\n";
PHP

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 266


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- candidate file ---'
fd 'Site_Exporter_Test\.php$' . || true

printf '%s\n' '--- relevant source lines ---'
file="$(fd 'Site_Exporter_Test\.php$' . | head -n1 || true)"
if [ -n "$file" ]; then
  sed -n '200,260p' "$file" | nl -ba -v200
fi

printf '%s\n' '--- server_export_files usages ---'
if [ -n "$file" ]; then
  rg -n "server_export_files|addFromString|close\(\)" "$file" || true
fi

printf '%s\n' '--- PHP ZipArchive return values probe ---'
tmp="$(mktemp)"
php - <<'PHP' "$tmp"
$zip = new ZipArchive();
$file = $argv[1];
echo "open_create=" . var_export($zip->open($file, ZipArchive::CREATE), true) . "\n";
echo "add_empty=" . var_export($zip->addFromString('manifest.json', '{}'), true) . "\n";
echo "close_after_empty=" . var_export($zip->close(), true) . "\n";

if (file_exists($file)) {
    `@unlink`($file);
}
echo "file_after_exists=" . var_export(file_exists($file), true) . "\n";
echo "is_file_after=" . var_export(is_file($file), true) . "\n";
PHP
rm -f "$tmp"

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 310


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- relevant source lines ---'
sed -n '200,260p' './tests/WP_Ultimo/Site_Exporter_Test.php'

printf '%s\n' '--- server_export_files usages ---'
rg -n "server_export_files|addFromString|close\(\)" './tests/WP_Ultimo/Site_Exporter_Test.php' || true

printf '%s\n' '--- PHP ZipArchive return values probe ---'
tmp="$(mktemp)"
php - <<'PHP' "$tmp"
$zip = new ZipArchive();
$file = $argv[1];
echo "open_create=" . var_export($zip->open($file, ZipArchive::CREATE), true) . "\n";
echo "add_empty=" . var_export($zip->addFromString('manifest.json', '{}'), true) . "\n";
echo "close_after_empty=" . var_export($zip->close(), true) . "\n";

if (file_exists($file)) {
    `@unlink`($file);
}
echo "file_after_exists=" . var_export(file_exists($file), true) . "\n";
echo "is_file_after=" . var_export(is_file($file), true) . "\n";
PHP
rm -f "$tmp"

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 2189


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- teardown cleanup lines ---'
sed -n '55,80p' './tests/WP_Ultimo/Site_Exporter_Test.php'

printf '%s\n' '--- ZIP API return values ---'
tmp="$(mktemp)"
php - <<'PHP' "$tmp" || :
<?php
$zip = new ZipArchive();
echo "class_exists=" . var_export(class_exists('ZipArchive'), true) . "\n";
echo "open_create=" . var_export($zip->open($tmp, ZipArchive::CREATE), true) . "\n";
echo "add_empty=" . var_export($zip->addFromString('manifest.json', '{}'), true) . "\n";
echo "close_after_empty=" . var_export($zip->close(), true) . "\n";
echo "file_after_exists=" . var_export(file_exists($tmp), true) . "\n";
echo "is_file_after=" . var_export(is_file($tmp), true) . "\n";
if (file_exists($tmp)) {
    unlink($tmp);
}
PHP
echo "exit=$?"

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 874


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- PHP ZipArchive return values probe ---'
tmp="$(mktemp)"
php -s <<'PHP'
<?php
$zip = new ZipArchive();
echo "class_exists=" . var_export(class_exists('ZipArchive'), true) . "\n";
echo "open_create=" . var_export($zip->open($argv[1], ZipArchive::CREATE), true) . "\n";
echo "add_empty=" . var_export($zip->addFromString('manifest.json', '{}'), true) . "\n";
echo "close_after_empty=" . var_export($zip->close(), true) . "\n";
echo "file_after_exists=" . var_export(file_exists($argv[1]), true) . "\n";
echo "is_file_after=" . var_export(is_file($argv[1]), true) . "\n";
if (file_exists($argv[1])) {
    unlink($argv[1]);
}
PHP
"$tmp"
rm -f "$tmp"

Repository: Ultimate-Multisite/ultimate-multisite

Length of output: 4629


Check ZipArchive fixture creation outcomes before accepting the archive.

create_server_export_zip() records the file path only after calling ZipArchive::addFromString() and ZipArchive::close(), and both results are ignored. A failed write or close can still return the ZIP path, making later file-based assertions unreliable.

If ext-zip is required by these tests, assert both operations return true; otherwise skip before instantiating ZipArchive if the class is unavailable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/WP_Ultimo/Site_Exporter_Test.php` around lines 233 - 242, Update
create_server_export_zip() to skip before constructing ZipArchive when the class
is unavailable, then require both addFromString('manifest.json', '{}') and
close() to return true before recording the path in server_export_files. Skip
the test when either operation fails so only successfully created ZIP fixtures
are accepted.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@superdav42

Copy link
Copy Markdown
Collaborator Author

Rebase needed — PR has merge conflicts and no origin:* label

This PR has merge conflicts against the default branch and lacks both origin:interactive and origin:worker labels. The pulse merge pass treats it as the label-agnostic stuck state (t3193) and surfaces it here with a one-shot rebase nudge.

To resolve

git fetch origin
git checkout fix/site-export-download-page
git rebase origin/main
# resolve any conflicts, then:
git push --force-with-lease

Or use the GitHub web UI's Update branch button if the conflicts are trivial enough.

Why this PR slipped through the existing nudges

The existing rebase-nudge family (_post_rebase_nudge_on_interactive_conflicting, _post_rebase_nudge_on_contributor_conflicting, _post_rebase_nudge_on_worker_conflicting) keys on the origin:* labels. PRs created without those labels — typically docs/migration commits made directly via the web UI or via a worker that didn't apply its origin label — were silently re-evaluated every cycle without surfacing.

Posted automatically by pulse-merge-stuck.sh (t3193 / GH#21895).


aidevops.sh v3.32.209 automated scan.

@superdav42

Copy link
Copy Markdown
Collaborator Author

Maintainer review needed: this PR is DIRTY (merge conflicts with the default branch) and does not meet the auto-rebase or auto-close criteria.

Reason: dirty-not-auto-resolvable

Options:

  • Rebase manually: git fetch origin && git rebase origin/$(git symbolic-ref --short refs/remotes/origin/HEAD | sed 's|origin/||') (or --strategy-option=union when TODO.md is the culprit).
  • Close as superseded: gh pr close 1689 --delete-branch.
  • Opt out of future sweeps: add the do-not-close label.

This comment is posted once per cooldown window (1800s) so the sweep stays quiet.

Triggered by pulse-dirty-pr-sweep.sh (t2350 / GH#19948).


aidevops.sh v3.32.210 automated scan.

@superdav42

Copy link
Copy Markdown
Collaborator Author

Rebase needed — branch has diverged from main

This PR has merge conflicts against main. The pulse merge pass does not auto-close contributor PRs (GH#20485) — this PR is kept open so your work is not lost.

To resolve

From a terminal:

git fetch origin
git rebase origin/main
# resolve any conflicts, then:
git push --force-with-lease

Or use the GitHub web UI's Update branch button if the conflicts are trivial enough for GitHub's web merger.

Why you're seeing this

Every pulse cycle the deterministic merge pass evaluates open PRs with merge conflicts. Contributor PRs (without origin:worker or origin:interactive labels) are always left open — the pulse will not close work it did not create. This nudge is posted once per PR to surface the conflict state in your notifications. If the PR still conflicts after rebasing, re-check manually via gh pr view 1689.

Posted automatically by pulse-merge-conflict.sh (GH#20485).


aidevops.sh v3.32.215 automated scan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant