Skip to content

Theme Directory: Guard against repopackage posts missing _status meta#705

Open
obenland wants to merge 4 commits into
WordPress:trunkfrom
obenland:fix/theme-directory-missing-status-meta
Open

Theme Directory: Guard against repopackage posts missing _status meta#705
obenland wants to merge 4 commits into
WordPress:trunkfrom
obenland:fix/theme-directory-missing-status-meta

Conversation

@obenland

@obenland obenland commented Jul 2, 2026

Copy link
Copy Markdown
Member

Problem

A repopackage post can exist without _status post meta: import() in class-wporg-themes-upload.php creates the post but only writes _status after Trac ticket creation — a hard failure in between leaves an orphan. A live example exists: post 132199 (block-magazine, draft) has versioned meta but no _status and no _ticket_id.

For missing meta, WP_Post::__get() returns '' (empty string). Three places assume an array and fatal on PHP 8:

  1. populate_post_with_meta()array_keys( $theme->_status ) throws TypeError: array_keys(): Argument #1 ($array) must be of type array, string given. This currently fatals the entire upload page for any theme with an orphaned post.
  2. create_or_update_trac_ticket()$this->theme_post->_ticket_id[ $this->theme_post->max_version ] throws TypeError: Cannot access offset of type string on string when the meta is missing.
  3. class-themes-api.phparray_keys( get_post_meta( $theme->ID, '_status', true ) ) would fatal the themes API for a published theme missing the meta.

Changes

  • populate_post_with_meta(): cast get_post_custom_keys() with (array) (it returns null for posts with no meta at all), and only call array_keys() on _status when it is an array. Note (array) casting the meta value itself would be wrong — (array) '' yields array( '' ), creating a bogus version key. end() on the empty array yields false for max_version; downstream code already copes (! empty() guards, ?? '', and the svn import fallback in add_to_svn()).
  • create_or_update_trac_ticket(): null-coalesce the ticket ID lookup to 0, so ticket_get( 0 ) falls into the existing branch that creates a fresh ticket.
  • class-themes-api.php: skip the versions loop when _status is not an array; versions remains an empty array in the response.

No behavior change when _status is a valid array. Post 132199 is left as-is; it will self-heal on the author's next upload.

Verification

  • php -l passes on both files.
  • php -r sanity checks on PHP 8.4: is_array( '' ) guard yields array(), end( array() ) yields false, '' ['1.0.0'] ?? 0 yields 0 without throwing (?? uses isset semantics, which return false for illegal string offsets).

🤖 Generated with Claude Code

https://claude.ai/code/session_01RuMZkdkeAiLcsXjCXjVxpZ

Copilot AI review requested due to automatic review settings July 2, 2026 16:44
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

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

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

A hard failure between post creation and meta writes in import() can
leave a repopackage post without _status/_ticket_id meta. WP_Post::__get()
returns an empty string for missing meta, which fatals on PHP 8 where
array_keys() and string offset access reject non-array values.

Guards populate_post_with_meta(), create_or_update_trac_ticket(), and
the themes API versions field so such posts no longer fatal the upload
page or the API. No behavior change when the meta is a valid array.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RuMZkdkeAiLcsXjCXjVxpZ
@obenland obenland force-pushed the fix/theme-directory-missing-status-meta branch from 62541b7 to e127784 Compare July 2, 2026 16:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the Theme Directory upload and API code paths against repopackage posts that are missing the _status post meta (e.g., orphaned posts created during a failed import), preventing PHP 8 fatals and allowing the data to self-heal on subsequent uploads.

Changes:

  • Guard populate_post_with_meta() against missing post meta keys and missing/non-array _status when deriving max_version.
  • Make ticket lookup tolerant of missing _ticket_id by null-coalescing to 0 before calling Trac.
  • Prevent the Themes API versions loop from calling array_keys() on non-array _status.

Reviewed changes

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

File Description
wordpress.org/public_html/wp-content/plugins/theme-directory/class-wporg-themes-upload.php Adds defensive handling around missing meta keys and missing ticket IDs during upload/Ticket creation flow.
wordpress.org/public_html/wp-content/plugins/theme-directory/class-themes-api.php Skips versions enumeration when _status meta is not an array to avoid PHP 8 fatals in API responses.
Comments suppressed due to low confidence (1)

wordpress.org/public_html/wp-content/plugins/theme-directory/class-wporg-themes-upload.php:1303

  • create_or_update_trac_ticket() still directly indexes $this->theme_post->_status[ $this->theme_post->max_version ] without guarding that _status is an array. If _status meta is missing, WP_Post::__get() returns an empty string and this line can still fatal on PHP 8 (Cannot access offset of type string on string), which undermines the stated goal of handling orphaned posts missing _status.
		/*
		 * Trac reporter is always the authenticated user, unless it's not-auth'd in which case it's the Theme Author.
		 *
		 * This allows for Committers to upload Default themes under 'WordPress.org' but it still be to noted on Trac who uploaded it.

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

obenland and others added 2 commits July 2, 2026 12:01
Adds a PHPUnit setup for the theme-directory plugin, mirroring the
plugin-directory one: a wp-env test environment, a WordPress-backed
test bootstrap, and a CI matrix entry.

The initial tests cover populate_post_with_meta() and the themes API
versions field for repopackage posts missing the _status post meta.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RuMZkdkeAiLcsXjCXjVxpZ

Copilot AI left a comment

Copy link
Copy Markdown

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 7 out of 8 changed files in this pull request and generated 2 comments.

When Trac ticket creation fails for a new upload, the freshly created
repopackage post is deleted again to allow a clean re-upload. That
deletion was caught by the fail-safe preventing repopackage deletions,
which wp_die()s before the post is removed — leaving an orphaned post
without versioned meta behind.

Extracts the deletion into a delete_theme_post() method that detaches
the fail-safe for the duration of the deletion and restores it after.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RuMZkdkeAiLcsXjCXjVxpZ
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