Skip to content

Commit 579784f

Browse files
committed
Support extra header filters in JSON metadata parsing
1 parent 84b82ea commit 579784f

4 files changed

Lines changed: 132 additions & 2 deletions

File tree

src/wp-admin/includes/plugin.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ function _get_plugin_json_data( $plugin_file ) {
103103
'updateUri' => 'UpdateURI',
104104
);
105105

106+
$extra_plugin_headers = (array) apply_filters( 'extra_plugin_headers', array() );
107+
foreach ( $extra_plugin_headers as $extra_header ) {
108+
$key_map[ $extra_header ] = $extra_header;
109+
}
110+
106111
// Initialize all headers to empty strings (matching get_file_data() behavior).
107112
$plugin_data = array(
108113
'Name' => '',
@@ -120,6 +125,9 @@ function _get_plugin_json_data( $plugin_file ) {
120125
'RequiresPlugins' => '',
121126
'_sitewide' => '',
122127
);
128+
foreach ( $extra_plugin_headers as $extra_header ) {
129+
$plugin_data[ $extra_header ] = '';
130+
}
123131

124132
foreach ( $key_map as $json_key => $header_key ) {
125133
if ( isset( $json_data[ $json_key ] ) ) {

src/wp-includes/class-wp-theme.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -898,11 +898,18 @@ private function read_json_metadata() {
898898

899899
$metadata = $theme_json_data['metadata'];
900900

901+
$extra_theme_headers = (array) apply_filters( 'extra_theme_headers', array() );
902+
901903
// Initialize all headers to empty strings (matching get_file_data() behavior).
902-
$headers = array_fill_keys( array_keys( self::$file_headers ), '' );
904+
$headers = array_fill_keys( array_merge( array_keys( self::$file_headers ), $extra_theme_headers ), '' );
905+
906+
$json_metadata_keys = self::$json_metadata_keys;
907+
foreach ( $extra_theme_headers as $extra_header ) {
908+
$json_metadata_keys[ $extra_header ] = $extra_header;
909+
}
903910

904911
// Map JSON metadata keys to internal header keys.
905-
foreach ( self::$json_metadata_keys as $json_key => $header_key ) {
912+
foreach ( $json_metadata_keys as $json_key => $header_key ) {
906913
if ( isset( $metadata[ $json_key ] ) ) {
907914
if ( 'tags' === $json_key && is_array( $metadata[ $json_key ] ) ) {
908915
$headers[ $header_key ] = implode( ', ', $metadata[ $json_key ] );

tests/phpunit/tests/admin/includesPluginJsonMetadata.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@
1212
*/
1313
class Tests_Admin_IncludesPluginJsonMetadata extends WP_UnitTestCase {
1414

15+
/**
16+
* Registers an extra plugin header for testing.
17+
*
18+
* @param string[] $headers Existing extra headers.
19+
* @return string[] Filtered extra headers.
20+
*/
21+
public function filter_extra_plugin_headers( $headers ) {
22+
$headers[] = 'Custom Header';
23+
return $headers;
24+
}
25+
1526
/**
1627
* Tests that plugin.json metadata is read correctly.
1728
*
@@ -214,6 +225,48 @@ public function test_plugin_json_network_requires_strict_boolean_true() {
214225
$this->assertSame( '', $int_one_data['Network'] );
215226
}
216227

228+
/**
229+
* Tests that custom plugin headers registered via extra_plugin_headers are read from plugin.json.
230+
*
231+
* @ticket 24152
232+
*/
233+
public function test_plugin_json_supports_extra_plugin_headers() {
234+
$plugin_dir = WP_PLUGIN_DIR . '/json-custom-header-test-' . uniqid();
235+
$dir_name = basename( $plugin_dir );
236+
$plugin_file = $plugin_dir . '/' . $dir_name . '.php';
237+
238+
add_filter( 'extra_plugin_headers', array( $this, 'filter_extra_plugin_headers' ) );
239+
mkdir( $plugin_dir );
240+
try {
241+
file_put_contents(
242+
$plugin_dir . '/plugin.json',
243+
wp_json_encode(
244+
array(
245+
'name' => 'Custom Header Plugin',
246+
'version' => '1.0.0',
247+
'Custom Header' => 'Custom Header Value',
248+
)
249+
)
250+
);
251+
file_put_contents( $plugin_file, '<?php // test' );
252+
253+
$plugin_data = get_plugin_data( $plugin_file, false, false );
254+
$this->assertSame( 'Custom Header Value', $plugin_data['Custom Header'] );
255+
} finally {
256+
remove_filter( 'extra_plugin_headers', array( $this, 'filter_extra_plugin_headers' ) );
257+
if ( file_exists( $plugin_dir . '/plugin.json' ) ) {
258+
unlink( $plugin_dir . '/plugin.json' );
259+
}
260+
if ( file_exists( $plugin_file ) ) {
261+
unlink( $plugin_file );
262+
}
263+
if ( is_dir( $plugin_dir ) ) {
264+
rmdir( $plugin_dir );
265+
}
266+
wp_clean_plugins_cache();
267+
}
268+
}
269+
217270
/**
218271
* Tests that plugin.json does not affect MU-plugins.
219272
*

tests/phpunit/tests/theme/wpThemeJsonMetadata.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,17 @@ public function _theme_root( $dir ) {
7070
return $this->theme_root;
7171
}
7272

73+
/**
74+
* Registers an extra theme header for testing.
75+
*
76+
* @param string[] $headers Existing extra headers.
77+
* @return string[] Filtered extra headers.
78+
*/
79+
public function filter_extra_theme_headers( $headers ) {
80+
$headers[] = 'Custom Header';
81+
return $headers;
82+
}
83+
7384
/**
7485
* Tests that theme.json metadata takes priority over style.css headers.
7586
*
@@ -152,6 +163,57 @@ public function test_block_theme_without_metadata_property_uses_stylesheet() {
152163
$this->assertFalse( $theme->errors() );
153164
}
154165

166+
/**
167+
* Tests that custom theme headers registered via extra_theme_headers are read from theme.json metadata.
168+
*
169+
* @ticket 24152
170+
*/
171+
public function test_theme_json_supports_extra_theme_headers() {
172+
$theme_slug = 'json-custom-header-theme-' . wp_generate_password( 8, false );
173+
$theme_dir = $this->theme_root . '/' . $theme_slug;
174+
175+
add_filter( 'extra_theme_headers', array( $this, 'filter_extra_theme_headers' ) );
176+
mkdir( $theme_dir );
177+
try {
178+
file_put_contents(
179+
$theme_dir . '/theme.json',
180+
wp_json_encode(
181+
array(
182+
'version' => 3,
183+
'metadata' => array(
184+
'name' => 'JSON Custom Header Theme',
185+
'Custom Header' => 'Custom Theme Header Value',
186+
),
187+
)
188+
)
189+
);
190+
file_put_contents( $theme_dir . '/style.css', "/*\nTheme Name: CSS Fallback\n*/\n" );
191+
file_put_contents( $theme_dir . '/index.php', "<?php\n// Test theme.\n" );
192+
193+
wp_clean_themes_cache();
194+
unset( $GLOBALS['wp_themes'] );
195+
196+
$theme = new WP_Theme( $theme_slug, $this->theme_root );
197+
$this->assertSame( 'Custom Theme Header Value', $theme->get( 'Custom Header' ) );
198+
} finally {
199+
remove_filter( 'extra_theme_headers', array( $this, 'filter_extra_theme_headers' ) );
200+
if ( file_exists( $theme_dir . '/theme.json' ) ) {
201+
unlink( $theme_dir . '/theme.json' );
202+
}
203+
if ( file_exists( $theme_dir . '/style.css' ) ) {
204+
unlink( $theme_dir . '/style.css' );
205+
}
206+
if ( file_exists( $theme_dir . '/index.php' ) ) {
207+
unlink( $theme_dir . '/index.php' );
208+
}
209+
if ( is_dir( $theme_dir ) ) {
210+
rmdir( $theme_dir );
211+
}
212+
wp_clean_themes_cache();
213+
unset( $GLOBALS['wp_themes'] );
214+
}
215+
}
216+
155217
/**
156218
* Tests that active theme validation allows a JSON-metadata theme without style.css.
157219
*

0 commit comments

Comments
 (0)