Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/scripts/check-wordpress-tested-up-to.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@

WORDPRESS_LATEST_VERSION = os.environ.get("WORDPRESS_LATEST_VERSION")
WORDPRESS_VERSION_CHECK_FILE = Path("wordpress-version-check.json")
METADATA_PATHS = {Path("enginescript-site-exporter.php"), Path("readme.txt")}
# Only readme.txt: Plugin Check (2.2 and later) refuses "Tested up to" in the plugin header
METADATA_PATHS = {Path("readme.txt")}
DEFAULT_EXCLUDED_DIRS = {
".git",
"build",
Expand Down
11 changes: 6 additions & 5 deletions .github/scripts/test-automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,12 @@ def findings(self) -> list[dict[str, object]]:

def test_metadata_changes_only_authoritative_headers(self) -> None:
findings = self.findings()
self.assertEqual(2, len(findings))
before = {name: (self.root / name).read_bytes() for name in ("README.md", ".private/review.md")}
self.assertEqual(
["enginescript-site-exporter.php", "readme.txt"], metadata.update_tested_up_to_entries(findings, "9.9")
)
self.assertEqual(1, len(findings))
before = {
name: (self.root / name).read_bytes()
for name in ("enginescript-site-exporter.php", "README.md", ".private/review.md")
}
self.assertEqual(["readme.txt"], metadata.update_tested_up_to_entries(findings, "9.9"))
self.assertEqual([], metadata.get_failures("9.9", self.findings()))
for name, content in before.items():
self.assertEqual(content, (self.root / name).read_bytes())
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@ see the current README and Security Policy for supported behavior and guidance.

## 2.1.3 - Unreleased

### Fixes

- **Stored Site Addresses in the Manifest**: `manifest.txt` now records the addresses stored in the `home` and `siteurl` options (`WP_HOME` and `WP_SITEURL` when `wp-config.php` defines them) as `home_url` and `site_url`, as EngineScript's own exports do. Before, it recorded `home_url()` and `site_url()`, which take the scheme of the admin page (`https` on an HTTPS admin page even when the stored address is `http`, and `http` when WordPress does not detect HTTPS) and pass through plugin filters such as those of multilingual plugins. EngineScript's importer now reads the site address and table prefix from these lines and moves the database's URLs from the stored address.
- **Address Constant Warning Removed**: The exporter page no longer warns when neither `WP_HOME` nor `WP_SITEURL` is defined, because EngineScript's importer reads the site address from `manifest.txt`. The warning about `wp-config.php` outside the WordPress directory stays.
- **Subfolder Sites Not Exported**: A site whose Site Address or WordPress Address has a path, such as a site in `/blog` or WordPress in its own `/wp` folder, is no longer exported. EngineScript only hosts sites at the root of their domain and its importer stops on such archives, so the exporter page shows an error, with both addresses, in place of the export form, and an export request is refused with the same error.

### Compatibility

- **Minimum WordPress 7.0**: Raise the minimum WordPress version to 7.0, the version Jetpack requires.
- **Tested Up To in the Readme Only**: The plugin header no longer declares "Tested up to"; `readme.txt` does. Plugin Check 2.2 refuses the field in the plugin header, because two copies can drift apart.
- **Minimum PHP 8.3**: Raise the minimum PHP version from 8.2 to 8.3. PHP 8.2 reaches end of life on December 31, 2026, and EngineScript servers no longer support it. A site on PHP 8.2 keeps its current version of the plugin until it moves to PHP 8.3 or newer. The tests cover PHP 8.3 to 8.5.

### Architecture
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ Database records older than seven days are removed when housekeeping, recovery,

### What does the EngineScript importer need from an export?

EngineScript's `vhost-import.sh` finds the site through a `wp-config.php` inside the files archive and reads the site address from `WP_HOME` or `WP_SITEURL` in that file. An export from a site that keeps `wp-config.php` above the WordPress directory, or that defines neither constant, is still a complete backup, but the importer stops on it. The exporter page warns when either is the case. For reference, `manifest.txt` also records `home_url`, `site_url`, `table_prefix`, and `wp_config_in_archive`.
EngineScript's `vhost-import.sh` finds the site through a `wp-config.php` inside the files archive. An export from a site that keeps `wp-config.php` above the WordPress directory is still a complete backup, but the importer stops on it, and the exporter page warns about it. `manifest.txt` also records `home_url`, `site_url`, `table_prefix`, and `wp_config_in_archive`. `home_url` and `site_url` are the addresses stored in the `home` and `siteurl` options (`WP_HOME` and `WP_SITEURL` when `wp-config.php` defines them), and the importer reads the site address and table prefix from the manifest, so neither constant is needed.

EngineScript only hosts sites at the root of their domain, such as `https://example.com`. A site with a path in its Site Address or WordPress Address, such as a site in `/blog` or WordPress in its own `/wp` folder, cannot be exported: the exporter page shows an error in place of the export form. A site at `www.example.com` can be exported, and EngineScript's importer offers to move it to `example.com` or to keep `www`.

### Can I use this plugin with non-EngineScript servers?

Expand Down
1 change: 0 additions & 1 deletion enginescript-site-exporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* Version: 2.1.2
* Author: EngineScript
* Requires at least: 7.0
* Tested up to: 7.1
* Requires PHP: 8.3
* Network: true
* License: GPL-3.0-or-later
Expand Down
21 changes: 21 additions & 0 deletions includes/admin-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,22 @@ function esse_render_import_requirement_warnings(): void {
);
}

/**
* Renders why this site cannot be exported, in place of the export form.
*
* @since 2.1.3
* @param string $message Error message from esse_get_site_layout_error().
*/
function esse_render_site_layout_error( string $message ): void {
wp_admin_notice(
esc_html( $message ),
[
'type' => 'error',
'additional_classes' => [ 'inline' ],
]
);
}

/**
* Renders the exporter page HTML interface.
*
Expand All @@ -507,6 +523,10 @@ function esse_exporter_page_html(): void {
<?php esse_render_import_requirement_warnings(); ?>
<?php esse_render_export_archive_list(); ?>
<h2><?php esc_html_e( 'Create an export', 'enginescript-site-exporter' ); ?></h2>
<?php $layout_error = esse_get_site_layout_error(); ?>
<?php if ( null !== $layout_error ) : ?>
<?php esse_render_site_layout_error( $layout_error ); ?>
<?php else : ?>
<p><?php esc_html_e( 'Click the button below to generate an EngineScript-compatible site archive containing your WordPress files and the database.', 'enginescript-site-exporter' ); ?></p>
<p><strong><?php esc_html_e( 'Warning:', 'enginescript-site-exporter' ); ?></strong> <?php esc_html_e( 'This can take a long time and consume significant server resources, especially on large sites. Ensure your server has sufficient disk space and execution time.', 'enginescript-site-exporter' ); ?></p>
<p class="sse-section-spacing">
Expand Down Expand Up @@ -551,6 +571,7 @@ class="sse-section-spacing sse-export-form"
<?php submit_button( __( 'Export Site', 'enginescript-site-exporter' ) ); ?>
<p class="sse-export-status" role="status"></p>
</form>
<?php endif; ?>
<?php esse_render_activity_records(); ?>
<hr>
<p>
Expand Down
9 changes: 6 additions & 3 deletions includes/archive.php
Original file line number Diff line number Diff line change
Expand Up @@ -521,9 +521,12 @@ function esse_write_engine_script_manifest( array $bundle_paths, string $site_id
*/
function esse_get_engine_script_manifest_site_lines( bool $has_wp_config ): string {
$database = esse_get_wordpress_database();
$values = [
'home_url' => home_url(),
'site_url' => site_url(),
// The stored addresses (WP_HOME and WP_SITEURL when wp-config.php defines them): the importer
// moves the URLs in the database from these. home_url() and site_url() can differ from them,
// because they take the scheme of the current request and pass through plugin filters.
$values = [
'home_url' => esse_normalize_string_value( get_option( 'home' ) ),
'site_url' => esse_normalize_string_value( get_option( 'siteurl' ) ),
'table_prefix' => $database->prefix ?? '',
'wp_config_in_archive' => $has_wp_config ? 'yes' : 'no',
];
Expand Down
48 changes: 40 additions & 8 deletions includes/export.php
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,12 @@ function esse_handle_export(): void {
esse_redirect_to_exporter_page();
}

$layout_error = esse_get_site_layout_error();
if ( null !== $layout_error ) {
esse_show_error_notice( $layout_error );
esse_redirect_to_exporter_page();
}

$lease = esse_acquire_export_lease();
if ( is_wp_error( $lease ) ) {
esse_show_error_notice( $lease->get_error_message() );
Expand Down Expand Up @@ -674,13 +680,43 @@ function esse_validate_export_request(): int|false {
return 0;
}

/**
* Gets the reason this site cannot be exported, when it is not at the root of its domain.
*
* EngineScript serves each site, and WordPress, at the root of its domain. A site
* in a subfolder (example.com/blog), or with WordPress in its own folder
* (WordPress Address example.com/wp), cannot be imported, so it is not exported.
* The stored options are read, as for the manifest.
*
* @since 2.1.3
* @return string|null Error message, or null when the site can be exported.
*/
function esse_get_site_layout_error(): ?string {
$home = esse_normalize_string_value( get_option( 'home' ) );
$siteurl = esse_normalize_string_value( get_option( 'siteurl' ) );

foreach ( [ $home, $siteurl ] as $address ) {
$path = wp_parse_url( $address, PHP_URL_PATH );
if ( is_string( $path ) && '' !== trim( $path, '/' ) ) {
return sprintf(
// translators: 1: Site Address (URL) setting, 2: WordPress Address (URL) setting.
__( 'This site cannot be exported. EngineScript only hosts sites at the root of their domain, such as https://example.com. It does not support sites in a subfolder (such as /blog), or WordPress in its own folder (such as /wp). Site Address: %1$s. WordPress Address: %2$s.', 'enginescript-site-exporter' ),
$home,
$siteurl
);
}
}

return null;
}

/**
* Gets warnings about conditions that EngineScript's importer does not accept.
*
* The importer finds the site through a wp-config.php inside the files archive
* and reads the site address from WP_HOME or WP_SITEURL in that file. An
* export from a site that does not meet this is still a complete backup, so
* these are warnings and never stop the export.
* The importer finds the site through a wp-config.php inside the files archive.
* It reads the site address from manifest.txt, so WP_HOME and WP_SITEURL are not
* needed. An export from a site that does not meet this is still a complete
* backup, so these are warnings and never stop the export.
*
* @since 2.1.1
* @return string[] Warning messages; empty when the archive can be imported.
Expand All @@ -692,10 +728,6 @@ function esse_get_import_requirement_warnings(): array {
$warnings[] = __( 'This site keeps wp-config.php outside the WordPress directory, so the file is not part of the export. The EngineScript importer needs wp-config.php in the export.', 'enginescript-site-exporter' );
}

if ( ! defined( 'WP_HOME' ) && ! defined( 'WP_SITEURL' ) ) {
$warnings[] = __( 'Neither WP_HOME nor WP_SITEURL is defined in wp-config.php. The EngineScript importer reads the site address from one of them.', 'enginescript-site-exporter' );
}

return $warnings;
}

Expand Down
Loading
Loading