themes' );
restore_current_blog();
}
/*
* This is all super old MU back compat joy.
* 'allowedthemes' keys things by stylesheet. 'allowed_themes' keyed things by name.
*/
if ( false === $allowed_themes[ $blog_id ] ) {
if ( $current ) {
$allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
} else {
switch_to_blog( $blog_id );
$allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
restore_current_blog();
}
if ( ! is_array( $allowed_themes[ $blog_id ] ) || empty( $allowed_themes[ $blog_id ] ) ) {
$allowed_themes[ $blog_id ] = array();
} else {
$converted = array();
$themes = wp_get_themes();
foreach ( $themes as $stylesheet => $theme_data ) {
if ( isset( $allowed_themes[ $blog_id ][ $theme_data->get( 'Name' ) ] ) ) {
$converted[ $stylesheet ] = true;
}
}
$allowed_themes[ $blog_id ] = $converted;
}
// Set the option so we never have to go through this pain again.
if ( is_admin() && $allowed_themes[ $blog_id ] ) {
if ( $current ) {
update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
delete_option( 'allowed_themes' );
} else {
switch_to_blog( $blog_id );
update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
delete_option( 'allowed_themes' );
restore_current_blog();
}
}
}
/** This filter is documented in wp-includes/class-wp-theme.php */
return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
}
/**
* Returns the folder names of the block template directories.
*
* @since 6.4.0
*
* @return string[] {
* Folder names used by block themes.
*
* @type string $wp_template Theme-relative directory name for block templates.
* @type string $wp_template_part Theme-relative directory name for block template parts.
* }
*/
public function get_block_template_folders() {
// Return set/cached value if available.
if ( isset( $this->block_template_folders ) ) {
return $this->block_template_folders;
}
$this->block_template_folders = $this->default_template_folders;
$stylesheet_directory = $this->get_stylesheet_directory();
// If the theme uses deprecated block template folders.
if ( file_exists( $stylesheet_directory . '/block-templates' ) || file_exists( $stylesheet_directory . '/block-template-parts' ) ) {
$this->block_template_folders = array(
'wp_template' => 'block-templates',
'wp_template_part' => 'block-template-parts',
);
}
return $this->block_template_folders;
}
/**
* Gets block pattern data for a specified theme.
* Each pattern is defined as a PHP file and defines
* its metadata using plugin-style headers. The minimum required definition is:
*
* /**
* * Title: My Pattern
* * Slug: my-theme/my-pattern
* *
*
* The output of the PHP source corresponds to the content of the pattern, e.g.:
*
* Hello
*
* If applicable, this will collect from both parent and child theme.
*
* Other settable fields include:
*
* - Description
* - Viewport Width
* - Inserter (yes/no)
* - Categories (comma-separated values)
* - Keywords (comma-separated values)
* - Block Types (comma-separated values)
* - Post Types (comma-separated values)
* - Template Types (comma-separated values)
*
* @since 6.4.0
*
* @return array Block pattern data.
*/
public function get_block_patterns() {
$can_use_cached = ! wp_is_development_mode( 'theme' );
$pattern_data = $this->get_pattern_cache();
if ( is_array( $pattern_data ) ) {
if ( $can_use_cached ) {
return $pattern_data;
}
// If in development mode, clear pattern cache.
$this->delete_pattern_cache();
}
$dirpath = $this->get_stylesheet_directory() . '/patterns/';
$pattern_data = array();
if ( ! file_exists( $dirpath ) ) {
if ( $can_use_cached ) {
$this->set_pattern_cache( $pattern_data );
}
return $pattern_data;
}
$files = glob( $dirpath . '*.php' );
if ( ! $files ) {
if ( $can_use_cached ) {
$this->set_pattern_cache( $pattern_data );
}
return $pattern_data;
}
$default_headers = array(
'title' => 'Title',
'slug' => 'Slug',
'description' => 'Description',
'viewportWidth' => 'Viewport Width',
'inserter' => 'Inserter',
'categories' => 'Categories',
'keywords' => 'Keywords',
'blockTypes' => 'Block Types',
'postTypes' => 'Post Types',
'templateTypes' => 'Template Types',
);
$properties_to_parse = array(
'categories',
'keywords',
'blockTypes',
'postTypes',
'templateTypes',
);
foreach ( $files as $file ) {
$pattern = get_file_data( $file, $default_headers );
if ( empty( $pattern['slug'] ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: file name. */
__( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ),
$file
),
'6.0.0'
);
continue;
}
if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern['slug'] ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: file name; 2: slug value found. */
__( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ),
$file,
$pattern['slug']
),
'6.0.0'
);
}
// Title is a required property.
if ( ! $pattern['title'] ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: 1: file name. */
__( 'Could not register file "%s" as a block pattern ("Title" field missing)' ),
$file
),
'6.0.0'
);
continue;
}
// For properties of type array, parse data as comma-separated.
foreach ( $properties_to_parse as $property ) {
if ( ! empty( $pattern[ $property ] ) ) {
$pattern[ $property ] = array_filter( wp_parse_list( (string) $pattern[ $property ] ) );
} else {
unset( $pattern[ $property ] );
}
}
// Parse properties of type int.
$property = 'viewportWidth';
if ( ! empty( $pattern[ $property ] ) ) {
$pattern[ $property ] = (int) $pattern[ $property ];
} else {
unset( $pattern[ $property ] );
}
// Parse properties of type bool.
$property = 'inserter';
if ( ! empty( $pattern[ $property ] ) ) {
$pattern[ $property ] = in_array(
strtolower( $pattern[ $property ] ),
array( 'yes', 'true' ),
true
);
} else {
unset( $pattern[ $property ] );
}
$key = str_replace( $dirpath, '', $file );
$pattern_data[ $key ] = $pattern;
}
if ( $can_use_cached ) {
$this->set_pattern_cache( $pattern_data );
}
return $pattern_data;
}
/**
* Gets block pattern cache.
*
* @since 6.4.0
* @since 6.6.0 Uses transients to cache regardless of site environment.
*
* @return array|false Returns an array of patterns if cache is found, otherwise false.
*/
private function get_pattern_cache() {
if ( ! $this->exists() ) {
return false;
}
$pattern_data = get_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash );
if ( is_array( $pattern_data ) && $pattern_data['version'] === $this->get( 'Version' ) ) {
return $pattern_data['patterns'];
}
return false;
}
/**
* Sets block pattern cache.
*
* @since 6.4.0
* @since 6.6.0 Uses transients to cache regardless of site environment.
*
* @param array $patterns Block patterns data to set in cache.
*/
private function set_pattern_cache( array $patterns ) {
$pattern_data = array(
'version' => $this->get( 'Version' ),
'patterns' => $patterns,
);
/**
* Filters the cache expiration time for theme files.
*
* @since 6.6.0
*
* @param int $cache_expiration Cache expiration time in seconds.
* @param string $cache_type Type of cache being set.
*/
$cache_expiration = (int) apply_filters( 'wp_theme_files_cache_ttl', self::$cache_expiration, 'theme_block_patterns' );
// We don't want to cache patterns infinitely.
if ( $cache_expiration <= 0 ) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: %1$s: The filter name.*/
__( 'The %1$s filter must return an integer value greater than 0.' ),
'wp_theme_files_cache_ttl
'
),
'6.6.0'
);
$cache_expiration = self::$cache_expiration;
}
set_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash, $pattern_data, $cache_expiration );
}
/**
* Clears block pattern cache.
*
* @since 6.4.0
* @since 6.6.0 Uses transients to cache regardless of site environment.
*/
public function delete_pattern_cache() {
delete_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash );
}
/**
* Enables a theme for all sites on the current network.
*
* @since 4.6.0
*
* @param string|string[] $stylesheets Stylesheet name or array of stylesheet names.
*/
public static function network_enable_theme( $stylesheets ) {
if ( ! is_multisite() ) {
return;
}
if ( ! is_array( $stylesheets ) ) {
$stylesheets = array( $stylesheets );
}
$allowed_themes = get_site_option( 'allowedthemes' );
foreach ( $stylesheets as $stylesheet ) {
$allowed_themes[ $stylesheet ] = true;
}
update_site_option( 'allowedthemes', $allowed_themes );
}
/**
* Disables a theme for all sites on the current network.
*
* @since 4.6.0
*
* @param string|string[] $stylesheets Stylesheet name or array of stylesheet names.
*/
public static function network_disable_theme( $stylesheets ) {
if ( ! is_multisite() ) {
return;
}
if ( ! is_array( $stylesheets ) ) {
$stylesheets = array( $stylesheets );
}
$allowed_themes = get_site_option( 'allowedthemes' );
foreach ( $stylesheets as $stylesheet ) {
if ( isset( $allowed_themes[ $stylesheet ] ) ) {
unset( $allowed_themes[ $stylesheet ] );
}
}
update_site_option( 'allowedthemes', $allowed_themes );
}
/**
* Sorts themes by name.
*
* @since 3.4.0
*
* @param WP_Theme[] $themes Array of theme objects to sort (passed by reference).
*/
public static function sort_by_name( &$themes ) {
if ( str_starts_with( get_user_locale(), 'en_' ) ) {
uasort( $themes, array( 'WP_Theme', '_name_sort' ) );
} else {
foreach ( $themes as $key => $theme ) {
$theme->translate_header( 'Name', $theme->headers['Name'] );
}
uasort( $themes, array( 'WP_Theme', '_name_sort_i18n' ) );
}
}
/**
* Callback function for usort() to naturally sort themes by name.
*
* Accesses the Name header directly from the class for maximum speed.
* Would choke on HTML but we don't care enough to slow it down with strip_tags().
*
* @since 3.4.0
*
* @param WP_Theme $a First theme.
* @param WP_Theme $b Second theme.
* @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
* Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
*/
private static function _name_sort( $a, $b ) {
return strnatcasecmp( $a->headers['Name'], $b->headers['Name'] );
}
/**
* Callback function for usort() to naturally sort themes by translated name.
*
* @since 3.4.0
*
* @param WP_Theme $a First theme.
* @param WP_Theme $b Second theme.
* @return int Negative if `$a` falls lower in the natural order than `$b`. Zero if they fall equally.
* Greater than 0 if `$a` falls higher in the natural order than `$b`. Used with usort().
*/
private static function _name_sort_i18n( $a, $b ) {
return strnatcasecmp( $a->name_translated, $b->name_translated );
}
private static function _check_headers_property_has_correct_type( $headers ) {
if ( ! is_array( $headers ) ) {
return false;
}
foreach ( $headers as $key => $value ) {
if ( ! is_string( $key ) || ! is_string( $value ) ) {
return false;
}
}
return true;
}
}