Skip to content

Conversation

@noruzzamans
Copy link

@noruzzamans noruzzamans commented Jan 14, 2026

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 [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.

@github-actions
Copy link

Hi @noruzzamans! 👋

Thank you for your contribution to WordPress! 💖

It looks like this is your first pull request to wordpress-develop. Here are a few things to be aware of that may help you out!

No one monitors this repository for new pull requests. Pull requests must be attached to a Trac ticket to be considered for inclusion in WordPress Core. To attach a pull request to a Trac ticket, please include the ticket's full URL in your pull request description.

Pull requests are never merged on GitHub. The WordPress codebase continues to be managed through the SVN repository that this GitHub repository mirrors. Please feel free to open pull requests to work on any contribution you are making.

More information about how GitHub pull requests can be used to contribute to WordPress can be found in the Core Handbook.

Please include automated tests. Including tests in your pull request is one way to help your patch be considered faster. To learn about WordPress' test suites, visit the Automated Testing page in the handbook.

If you have not had a chance, please review the Contribute with Code page in the WordPress Core Handbook.

The Developer Hub also documents the various coding standards that are followed:

Thank you,
The WordPress Project

@github-actions
Copy link

github-actions bot commented Jan 14, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props noruzzaman, westonruter.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions
Copy link

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Updates the downloaded zip filename to follow the 'slug.version.zip' format (e.g., 'twentytwentyfour.1.0.zip'), aligning with WordPress standards and improving file organization.
Fixes the directory structure within the downloaded zip file. Previously, files were added to the root of the zip. This change ensures all files are nested within a folder named after the theme slug (stylesheet), adhering to standard theme packaging conventions.
noruzzamans and others added 5 commits January 15, 2026 10:16
Co-authored-by: Weston Ruter <westonruter@gmail.com>
Co-authored-by: Weston Ruter <westonruter@gmail.com>
Co-authored-by: Weston Ruter <westonruter@gmail.com>
Co-authored-by: Weston Ruter <westonruter@gmail.com>
- Replace inline style on download form with CSS class.
- Restore newline at end of file.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request introduces a theme download feature to the WordPress Theme File Editor, allowing users to download the currently selected theme as a ZIP archive. The implementation provides both a UI button and a secure backend handler.

Changes:

  • Added a "Download Theme" button below the theme editor form
  • Implemented secure backend logic to create ZIP archives using ZipArchive (with PclZip fallback)
  • Added minimal styling for the download form

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.

File Description
src/wp-admin/theme-editor.php Added download handler with permission checks, nonce verification, and ZIP generation logic; added download form UI
src/wp-admin/css/common.css Added minimal styling for the download theme form

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

noruzzamans and others added 6 commits January 15, 2026 13:58
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@noruzzamans
Copy link
Author

@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.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

noruzzamans and others added 5 commits January 19, 2026 12:35
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@noruzzamans
Copy link
Author

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

noruzzamans and others added 3 commits January 21, 2026 07:01
Co-authored-by: Weston Ruter <westonruter@gmail.com>
Co-authored-by: Weston Ruter <westonruter@gmail.com>
@noruzzamans
Copy link
Author

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

@westonruter
Copy link
Member

@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';
  2. 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.
  3. 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.
  4. 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().

- Resolve theme directory with realpath() for symlink support.
- Consolidate file iteration logic to avoid code duplication (DRY).
- Remove redundant DIRECTORY_SEPARATOR from temp file path.
@noruzzamans
Copy link
Author

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.

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.

2 participants