Security
- Private Export Storage: Moved generated export ZIPs out of public uploads and into WordPress' private temporary directory. Exports now fail with an actionable error if the resolved temp directory is inside the WordPress web root.
- Export Directory Protection: Kept
.htaccessandindex.phpdefense-in-depth files in the export directory when the filesystem supports them. - Private API Removal: Removed usage of
_get_cron_array()(WordPress private/internal function) from cron failure diagnostics. Uses only public APIs (wp_next_scheduled(),wp_schedule_single_event()) now. - Filesystem Compatibility: Replaced
glob()withscandir()insse_bulk_cleanup_exports_handler()for cross-platform compatibility and consistency with WordPress filesystem conventions. - SSRF Hardening: File download functions now use
realpath()-resolved paths for all filesystem operations (readfile(),is_readable(),is_file()), preventing TOCTOU and SSRF attack vectors.sse_validate_file_output_security()now returns the resolved path for direct use. - CSP Compliance: Replaced inline
onclickJavaScript handler with externaljs/admin.jsfile to comply with Content Security Policy headers and prevent inline script execution risks. - Native Admin Actions: Switched export, download, and delete handlers to authenticated
admin-post.phpactions withcheck_admin_referer()validation. - Scheduled Cleanup Hardening: Scheduled export deletion now deletes only the validated export directory path rather than the raw cron argument.
- Symlink Export Hardening: WordPress file archive creation now skips symbolic links and verifies resolved paths stay within the WordPress root before adding them.
- WP-CLI Path Hardening: Removed PATH lookup for WP-CLI and now only executes WP-CLI from explicit allowed locations.
- Download Boundary Hardening: Final download path validation now uses normalized trailing-slash directory containment checks.
- Destructive Action Hardening: Manual export deletion now uses POST with nonce verification instead of a GET link.
- Extension Policy Tightening: Export download validation now allows only
.zipfiles. - Delete Containment: File deletion now uses WordPress'
wp_delete_file_from_directory()containment helper for generated export files.
Bug Fixes
- Documentation Fix: Corrected README.md Security Features section from "after 1 hour" to "after 5 minutes" to match actual cleanup timer.
- Unused Variable: Removed unused
$export_dir_namevariable assignment insse_exporter_page_html(). - phpcs Suppression: Removed unnecessary
phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscapedcomment on a line already properly escaped withesc_html(). - GEMINI.md Accuracy: Updated WP-CLI Integration section to reflect that WP-CLI is a required dependency (returns
WP_Errorif unavailable), replacing outdated "graceful fallback" language. - WP-CLI Language: Updated README.md and readme.txt from "when available" to "Requires WP-CLI" to match v2.0.0 behavior.
- PHPUnit Discovery: Renamed the generated WordPress compatibility test file/class pair to
EngineScriptSiteExporterTestso PHPUnit can discover it reliably. - WordPress Test Compatibility: Pinned the generated WordPress compatibility test job to PHPUnit 9.6 with Yoast PHPUnit Polyfills 4.x because the WordPress test library still calls PHPUnit APIs removed in PHPUnit 10+.
- WordPress Compatibility Coverage: Added PHP syntax linting, PHPUnit dependency verification, hook registration checks, constant checks, security helper tests, and a PHP 8.2/latest WordPress lowest-dependency matrix run.
- Release Packaging: Fixed release and CI package builds to include required
includes/,css/,js/,languages/, andreadme.txtfiles. - Release Package Hygiene: Excluded Composer files, CI metadata, tests, static-analysis stubs/configs, and source-only docs from generated plugin release packages.
- Archive Failure Reporting: File archive creation now returns a
WP_Errorwhen a file cannot be added instead of logging the failure and reporting success.
Architecture
-
EngineScript Archive Format: Updated exports to match the canonical EngineScript combined site archive: outer ZIP named
<site>_enginescript_site_export_<timestamp>.zip, rootmanifest.txt,database/<site>_db_<timestamp>.sql.gz, andfiles/<site>_files_<timestamp>.tar.gz. -
File Splitting: Split monolithic
enginescript-site-exporter.php(~1,400 lines) into a 112-line bootstrap file plus 7 focused include files underincludes/:helpers.php,security.php,admin-page.php,export.php,archive.php,cleanup.php,download.php. Each file is guarded byABSPATHcheck. -
Plugin File Constant: Added
SSE_PLUGIN_FILEconstant defined as__FILE__in bootstrap, used byincludes/admin-page.phpforplugin_dir_url()calls since__FILE__resolves to the include path, not the plugin root. -
Filter Name Constant: Replaced hardcoded
'sse_max_file_size_for_export'filter name string withSSE_FILTER_MAX_FILE_SIZEconstant for discoverability. -
Shell Output Sanitization: Added
sanitize_text_field()to WP-CLI error output insse_export_database()for defense-in-depth. -
Explicit Null Return: Added explicit
return null;tosse_process_file_for_tar()to match PHPDoc return typetrue|null. -
RuntimeException Catch: Changed
sse_add_wordpress_files_to_tar()to catchRuntimeExceptionspecifically before genericExceptionfallback. -
DirectoryIterator: Replaced
scandir()withDirectoryIteratorinsse_bulk_cleanup_exports_handler()for more efficient file iteration. -
PHPStan Level Increase: Increased PHPStan analysis level from 5 to 6, added
includes/directory to scan paths. -
Inline CSS Removal: Extracted 7 inline
styleattributes from admin page and success notice into dedicatedcss/admin.cssfile with semantic CSS classes (sse-section-spacing,sse-form-table,sse-warning-text,sse-action-button). -
Inline JS Removal: Extracted inline
onclickconfirmation dialog into dedicatedjs/admin.jsfile with asse-confirm-deleteform submit listener. -
Asset Enqueueing: Added
sse_enqueue_admin_assets()function hooked toadmin_enqueue_scriptswith page-slug check (tools_page_enginescript-site-exporter) to load CSS/JS only on the plugin's admin page. Useswp_localize_script()for i18n of JavaScript confirmation string. -
EngineScript Documentation: Clarified that the plugin does not detect EngineScript servers; it produces an EngineScript-compatible archive format usable from any supported WordPress server.
-
Copilot Instructions Revision: Rewrote
.github/copilot-instructions.mdto remove irrelevant references (WooCommerce, package.json, admin.php), consolidate redundant security subsections, add plugin-specific naming conventions (sse_,SSE_), and fix version file list. -
WP_Filesystem Helper: Extracted duplicated
WP_Filesysteminitialization from 4 functions into a singlesse_init_filesystem()helper that returnstrue|WP_Error, reducing ~40 lines of duplicated code to ~10. -
Removed Wrapper Functions: Inlined 3 pass-through wrapper functions (
sse_validate_download_request(),sse_validate_file_deletion(),sse_validate_export_file_for_deletion()) — callers now invoke the underlying functions directly. -
Download Validation Consolidation: Removed 2 redundant intermediate validation passes (
sse_validate_download_file_data(),sse_validate_download_file_access()) from the download flow. Entry validation and finalreadfile()security gate remain; intermediate re-validation of already-validated data removed. -
Path Resolution Consolidation: Consolidated 7-function-deep path resolution chain into a single
sse_resolve_file_path()function. Removed 6 single-use intermediary functions (sse_resolve_nonexistent_file_path(),sse_get_upload_directory_info(),sse_build_validated_file_path(),sse_validate_parent_directory_safety(),sse_construct_final_file_path(),sse_resolve_parent_directory(),sse_sanitize_filename()). -
Dead Code Removal: Removed no-op
sse_prepare_execution_environment()function and its call from the export flow. -
Debug Code Removal: Removed
sse_test_cron_scheduling()debug function that created/verified/removed a test cron event on every export — no longer needed after v2.0.0 cron fixes. -
Cron Logging Reduction: Reduced cron scheduling functions from 5+ log entries each to 2 (success/failure), keeping
DISABLE_WP_CRONdiagnostic on failure only.
PHP 8.2 Baseline
- Minimum PHP Version: Raised the supported PHP baseline to 8.2 across plugin metadata, Composer, PHPCS, documentation, and GitHub guidance.
- CI Compatibility Matrix: Updated WordPress compatibility testing to cover PHP 8.2, 8.3, 8.4, and 8.5.
- PHPUnit Tooling: Updated PHPUnit and Yoast PHPUnit Polyfills constraints for the PHP 8.2+ baseline.
- QA Tooling Constraints: Kept PHPCS on the stable WPCS/PHPCompatibility-compatible 3.13 line, added PHPMD as a Composer-managed dev dependency, and aligned workflow-installed coding standards with current stable package constraints.
- Type Declarations: Added parameter types and return types to all functions where deterministic.
- Short Array Syntax: Standardized all
array()constructor calls to short[]syntax throughout the plugin. - Null Coalescing Assignment: Replaced explicit null check + assignment pattern with
??=insse_should_exclude_file()file size cache, and?:Elvis operator for the ternary fallback. - PHPStan Array Shapes: Added PHPStan
array{}shape annotations to all functions accepting or returning associative arrays, resolving 10 level-6 "no value type specified in iterable type array" errors. - Trailing Whitespace: Removed trailing whitespace (tabs on blank lines) across
export.php,download.php,cleanup.php,admin-page.php, andsecurity.php. - JS File Header: Converted
admin.jsfile header from JSDoc (/** @package,@since) to plain block comment to avoid TSDoc linter false positives. - Bulk Cleanup Complexity: Extracted per-file deletion logic from
sse_bulk_cleanup_exports_handler()intosse_cleanup_expired_export_file()helper, reducing cyclomatic complexity from 13 to 8 and NPath complexity from 336 to under 200. - PHPStan Array Shape: Added
array{filepath: string, filename: string}shape annotation tosse_validate_export_file_path()return type insecurity.php, resolving level-6 error. - PHPStan Ignore Cleanup: Removed three obsolete
ignoreErrorspatterns fromphpstan.neon($post,$wp_query,$wpdb) that are now resolved by the WordPress stubs.
Installation
- Download the zip file
- Upload to your WordPress site through the Plugins > Add New > Upload menu
- Activate the plugin