Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
55f37f6
Trac 64439: Add theme download capability to theme editor
noruzzamans Jan 14, 2026
2029431
Trac 64439: Use theme slug and version for zip filename
noruzzamans Jan 14, 2026
5571e67
Trac 64439: Ensure zip archive contains theme directory
noruzzamans Jan 14, 2026
1c6e301
Update src/wp-admin/theme-editor.php
noruzzamans Jan 15, 2026
8a7c128
Update src/wp-admin/theme-editor.php
noruzzamans Jan 15, 2026
e3e909d
Update src/wp-admin/theme-editor.php
noruzzamans Jan 15, 2026
b260e24
Update src/wp-admin/theme-editor.php
noruzzamans Jan 15, 2026
0b5b476
Theme Editor: Address code review feedback
noruzzamans Jan 15, 2026
001683d
Update src/wp-admin/theme-editor.php
noruzzamans Jan 15, 2026
458f3a8
Update src/wp-admin/theme-editor.php
noruzzamans Jan 15, 2026
fa4a308
Update src/wp-admin/theme-editor.php
noruzzamans Jan 15, 2026
011801e
Update src/wp-admin/theme-editor.php
noruzzamans Jan 15, 2026
945ae0e
Merge branch 'trunk' into trac-64439-theme-editor
noruzzamans Jan 15, 2026
7c48a79
Address Copilot feedback: improve file iteration and sanitize zip fil…
noruzzamans Jan 15, 2026
a35ab57
Update src/wp-admin/theme-editor.php
noruzzamans Jan 19, 2026
8f532f7
Update src/wp-admin/theme-editor.php
noruzzamans Jan 19, 2026
723bc35
Update src/wp-admin/theme-editor.php
noruzzamans Jan 19, 2026
3fa369b
Admin: Implement security, cleanup, and reliability fixes for Downloa…
noruzzamans Jan 19, 2026
868b87f
Merge branch 'trunk' into trac-64439-theme-editor
noruzzamans Jan 19, 2026
d74bd6c
Merge branch 'trunk' into trac-64439-theme-editor
noruzzamans Jan 19, 2026
1657c2a
Merge branch 'trunk' into trac-64439-theme-editor
westonruter Jan 20, 2026
faec637
Update src/wp-admin/theme-editor.php
noruzzamans Jan 21, 2026
8981d92
Update src/wp-admin/theme-editor.php
noruzzamans Jan 21, 2026
ec9e0e4
Address core committer feedback: remove redundant escaping in theme d…
noruzzamans Jan 21, 2026
a2d7518
Theme: Address reviewer feedback for theme download feature.
noruzzamans Jan 21, 2026
e5b7d23
Merge branch 'trunk' into trac-64439-theme-editor
noruzzamans Mar 10, 2026
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
4 changes: 4 additions & 0 deletions src/wp-admin/css/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -3572,6 +3572,10 @@ img {
line-height: 180%;
}

.fileedit-sub .download-theme-form {
margin-top: 8px;
}

#file-editor-warning .file-editor-warning-content {
margin: 25px;
}
Expand Down
83 changes: 83 additions & 0 deletions src/wp-admin/theme-editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,82 @@
wp_die( __( 'The requested theme does not exist.' ) . ' ' . $theme->errors()->get_error_message() );
}

// Handle theme download action: create a zip of the theme and send it to the browser.
if ( 'download_theme' === $action ) {
if ( ! current_user_can( 'edit_themes' ) ) {
wp_die( '<p>' . __( 'Sorry, you are not allowed to download themes for this site.' ) . '</p>' );
}
Comment thread
noruzzamans marked this conversation as resolved.

// Verify nonce (accept either _wpnonce or nonce parameter for compatibility).
Comment thread
noruzzamans marked this conversation as resolved.
Outdated
if ( ! check_admin_referer( 'download-theme_' . $stylesheet ) ) {
wp_die( '<p>' . __( 'Security check failed.' ) . '</p>' );
}

$theme_dir = $theme->get_stylesheet_directory();

if ( ! is_dir( $theme_dir ) ) {
wp_die( '<p>' . __( 'Theme directory not found.' ) . '</p>' );
}

$zipname = $stylesheet;
$version = $theme->get( 'Version' );
if ( $version ) {
$zipname .= '.' . $version;
}

$tmpfile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $zipname . '-' . time() . '.zip';
Comment thread
noruzzamans marked this conversation as resolved.
Outdated

Comment thread
noruzzamans marked this conversation as resolved.
// Try native ZipArchive first, fall back to PclZip if needed.
if ( class_exists( 'ZipArchive' ) ) {
$zip = new ZipArchive();
if ( true !== $zip->open( $tmpfile, ZipArchive::CREATE ) ) {
wp_die( '<p>' . __( 'Could not create zip archive.' ) . '</p>' );
}

$files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $theme_dir ) );
Comment thread
noruzzamans marked this conversation as resolved.
Outdated
foreach ( $files as $file ) {
if ( $file->isDir() ) {
continue;
}
$filePath = $file->getRealPath();
$relativePath = substr( $filePath, strlen( $theme_dir ) + 1 );
$zip->addFile( $filePath, $stylesheet . '/' . $relativePath );
Comment thread
noruzzamans marked this conversation as resolved.
Outdated
}
Comment thread
noruzzamans marked this conversation as resolved.
Outdated

$zip->close();
} else {
// Use PclZip fallback bundled with WordPress admin.
require_once ABSPATH . 'wp-admin/includes/class-pclzip.php';

$filelist = array();
$files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $theme_dir ) );
Comment thread
noruzzamans marked this conversation as resolved.
Outdated
foreach ( $files as $file ) {
if ( $file->isDir() ) {
continue;
}
$filelist[] = $file->getRealPath();
Comment thread
noruzzamans marked this conversation as resolved.
Outdated
}

$archive = new PclZip( $tmpfile );
$result = $archive->create( $filelist, PCLZIP_OPT_REMOVE_PATH, $theme_dir, PCLZIP_OPT_ADD_PATH, $stylesheet );
if ( 0 === $result ) {
wp_die( '<p>' . __( 'Could not create zip archive.' ) . ' ' . esc_html( $archive->errorInfo( true ) ) . '</p>' );
}
Comment thread
noruzzamans marked this conversation as resolved.
}

if ( ! file_exists( $tmpfile ) ) {
wp_die( '<p>' . __( 'Failed to create theme archive.' ) . '</p>' );
}

// Send the file to the browser.
header( 'Content-Type: application/zip' );
header( 'Content-Disposition: attachment; filename="' . $zipname . '.zip"' );
Comment thread
noruzzamans marked this conversation as resolved.
Outdated
header( 'Content-Length: ' . filesize( $tmpfile ) );
readfile( $tmpfile );
Comment thread
noruzzamans marked this conversation as resolved.
@unlink( $tmpfile );
exit;
}

$allowed_files = array();
$style_files = array();

Expand Down Expand Up @@ -415,6 +491,13 @@

<?php wp_print_file_editor_templates(); ?>
</form>

<form action="theme-editor.php" method="post" class="download-theme-form">
<?php wp_nonce_field( 'download-theme_' . $stylesheet ); ?>
<input type="hidden" name="action" value="download_theme" />
<input type="hidden" name="theme" value="<?php echo esc_attr( $stylesheet ); ?>" />
<?php submit_button( __( 'Download Theme' ), 'secondary', '', false ); ?>
</form>
<?php
endif; // End if $error.
?>
Expand Down
Loading