Make WordPress Core

Opened 8 weeks ago

Last modified 3 weeks ago

#64439 new enhancement

Add admin download functionality for themes in wp-admin

Reported by: solankisoftware's profile solankisoftware Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version:
Component: Themes Keywords: 2nd-opinion needs-unit-tests
Focuses: administration Cc:

Description

This patch adds the ability to download themes from the wp-admin Themes page.
Currently, WordPress does not provide a download button in the admin UI.

Changes included:

  • wp-admin/includes/theme.php
  • wp-admin/themes.php

The patch adds:

  • A "Download" button next to each installed theme.
  • Proper security checks to ensure only users with 'manage_options' capability can download themes.
  • Sanitization of file names for safe download.

Testing:

  • Tested on a clean WordPress trunk setup.
  • Confirmed the download works for active and inactive themes.
  • Confirmed no PHP errors are thrown during the process.

Patch file attached: theme-admin-download.patch

Attachments (2)

theme-admin-download.patch (16.7 KB) - added by solankisoftware 8 weeks ago.
64439-theme-editor.patch (7.7 KB) - added by solankisoftware 6 weeks ago.
Fix :: Theme Editor Download button

Download all attachments as: .zip

Change History (18)

#1 @solankisoftware
8 weeks ago

  • Keywords needs-testing added

#2 @sabernhardt
6 weeks ago

  • Keywords 2nd-opinion added; needs-testing removed
  • Version trunk deleted

Similar to plugins (#64424), I think that a download link would be more appropriate on the Theme File Editor screen than anywhere on the Themes screen.

#3 @solankisoftware
6 weeks ago

Thanks for the feedback!
I agree that placing the download option in the Theme File Editor could be a better UX choice.
I’ll explore updating the patch to add the download functionality there instead of (or in addition to) the main Themes screen and share an updated patch soon.

@solankisoftware
6 weeks ago

Fix :: Theme Editor Download button

#4 @solankisoftware
6 weeks ago

  • Keywords needs-testing needs-unit-tests added

I’ve updated the patch to add the download functionality directly in the Theme File Editor screen, based on the feedback provided.

This patch includes changes only to:
wp-admin/theme-editor.php

What’s included:

  • Added a secure download option for themes from the Theme File Editor.
  • Proper capability checks (manage_options).
  • Nonce verification for security.
  • File name sanitization before download.

Testing:

  • Tested on a clean WordPress install.
  • Verified only administrators can access the download option.
  • Confirmed downloads work for both active and inactive themes.
  • No PHP warnings or notices observed.

Looking forward to feedback and happy to iterate further if needed. Thanks!

This ticket was mentioned in PR #10731 on WordPress/wordpress-develop by @noruzzaman.


5 weeks ago
#5

## Description
This Pull Request introduces the ability to download the currently selected theme as a ZIP file directly from the Appearance > Theme File Editor screen. This feature allows users to easily create a backup or copy of the theme they are editing without needing FTP or file manager access.

## Ticket
https://core.trac.wordpress.org/ticket/64439

## Acknowledgements
Props to @solankisoftware for the initial patch.
Thanks for the contribution! I noticed the file path in the original patch was slightly incorrect for the development environment (it was missing src/), so I have corrected the path to ensure it applies cleanly to the wordpress-develop repository.

## Changes

  • Added a "Download Theme" button below the theme editor form in [cci:7://file:///Users/noruzzamanrubel/Desktop/WPContribution/wordpress-develop/src/wp-admin/theme-editor.php:0:0-0:0 src/wp-admin/theme-editor.php].
  • Implemented a secure handler for the download_theme action.
  • Added logic to generate a ZIP archive of the theme using ZipArchive (with a fallback to PclZip for compatibility).
  • Ensures proper permission checks (edit_themes) and nonce verification before processing the download.

## Testing Instructions

  1. Navigate to Appearance > Theme File Editor.
  2. Select any theme from the dropdown menu (if multiple themes are installed).
  3. Scroll down to the bottom of the editor area.
  4. Click the newly added Download Theme button.
  5. Verify that a .zip file containing the full theme directory is downloaded successfully.
  6. Extract the ZIP file and confirm that all theme files are present and strictly correspond to the selected theme.

#6 @huzaifaalmesbah
5 weeks ago

Test Report & Feedback

Patch tested: https://github.com/WordPress/wordpress-develop/pull/10731

Feature Description

This patch adds a “Download Theme” button to the Theme File Editor, allowing users to download the active theme directly from the editor screen.

Steps to Reproduce or Test

  1. Navigate to Tools in the WordPress admin dashboard.
  2. Open Theme File Editor.
  3. Observe that the Download Theme button is not displayed before applying the patch.

Expected Results

  • ✅ A Download Theme button should be visible in the Theme File Editor.
  • ✅ Users should be able to download the selected theme as a ZIP file.

Environment

  • WordPress: 7.0-alpha-61215-src
  • PHP: 8.2.29
  • Server: nginx/1.29.4
  • Database: mysqli (Server: 8.4.7 / Client: mysqlnd 8.2.29)
  • Browser: Chrome 143.0.0.0
  • OS: macOS
  • Theme: Twenty Twenty-Five 1.4
  • MU Plugins: None activated
  • Plugins:
    • Test Reports 1.2.1

Actual Results

Before applying the patch

  • ❌ The Download Theme button was not available in the Theme File Editor.

After applying the patch

  • ✅ The Download Theme button is now displayed correctly.
  • ✅ The feature works as expected and allows downloading the theme.

Additional Issue: Theme Download Filename Format

When clicking the Download Theme button, the downloaded ZIP file name appears as:

  • ❌ Twenty-Twenty-Five.zip

This uses the theme display name with title-style capitalization and hyphen separation.

However, in the WordPress.org theme directory, the theme is distributed using the theme slug and version, for example:

  • ✅ twentytwentyfive.1.4.zip

Using the display name instead of the slug may feel inconsistent with WordPress standards and can give the impression that the theme slug is split or treated as separate words.

Expected / Suggested Behavior

  • ✅ Use the theme slug for the downloaded file name, optionally including the version, for example:
    • twentytwentyfive.zip
    • twentytwentyfive.1.4.zip

Using the slug-based filename would better align with WordPress theme directory conventions, improve consistency across downloads, and avoid confusion caused by title-formatted names.

UI / UX Suggestion

Currently, the Download Theme button appears in a separate row from the Update File button.

From a UI/UX perspective, it may be cleaner and more intuitive to place both Update File and Download Theme actions on the same row, as they are closely related actions. Grouping them together could improve visual consistency, reduce vertical spacing, and make related actions easier to discover.

Screenshots

https://i.ibb.co/sdLG9mWf/Huzaifa-20260114114616.png

#7 @noruzzaman
5 weeks ago

@huzaifaalmesbah Thanks for testing and for the feedback!

I have addressed the filename issue in the latest commits. The downloaded ZIP file now correctly uses the theme slug and version (e.g., twentytwentyfive.1.4.zip) to align with WordPress standards.

Please pull the latest changes and test again. Let me know if it works as expected now!

#8 @huzaifaalmesbah
5 weeks ago

Thanks for the update @noruzzaman!

I’ve tested the latest commits, and I can confirm that the filename issue is now fixed.
The downloaded ZIP correctly uses the theme slug and version format twentytwentyfive.1.4.zip

I also tested uploading the downloaded ZIP theme, and it installs and works correctly without any issues.

Everything is working as expected on my end. Great improvement. thanks for addressing this so quickly!

Download Time Upload This Zip Theme
https://i.ibb.co/RGGdW62D/Huzaifa-20260114132142.png https://i.ibb.co/QjMS3Vsp/Huzaifa-20260114132312.png

#9 @azaozz
5 weeks ago

  • Keywords has-patch removed

See https://core.trac.wordpress.org/ticket/64424#comment:7 re: similar functionality for plugins.

Looking at the PR, WordPress has a very good filesystem API. Thinking this should be implemented by using the API.

Last edited 5 weeks ago by azaozz (previous) (diff)

@noruzzaman commented on PR #10731:


5 weeks ago
#10

@westonruter Thanks for the review. I have addressed the feedback:

  1. Updated the file iteration logic to use RecursiveIteratorIterator::LEAVES_ONLY and FilesystemIterator::SKIP_DOTS to correctly skip dot directories.
  2. Added sanitize_file_name() to the ZIP filename in the Content-Disposition header.

#11 @solankisoftware
4 weeks ago

Hi everyone,
I’ve reviewed the full discussion and the latest updates look solid — especially the use of the Filesystem API and the improvements to filename handling.

I tested the current implementation and can confirm the ZIP download and install flow works correctly on my end.

Thanks to everyone involved for pushing this forward!

@noruzzaman commented on PR #10731:


4 weeks ago
#12

@westonruter I have resolved the issues. Could you please review and let me know if need any further changes. Thanks

@noruzzaman commented on PR #10731:


4 weeks ago
#13

Hey @westonruter, I've removed that redundant escaping you pointed out. Ready for another look whenever you're free!

@westonruter commented on PR #10731:


4 weeks ago
#14

@noruzzamans I don't have time to do an in-depth review, but I wanted to forward along this review via Gemini CLI:


I have analyzed the changes and formulated a review plan. I will now present the review findings, highlighting critical issues regarding symlink support and code duplication, along with minor suggestions.

### Review of Theme Download Feature

Here is a review of the changes in src/wp-admin/theme-editor.php and src/wp-admin/css/common.css.

#### Critical Issues

  1. Symlink Support & Path Exposure (Security/Reliability)
    • Issue: The code uses $theme_dir = $theme->get_stylesheet_directory();. In many development environments, themes are symlinked (e.g., from wp-content/themes/my-theme to /home/user/projects/my-theme).
      • RecursiveDirectoryIterator resolves symlinks by default or yields SplFileInfo objects where getRealPath() returns the *target* path (e.g., /home/user/projects/my-theme/style.css).
      • The check str_starts_with( $file_path, $theme_dir ) will fail because the file path is the *target* path, but $theme_dir is the *link* path. This results in an empty zip file.
      • PclZip Risk: When falling back to PclZip, PCLZIP_OPT_REMOVE_PATH is set to $theme_dir. If $filelist contains target paths (from getRealPath()) and $theme_dir is the link path, PclZip will fail to remove the path prefix. This causes the zip to contain the full absolute server path (e.g., home/user/projects/my-theme/style.css), which is a privacy/security risk.
    • Fix: Use realpath() to resolve the theme directory before iteration.
      $theme_dir = realpath( $theme->get_stylesheet_directory() );
      

#### Major Feedback

  1. Code Duplication (DRY)
    • Issue: The logic to iterate through files, validate them, and calculate relative paths is duplicated for both the ZipArchive and PclZip blocks. This increases maintenance burden and the risk of fixing bugs in one place but missing the other.
    • Suggestion: Refactor the file collection into a single loop that runs before the if ( class_exists( 'ZipArchive' ) ) block.
      1. Initialize $filelist = array();.
      2. Iterate RecursiveIteratorIterator once.
      3. Validate str_starts_with.
      4. Add valid files to $filelist.
      5. Pass $filelist to PclZip::create (it accepts an array) OR iterate $filelist for ZipArchive::addFile.

#### Minor / Nitpicks

  1. Double Slashes in Path
    • Issue: get_temp_dir() is documented to return a path *with* a trailing slash. Appending DIRECTORY_SEPARATOR results in double slashes (e.g., /tmp//theme.zip).
    • Suggestion: Remove DIRECTORY_SEPARATOR.
      $tmpfile = get_temp_dir() . $zipname . '-' . time() . '.zip';
      
  1. PHP 8.0 Compatibility
    • Observation: usage of str_starts_with (PHP 8.0+).
    • Verdict: Acceptable. Verified that src/wp-includes/compat.php polyfills this function for older PHP versions since WP 5.9.
  1. CSS Placement
    • Observation: The style .fileedit-sub .download-theme-form { margin-top: 8px; } is added to common.css.
    • Verdict: Acceptable, as theme-editor.php does not have its own specific stylesheet, though edit.css might have been a closer alternative. The 8px value is a bit magic; verify if a standard spacing variable or utility class matches the design system, but likely fine for admin.
  1. Admin Header Inclusion
    • Verification: Verified that the download logic is placed early in the file, before admin-header.php is included (which happens implicitly via admin.php or later in the file structure? Actually theme-editor.php structure shows headers are sent *before* HTML output). The placement is correct for header() calls.

### Proposed Plan

  1. Refactor src/wp-admin/theme-editor.php to resolve $theme_dir with realpath().
  2. Consolidate the file iteration logic to populate a $files array first, then use it for either archiving method.
  3. Remove the redundant DIRECTORY_SEPARATOR after get_temp_dir().

@noruzzaman commented on PR #10731:


4 weeks ago
#15

Hi @westonruter,

I have addressed all your feedback in the latest commit:

Added symlink support by resolving the theme directory with realpath().
Consolidated file iteration logic to avoid duplication (DRY).
Fixed the double-slash issue in the temporary zip path.

#16 @huzaifaalmesbah
3 weeks ago

  • Keywords needs-testing removed
Note: See TracTickets for help on using tickets.