From 53a209a4ee07028b8956b81eb7147795d47494fa Mon Sep 17 00:00:00 2001 From: Andrew Nacin Date: Tue, 28 Feb 2012 21:24:44 +0000 Subject: [PATCH] Introduce WP_Theme, wp_get_themes(), and wp_get_theme() to replace get_themes(), get_theme(), get_theme_data(), current_theme_info(), and others. * Getters and Helpers: Introduces a series of methods to allow for easy generation of headers for display, and other theme metadata, including page templates. * Screenshots: Handles support for multiple screenshots. (see # Additional screenshots must be PNG and start with screenshot-2.png, and be sequential to be counted. see #19816. * Error Handling: Broken themes have a WP_Error object attached to them. * Caching: Introduces a wp_cache_themes_persistently filter (also in [20020]) to enable persistent caching of all filesystem and sanitization operations normally handled by WP_Theme (and formerly get_file_data() and get_themes()). Themes are cached individually and across five different cache keys for different data pieces. * Compatibility: A WP_Theme object is backwards compatible with a theme's array formerly returned by get_themes() and get_theme(), and an stdClass object formerly returned by current_theme_info(). * i18n/L10n: Theme headers are now localizable with proper Text Domain and Domain Path headers, like plugins. (Language packs may remove the requirement for headers.) For page templates, see #6007 (not fixed yet, but will be easy now). For headers, fixes #15858. * PHP and CSS files: New methods that fetch a list of theme files (for the theme editor) only on demand, rather than only loading them into memory. fixes #11214. Functions deprecated: * get_themes(), get_allowed_themes() and get_broken_themes() -- use wp_get_themes() * get_theme() and current_theme_info() -- use wp_get_theme() * get_site_allowed_themes() -- use WP_Theme::get_allowed_on_network() * wpmu_get_blog_allowedthemes() -- use WP_theme::get_allowed_on_site() see also [20016], [20018], [20019], [20020], [20021], [20022], [20025], [20026], [20027]. also fixes #19244. see #20103. git-svn-id: https://develop.svn.wordpress.org/trunk@20029 602fd350-edb4-49c9-b593-d223f7449a82 --- .../class-wp-ms-themes-list-table.php | 120 +- .../includes/class-wp-themes-list-table.php | 152 +-- wp-admin/includes/dashboard.php | 20 +- wp-admin/includes/deprecated.php | 57 + wp-admin/includes/ms-deprecated.php | 10 + wp-admin/includes/ms.php | 49 - wp-admin/includes/schema.php | 9 +- wp-admin/includes/theme-install.php | 14 +- wp-admin/includes/theme.php | 137 +- wp-admin/includes/update.php | 11 +- wp-admin/themes.php | 44 +- wp-includes/class-wp-customize.php | 12 +- wp-includes/class-wp-theme.php | 1116 +++++++++++++++++ wp-includes/deprecated.php | 46 + wp-includes/theme.php | 341 ++--- wp-includes/update.php | 27 +- wp-settings.php | 1 + 17 files changed, 1520 insertions(+), 646 deletions(-) create mode 100644 wp-includes/class-wp-theme.php diff --git a/wp-admin/includes/class-wp-ms-themes-list-table.php b/wp-admin/includes/class-wp-ms-themes-list-table.php index 61ae819378..b1a7b5921c 100644 --- a/wp-admin/includes/class-wp-ms-themes-list-table.php +++ b/wp-admin/includes/class-wp-ms-themes-list-table.php @@ -50,53 +50,41 @@ class WP_MS_Themes_List_Table extends WP_List_Table { } function prepare_items() { - global $status, $themes, $totals, $page, $orderby, $order, $s; + global $status, $totals, $page, $orderby, $order, $s; wp_reset_vars( array( 'orderby', 'order', 's' ) ); $themes = array( - 'all' => apply_filters( 'all_themes', get_themes() ), + 'all' => apply_filters( 'all_themes', wp_get_themes() ), 'search' => array(), 'enabled' => array(), 'disabled' => array(), 'upgrade' => array() ); - $site_allowed_themes = get_site_allowed_themes(); - if ( !$this->is_site_themes ) { - $allowed_themes = $site_allowed_themes; - $themes_per_page = $this->get_items_per_page( 'themes_network_per_page' ); - } else { - $allowed_themes = wpmu_get_blog_allowedthemes( $this->site_id ); + if ( $this->is_site_themes ) { $themes_per_page = $this->get_items_per_page( 'site_themes_network_per_page' ); + $allowed_where = 'site'; + } else { + $themes_per_page = $this->get_items_per_page( 'themes_network_per_page' ); + $allowed_where = 'network'; } - $current = get_site_transient( 'update_themes' ); + $current = current_user_can( 'update_themes' ) && ! $this->is_site_themes && get_site_transient( 'update_themes' ); foreach ( (array) $themes['all'] as $key => $theme ) { - $theme_key = $theme['Stylesheet']; + if ( $this->is_site_themes && $theme->is_allowed( 'network' ) ) { + unset( $themes['all'][ $key ] ); + continue; + } - if ( isset( $allowed_themes [ $theme_key ] ) ) { - $themes['all'][$key]['enabled'] = true; - $themes['enabled'][$key] = $themes['all'][$key]; - } - else { - $themes['all'][$key]['enabled'] = false; - $themes['disabled'][$key] = $themes['all'][$key]; - } - if ( isset( $current->response[ $theme['Template'] ] ) ) - $themes['upgrade'][$key] = $themes['all'][$key]; + $filter = $theme->is_allowed( $allowed_where, $this->site_id ) ? 'enabled' : 'disabled'; + $themes[ $filter ][ $key ] = $themes['all'][ $key ]; - if ( $this->is_site_themes && isset( $site_allowed_themes[$theme_key] ) ) { - unset( $themes['all'][$key] ); - unset( $themes['enabled'][$key] ); - unset( $themes['disabled'][$key] ); - } + if ( $current && isset( $current->response[ $key ] ) ) + $themes['upgrade'][ $key ] = $themes['all'][ $key ]; } - if ( !current_user_can( 'update_themes' ) || $this->is_site_themes ) - $themes['upgrade'] = array(); - if ( $s ) { $status = 'search'; $themes['search'] = array_filter( $themes['all'], array( &$this, '_search_callback' ) ); @@ -110,19 +98,27 @@ class WP_MS_Themes_List_Table extends WP_List_Table { $status = 'all'; $this->items = $themes[ $status ]; + WP_Theme::sort_by_name( $this->items ); + + $this->has_items = ! empty( $themes['all'] ); $total_this_page = $totals[ $status ]; if ( $orderby ) { $orderby = ucfirst( $orderby ); $order = strtoupper( $order ); - uasort( $this->items, array( &$this, '_order_callback' ) ); + if ( $orderby == 'Name' ) { + if ( 'ASC' == $order ) + $this->items = array_reverse( $this->items ); + } else { + uasort( $this->items, array( &$this, '_order_callback' ) ); + } } $start = ( $page - 1 ) * $themes_per_page; if ( $total_this_page > $themes_per_page ) - $this->items = array_slice( $this->items, $start, $themes_per_page ); + $this->items = array_slice( $this->items, $start, $themes_per_page, true ); $this->set_pagination_args( array( 'total_items' => $total_this_page, @@ -135,19 +131,27 @@ class WP_MS_Themes_List_Table extends WP_List_Table { if ( is_null( $term ) ) $term = stripslashes( $_REQUEST['s'] ); - $search_fields = array( 'Name', 'Title', 'Description', 'Author', 'Author Name', 'Author URI', 'Template', 'Stylesheet' ); - foreach ( $search_fields as $field ) - if ( stripos( $theme[ $field ], $term ) !== false ) + foreach ( array( 'Name', 'Description', 'Author', 'Author', 'AuthorURI' ) as $field ) { + // Don't mark up; Do translate. + if ( false !== stripos( $theme->display( $field, false, true ), $term ) ) return true; + } + + if ( false !== stripos( $theme->get_stylesheet(), $term ) ) + return true; + + if ( false !== stripos( $theme->get_template(), $term ) ) + return true; return false; } + // Not used by any core columns. function _order_callback( $theme_a, $theme_b ) { global $orderby, $order; - $a = $theme_a[$orderby]; - $b = $theme_b[$orderby]; + $a = $theme_a[ $orderby ]; + $b = $theme_b[ $orderby ]; if ( $a == $b ) return 0; @@ -159,9 +163,7 @@ class WP_MS_Themes_List_Table extends WP_List_Table { } function no_items() { - global $themes; - - if ( !empty( $themes['all'] ) ) + if ( ! $$this->has_items ) _e( 'No themes found.' ); else _e( 'You do not appear to have any themes available at this time.' ); @@ -259,10 +261,13 @@ class WP_MS_Themes_List_Table extends WP_List_Table { $context = $status; - if ( $this->is_site_themes ) + if ( $this->is_site_themes ) { $url = "site-themes.php?id={$this->site_id}&"; - else + $allowed = $theme->is_allowed( 'site', $this->site_id ); + } else { $url = 'themes.php?'; + $allowed = $theme->is_allowed( 'network' ); + } // preorder $actions = array( @@ -272,30 +277,29 @@ class WP_MS_Themes_List_Table extends WP_List_Table { 'delete' => '' ); - $theme_key = $theme['Stylesheet']; + $theme_key = $theme->get_stylesheet(); - if ( empty( $theme['enabled'] ) ) + if ( ! $allowed ) $actions['enable'] = '' . ( $this->is_site_themes ? __( 'Enable' ) : __( 'Network Enable' ) ) . ''; else $actions['disable'] = '' . ( $this->is_site_themes ? __( 'Disable' ) : __( 'Network Disable' ) ) . ''; if ( current_user_can('edit_themes') ) - $actions['edit'] = '' . __('Edit') . ''; + $actions['edit'] = '' . __('Edit') . ''; - if ( empty( $theme['enabled'] ) && current_user_can( 'delete_themes' ) && ! $this->is_site_themes && $theme_key != get_option( 'stylesheet' ) && $theme_key != get_option( 'template' ) ) + if ( ! $allowed && current_user_can( 'delete_themes' ) && ! $this->is_site_themes && $theme_key != get_option( 'stylesheet' ) && $theme_key != get_option( 'template' ) ) $actions['delete'] = '' . __( 'Delete' ) . ''; $actions = apply_filters( 'theme_action_links', array_filter( $actions ), $theme_key, $theme, $context ); $actions = apply_filters( "theme_action_links_$theme_key", $actions, $theme_key, $theme, $context ); - $class = empty( $theme['enabled'] ) ? 'inactive' : 'active'; - $checkbox_id = "checkbox_" . md5($theme['Name']); - $checkbox = ""; + $class = ! $allowed ? 'inactive' : 'active'; + $checkbox_id = "checkbox_" . md5( $theme->get('Name') ); + $checkbox = ""; - $description = '

' . $theme['Description'] . '

'; - $theme_name = $theme['Name']; + $description = '

' . $theme->display( 'Description' ) . '

'; - $id = sanitize_title( $theme_name ); + $id = sanitize_html_class( $theme->get_stylesheet() ); echo ""; @@ -311,25 +315,25 @@ class WP_MS_Themes_List_Table extends WP_List_Table { echo "$checkbox"; break; case 'name': - echo "$theme_name"; + echo "" . $theme->display('Name') . ""; echo $this->row_actions( $actions, true ); echo ""; break; case 'description': echo " -
$description
+
" . $theme->display( 'Description' ) . "
"; $theme_meta = array(); - if ( !empty( $theme['Version'] ) ) - $theme_meta[] = sprintf( __( 'Version %s' ), $theme['Version'] ); + if ( $theme->get('Version') ) + $theme_meta[] = sprintf( __( 'Version %s' ), $theme->display('Version') ); - if ( !empty( $theme['Author'] ) ) - $theme_meta[] = sprintf( __( 'By %s' ), $theme['Author'] ); + if ( $theme->get('Author') ) + $theme_meta[] = sprintf( __( 'By %s' ), $theme->display('Author') ); - if ( !empty( $theme['Theme URI'] ) ) - $theme_meta[] = '' . __( 'Visit Theme Site' ) . ''; + if ( $theme->get('ThemeURI') ) + $theme_meta[] = '' . __( 'Visit Theme Site' ) . ''; $theme_meta = apply_filters( 'theme_row_meta', $theme_meta, $theme_key, $theme, $status ); echo implode( ' | ', $theme_meta ); diff --git a/wp-admin/includes/class-wp-themes-list-table.php b/wp-admin/includes/class-wp-themes-list-table.php index 059335baa6..f4bb2d8802 100644 --- a/wp-admin/includes/class-wp-themes-list-table.php +++ b/wp-admin/includes/class-wp-themes-list-table.php @@ -24,11 +24,7 @@ class WP_Themes_List_Table extends WP_List_Table { } function prepare_items() { - global $ct; - - $ct = current_theme_info(); - - $themes = get_allowed_themes(); + $themes = wp_get_themes( array( 'allowed' => true ) ); if ( ! empty( $_REQUEST['s'] ) ) { $search = strtolower( stripslashes( $_REQUEST['s'] ) ); @@ -45,13 +41,13 @@ class WP_Themes_List_Table extends WP_List_Table { if ( $this->search || $this->features ) { foreach ( $themes as $key => $theme ) { - if ( !$this->search_theme( $theme ) ) + if ( ! $this->search_theme( $theme ) ) unset( $themes[ $key ] ); } } - unset( $themes[$ct->name] ); - uksort( $themes, "strnatcasecmp" ); + unset( $themes[ get_option( 'stylesheet' ) ] ); + WP_Theme::sort_by_name( $themes ); $per_page = 999; $page = $this->get_pagenum(); @@ -125,75 +121,74 @@ class WP_Themes_List_Table extends WP_List_Table { function display_rows() { $themes = $this->items; - $theme_names = array_keys( $themes ); - natcasesort( $theme_names ); - foreach ( $theme_names as $theme_name ) { - $class = array( 'available-theme' ); - ?> -
- 1, 'template' => $template, 'stylesheet' => $stylesheet, 'preview_iframe' => true, 'TB_iframe' => 'true' ), home_url( '/' ) ) ); - $preview_text = esc_attr( sprintf( __( 'Preview of “%s”' ), $title ) ); - $tags = $themes[$theme_name]['Tags']; - $thickbox_class = 'thickbox thickbox-preview'; - $activate_link = wp_nonce_url( "themes.php?action=activate&template=" . urlencode( $template ) . "&stylesheet=" . urlencode( $stylesheet ), 'switch-theme_' . $template ); - $activate_text = esc_attr( sprintf( __( 'Activate “%s”' ), $title ) ); - $actions = array(); - $actions[] = '' . __( 'Activate' ) . ''; - $actions[] = '' . __( 'Preview' ) . ''; - if ( ! is_multisite() && current_user_can( 'delete_themes' ) ) - $actions[] = '' . __( 'Delete' ) . ''; - $actions = apply_filters( 'theme_action_links', $actions, $themes[$theme_name] ); + foreach ( $themes as $theme ) { + echo '
'; - $actions = implode ( ' | ', $actions ); -?> - - - - - -

+ $template = $theme->get_template(); + $stylesheet = $theme->get_stylesheet(); - -| -
-

- -

%2$s. The stylesheet files are located in %3$s. %4$s uses templates from %5$s. Changes made to the templates will affect both themes.' ), $title, str_replace( WP_CONTENT_DIR, '', $template_dir ), str_replace( WP_CONTENT_DIR, '', $stylesheet_dir ), $title, $parent_theme ); ?>

- -

%2$s.' ), $title, str_replace( WP_CONTENT_DIR, '', $template_dir ), str_replace( WP_CONTENT_DIR, '', $stylesheet_dir ) ); ?>

- - -

- - -
- -
-display('Name'); + $version = $theme->display('Version'); + $author = $theme->display('Author'); + + $activate_link = wp_nonce_url( "themes.php?action=activate&template=" . urlencode( $template ) . "&stylesheet=" . urlencode( $stylesheet ), 'switch-theme_' . $template ); + $preview_link = esc_url( add_query_arg( + array( 'preview' => 1, 'template' => $template, 'stylesheet' => $stylesheet, 'preview_iframe' => true, 'TB_iframe' => 'true' ), + home_url( '/' ) ) ); + + $actions = array(); + $actions[] = '' . __( 'Activate' ) . ''; + $actions[] = '' . __( 'Preview' ) . ''; + if ( ! is_multisite() && current_user_can( 'delete_themes' ) ) + $actions[] = '' . __( 'Delete' ) . ''; + + $actions = apply_filters( 'theme_action_links', $actions, $theme ); + + $actions = implode ( ' | ', $actions ); + ?> + + get_screenshot() ) : ?> + + + +

+ + + | +
+

display('Description'); ?>

+ parent() ) : + /* translators: 1: theme title, 2: template dir, 3: stylesheet_dir, 4: theme title, 5: parent_theme */ ?> +

%2$s. The stylesheet files are located in %3$s. %4$s uses templates from %5$s. Changes made to the templates will affect both themes.' ), + $title, str_replace( WP_CONTENT_DIR, '', $theme->get_template_directory() ), str_replace( WP_CONTENT_DIR, '', $theme->get_stylesheet_directory() ), $title, $theme->parent()->display('Name') ); ?>

+ +

%2$s.' ), + $title, str_replace( WP_CONTENT_DIR, '', $theme->get_template_directory() ), str_replace( WP_CONTENT_DIR, '', $theme->get_stylesheet_directory() ) ); ?>

+ + get('Tags') ) + printf( '

' . __( 'Tags: %s.' ) . '

', $theme->display('Tags') ); + ?> +
+ +
+ features ) { foreach ( $this->features as $word ) { - if ( ! in_array( $word, $theme['Tags'] ) ) + if ( ! in_array( $word, $theme->get('Tags') ) ) return false; } } @@ -201,16 +196,23 @@ class WP_Themes_List_Table extends WP_List_Table { // Match all phrases if ( $this->search ) { foreach ( $this->search as $word ) { - if ( in_array( $word, $theme['Tags'] ) ) + if ( in_array( $word, $theme->get('Tags') ) ) continue; - } - foreach ( array( 'Name', 'Title', 'Description', 'Author', 'Template', 'Stylesheet' ) as $header ) { - if ( false !== stripos( $theme[ $header ], $word ) ) - continue 2; - } + foreach ( array( 'Name', 'Description', 'Author', 'AuthorURI' ) as $header ) { + // Don't mark up; Do translate. + if ( false !== stripos( $theme->display( $header, false, true ), $word ) ) + continue 2; + } - return false; + if ( false !== stripos( $theme->get_stylesheet(), $word ) ) + continue; + + if ( false !== stripos( $theme->get_template(), $word ) ) + continue; + + return false; + } } return true; diff --git a/wp-admin/includes/dashboard.php b/wp-admin/includes/dashboard.php index 62715b2eae..71ae130898 100644 --- a/wp-admin/includes/dashboard.php +++ b/wp-admin/includes/dashboard.php @@ -383,11 +383,11 @@ function wp_dashboard_right_now() { echo "\n\t\n\t
"; echo "\n\t".'
'; - $ct = current_theme_info(); + $theme = wp_get_theme(); echo "\n\t

"; - if ( empty( $ct->stylesheet_dir ) ) { + if ( $theme->errors() ) { if ( ! is_multisite() || is_super_admin() ) echo '' . __('ERROR: The themes directory is either empty or doesn’t exist. Please check your installation.') . ''; } elseif ( ! empty($wp_registered_sidebars) ) { @@ -401,7 +401,7 @@ function wp_dashboard_right_now() { } $num = number_format_i18n( $num_widgets ); - $switch_themes = $ct->title; + $switch_themes = $theme->display('Name'); if ( current_user_can( 'switch_themes') ) $switch_themes = '' . $switch_themes . ''; if ( current_user_can( 'edit_theme_options' ) ) { @@ -411,9 +411,9 @@ function wp_dashboard_right_now() { } } else { if ( current_user_can( 'switch_themes' ) ) - printf( __('Theme %1$s'), $ct->title ); + printf( __('Theme %1$s'), $theme->display('Name') ); else - printf( __('Theme %1$s'), $ct->title ); + printf( __('Theme %1$s'), $theme->display('Name') ); } echo '

'; @@ -1313,14 +1313,14 @@ function wp_welcome_panel() {

stylesheet_dir ) ) : + $theme = wp_get_theme(); + if ( $theme->errors() ) : echo '

'; printf( __( 'Install a theme to get started customizing your site.' ), esc_url( admin_url( 'themes.php' ) ) ); echo '

'; else: $customize_links = array(); - if ( 'twentyeleven' == $ct->stylesheet ) + if ( 'twentyeleven' == $theme->get_stylesheet() ) $customize_links[] = sprintf( __( 'Choose light or dark' ), esc_url( admin_url( 'themes.php?page=theme_options' ) ) ); if ( current_theme_supports( 'custom-background' ) ) @@ -1334,7 +1334,7 @@ function wp_welcome_panel() { if ( ! empty( $customize_links ) ) { echo '

'; - printf( __( 'Use the current theme — %1$s — or choose a new one. If you stick with %3$s, here are a few ways to make your site look unique.' ), $ct->title, esc_url( admin_url( 'themes.php' ) ), $ct->title ); + printf( __( 'Use the current theme — %1$s — or choose a new one. If you stick with %1$s, here are a few ways to make your site look unique.' ), $theme->display('Name'), esc_url( admin_url( 'themes.php' ) ) ); echo '

'; ?>
    @@ -1345,7 +1345,7 @@ function wp_welcome_panel() { '; - printf( __( 'Use the current theme — %1$s — or choose a new one.' ), $ct->title, esc_url( admin_url( 'themes.php' ) ) ); + printf( __( 'Use the current theme — %1$s — or choose a new one.' ), $this->display('Name'), esc_url( admin_url( 'themes.php' ) ) ); echo '

    '; } endif; ?> diff --git a/wp-admin/includes/deprecated.php b/wp-admin/includes/deprecated.php index 32498c7e3f..ff24583e8c 100644 --- a/wp-admin/includes/deprecated.php +++ b/wp-admin/includes/deprecated.php @@ -879,4 +879,61 @@ function add_contextual_help( $screen, $help ) { $screen = convert_to_screen( $screen ); WP_Screen::add_old_compat_help( $screen, $help ); +} + +/** + * Get the allowed themes for the current blog. + * + * @since 3.0.0 + * @deprecated 3.4.0 + * @deprecated Use wp_get_themes() + * @see wp_get_themes() + * + * @return array $themes Array of allowed themes. + */ +function get_allowed_themes() { + _deprecated_function( __FUNCTION__, '3.4', "wp_get_themes( array( 'allowed' => true ) )" ); + + $themes = wp_get_themes( array( 'allowed' => true ) ); + + $wp_themes = array(); + foreach ( $themes as $theme ) { + $wp_themes[ $theme->get('Name') ] = $theme; + } + + return $wp_themes; +} + +/** + * {@internal Missing Short Description}} + * + * @since 1.5.0 + * + * @return unknown + */ +function get_broken_themes() { + _deprecated_function( __FUNCTION__, '3.4', "wp_get_themes( array( 'errors' => true )" ); + + $themes = wp_get_themes( array( 'errors' => true ) ); + $broken = array(); + foreach ( $themes as $theme ) { + $broken[ $theme->get('Name') ] = array( + 'Title' => $theme->get('Name'), + 'Description' => $theme->errors()->get_error_message(), + ); + } + return $broken; +} + +/** + * {@internal Missing Short Description}} + * + * @since 2.0.0 + * + * @return unknown + */ +function current_theme_info() { + _deprecated_function( __FUNCTION__, '3.4', 'wp_get_theme()' ); + + return wp_get_theme(); } \ No newline at end of file diff --git a/wp-admin/includes/ms-deprecated.php b/wp-admin/includes/ms-deprecated.php index aa4bb2dfe7..7119018b1f 100644 --- a/wp-admin/includes/ms-deprecated.php +++ b/wp-admin/includes/ms-deprecated.php @@ -64,3 +64,13 @@ function is_wpmu_sitewide_plugin( $file ) { _deprecated_function(__FUNCTION__, '3.0', 'is_network_only_plugin()' ); return is_network_only_plugin( $file ); } + +function get_site_allowed_themes() { + _deprecated_function( __FUNCTION__, '3.4', 'WP_Theme::get_allowed_on_network()' ); + return array_map( 'intval', WP_Theme::get_allowed_on_network() ); +} + +function wpmu_get_blog_allowedthemes( $blog_id = 0 ) { + _deprecated_function( __FUNCTION__, '3.4', 'WP_Theme::get_allowed_on_site()' ); + return array_map( 'intval', WP_Theme::get_allowed_on_site( $blog_id ) ); +} \ No newline at end of file diff --git a/wp-admin/includes/ms.php b/wp-admin/includes/ms.php index 8d9a67355a..c61542ef8a 100644 --- a/wp-admin/includes/ms.php +++ b/wp-admin/includes/ms.php @@ -169,35 +169,6 @@ function wpmu_delete_user( $id ) { return true; } -function wpmu_get_blog_allowedthemes( $blog_id = 0 ) { - $themes = get_themes(); - - if ( $blog_id != 0 ) - switch_to_blog( $blog_id ); - - $blog_allowed_themes = get_option( 'allowedthemes' ); - if ( !is_array( $blog_allowed_themes ) || empty( $blog_allowed_themes ) ) { // convert old allowed_themes to new allowedthemes - $blog_allowed_themes = get_option( 'allowed_themes' ); - - if ( is_array( $blog_allowed_themes ) ) { - foreach( (array) $themes as $key => $theme ) { - $theme_key = esc_html( $theme['Stylesheet'] ); - if ( isset( $blog_allowed_themes[$key] ) == true ) { - $blog_allowedthemes[$theme_key] = 1; - } - } - $blog_allowed_themes = $blog_allowedthemes; - add_option( 'allowedthemes', $blog_allowed_themes ); - delete_option( 'allowed_themes' ); - } - } - - if ( $blog_id != 0 ) - restore_current_blog(); - - return $blog_allowed_themes; -} - function update_option_new_admin_email( $old_value, $value ) { $email = get_option( 'admin_email' ); if ( $value == get_option( 'admin_email' ) || !is_email( $value ) ) @@ -296,26 +267,6 @@ function new_user_email_admin_notice() { } add_action( 'admin_notices', 'new_user_email_admin_notice' ); -function get_site_allowed_themes() { - $themes = get_themes(); - $allowed_themes = get_site_option( 'allowedthemes' ); - if ( !is_array( $allowed_themes ) || empty( $allowed_themes ) ) { - $allowed_themes = get_site_option( 'allowed_themes' ); // convert old allowed_themes format - if ( !is_array( $allowed_themes ) ) { - $allowed_themes = array(); - } else { - foreach( (array) $themes as $key => $theme ) { - $theme_key = esc_html( $theme['Stylesheet'] ); - if ( isset( $allowed_themes[ $key ] ) == true ) { - $allowedthemes[ $theme_key ] = 1; - } - } - $allowed_themes = $allowedthemes; - } - } - return $allowed_themes; -} - /** * Determines if there is any upload space left in the current blog's quota. * diff --git a/wp-admin/includes/schema.php b/wp-admin/includes/schema.php index 87a0c4fe6b..d1b6c1bb7e 100644 --- a/wp-admin/includes/schema.php +++ b/wp-admin/includes/schema.php @@ -343,12 +343,9 @@ function populate_options() { $template = WP_DEFAULT_THEME; // If default theme is a child theme, we need to get its template - foreach ( (array) get_themes() as $theme ) { - if ( WP_DEFAULT_THEME == $theme['Stylesheet'] ) { - $template = $theme['Template']; - break; - } - } + $theme = wp_get_theme( $template ); + if ( ! $theme->errors() ) + $template = $theme->get_template(); $timezone_string = ''; $gmt_offset = 0; diff --git a/wp-admin/includes/theme-install.php b/wp-admin/includes/theme-install.php index ab0cda348e..8d34cbe9a7 100644 --- a/wp-admin/includes/theme-install.php +++ b/wp-admin/includes/theme-install.php @@ -265,16 +265,14 @@ function install_theme_information() { } } - $themes = get_themes(); - foreach ( (array) $themes as $this_theme ) { - if ( is_array($this_theme) && $this_theme['Stylesheet'] == $api->slug ) { - if ( version_compare( $this_theme['Version'], $api->version, '=' ) ) { + $theme = wp_get_theme( $api->slug ); + if ( is_a( $theme, 'WP_Theme' ) ) { + switch ( version_compare( $theme->get('Version'), $api->version ) ) { + case 0; // equal $type = 'latest_installed'; - } elseif ( version_compare( $this_theme['Version'], $api->version, '>' ) ) { + case 1: // installed theme > api version $type = 'newer_installed'; - $newer_version = $this_theme['Version']; - } - break; + $newer_version = $theme->get('Version'); } } ?> diff --git a/wp-admin/includes/theme.php b/wp-admin/includes/theme.php index 18fa549e56..420bb32c09 100644 --- a/wp-admin/includes/theme.php +++ b/wp-admin/includes/theme.php @@ -6,46 +6,6 @@ * @subpackage Administration */ -/** - * {@internal Missing Short Description}} - * - * @since 2.0.0 - * - * @return unknown - */ -function current_theme_info() { - $themes = get_themes(); - $current_theme = get_current_theme(); - - if ( ! $themes ) { - $ct = new stdClass; - $ct->name = $current_theme; - return $ct; - } - - if ( ! isset( $themes[$current_theme] ) ) { - delete_option( 'current_theme' ); - $current_theme = get_current_theme(); - } - - $ct = new stdClass; - $ct->name = $current_theme; - $ct->title = $themes[$current_theme]['Title']; - $ct->version = $themes[$current_theme]['Version']; - $ct->parent_theme = $themes[$current_theme]['Parent Theme']; - $ct->template_dir = $themes[$current_theme]['Template Dir']; - $ct->stylesheet_dir = $themes[$current_theme]['Stylesheet Dir']; - $ct->template = $themes[$current_theme]['Template']; - $ct->stylesheet = $themes[$current_theme]['Stylesheet']; - $ct->screenshot = $themes[$current_theme]['Screenshot']; - $ct->description = $themes[$current_theme]['Description']; - $ct->author = $themes[$current_theme]['Author']; - $ct->tags = $themes[$current_theme]['Tags']; - $ct->theme_root = $themes[$current_theme]['Theme Root']; - $ct->theme_root_uri = $themes[$current_theme]['Theme Root URI']; - return $ct; -} - /** * Remove a theme * @@ -113,59 +73,6 @@ function delete_theme($template, $redirect = '') { return true; } -/** - * {@internal Missing Short Description}} - * - * @since 1.5.0 - * - * @return unknown - */ -function get_broken_themes() { - global $wp_broken_themes; - - get_themes(); - return $wp_broken_themes; -} - -/** - * Get the allowed themes for the current blog. - * - * @since 3.0.0 - * - * @uses get_themes() - * @uses current_theme_info() - * @uses get_site_allowed_themes() - * @uses wpmu_get_blog_allowedthemes - * - * @return array $themes Array of allowed themes. - */ -function get_allowed_themes() { - if ( !is_multisite() ) - return get_themes(); - - $themes = get_themes(); - $ct = current_theme_info(); - $allowed_themes = apply_filters("allowed_themes", get_site_allowed_themes() ); - if ( $allowed_themes == false ) - $allowed_themes = array(); - - $blog_allowed_themes = wpmu_get_blog_allowedthemes(); - if ( is_array( $blog_allowed_themes ) ) - $allowed_themes = array_merge( $allowed_themes, $blog_allowed_themes ); - - if ( isset( $allowed_themes[ esc_html( $ct->stylesheet ) ] ) == false ) - $allowed_themes[ esc_html( $ct->stylesheet ) ] = true; - - reset( $themes ); - foreach ( $themes as $key => $theme ) { - if ( isset( $allowed_themes[ esc_html( $theme[ 'Stylesheet' ] ) ] ) == false ) - unset( $themes[ $key ] ); - } - reset( $themes ); - - return $themes; -} - /** * Get the Page Templates available in this theme * @@ -174,37 +81,7 @@ function get_allowed_themes() { * @return array Key is the template name, value is the filename of the template */ function get_page_templates() { - $themes = get_themes(); - $theme = get_current_theme(); - $templates = $themes[$theme]['Template Files']; - $page_templates = array(); - - if ( is_array( $templates ) ) { - $base = array( trailingslashit(get_template_directory()), trailingslashit(get_stylesheet_directory()) ); - - foreach ( $templates as $template ) { - $basename = str_replace($base, '', $template); - - // don't allow template files in subdirectories - if ( false !== strpos($basename, '/') ) - continue; - - if ( 'functions.php' == $basename ) - continue; - - $template_data = implode( '', file( $template )); - - $name = ''; - if ( preg_match( '|Template Name:(.*)$|mi', $template_data, $name ) ) - $name = _cleanup_header_comment($name[1]); - - if ( !empty( $name ) ) { - $page_templates[trim( $name )] = $basename; - } - } - } - - return $page_templates; + return wp_get_theme()->get_page_templates(); } /** @@ -240,16 +117,14 @@ function theme_update_available( $theme ) { if ( !isset($themes_update) ) $themes_update = get_site_transient('update_themes'); - if ( is_object($theme) && isset($theme->stylesheet) ) - $stylesheet = $theme->stylesheet; - elseif ( is_array($theme) && isset($theme['Stylesheet']) ) - $stylesheet = $theme['Stylesheet']; - else - return false; //No valid info passed. + if ( ! is_a( $theme, 'WP_Theme' ) ) + return; + + $stylesheet = $theme->get_stylesheet(); if ( isset($themes_update->response[ $stylesheet ]) ) { $update = $themes_update->response[ $stylesheet ]; - $theme_name = is_object($theme) ? $theme->name : (is_array($theme) ? $theme['Name'] : ''); + $theme_name = $theme->get('Name'); $details_url = add_query_arg(array('TB_iframe' => 'true', 'width' => 1024, 'height' => 800), $update['url']); //Theme browser inside WP? replace this, Also, theme preview JS will override this on the available list. $update_url = wp_nonce_url('update.php?action=upgrade-theme&theme=' . urlencode($stylesheet), 'upgrade-theme_' . $stylesheet); $update_onclick = 'onclick="if ( confirm(\'' . esc_js( __("Updating this theme will lose any customizations you have made. 'Cancel' to stop, 'OK' to update.") ) . '\') ) {return true;}return false;"'; diff --git a/wp-admin/includes/update.php b/wp-admin/includes/update.php index 6c748a1580..c69c3c0a46 100644 --- a/wp-admin/includes/update.php +++ b/wp-admin/includes/update.php @@ -217,16 +217,13 @@ function wp_update_plugin($plugin, $feedback = '') { } function get_theme_updates() { - $themes = get_themes(); + $themes = wp_get_themes(); $current = get_site_transient('update_themes'); $update_themes = array(); - foreach ( $themes as $theme ) { - $theme = (object) $theme; - if ( isset($current->response[ $theme->Stylesheet ]) ) { - $update_themes[$theme->Stylesheet] = $theme; - $update_themes[$theme->Stylesheet]->update = $current->response[ $theme->Stylesheet ]; - } + foreach ( $current->response as $stylesheet => $data ) { + $update_themes[ $stylesheet ] = wp_get_theme( $stylesheet ); + $update_themes[ $stylesheet ]->update = $data; } return $update_themes; diff --git a/wp-admin/themes.php b/wp-admin/themes.php index f81738570c..05be2b24fe 100644 --- a/wp-admin/themes.php +++ b/wp-admin/themes.php @@ -96,16 +96,16 @@ if ( ! is_multisite() && current_user_can( 'install_themes' ) ) : ?>

    - -

    + +

    -screenshot ) : ?> -<?php esc_attr_e('Current theme preview'); ?> +get_screenshot() ) : ?> +<?php esc_attr_e( 'Current theme preview'); ?>

    title, $ct->version, $ct->author) ; ?>

    -

    description; ?>

    + printf( __( '%1$s %2$s by %3$s' ), $ct->display('Name'), $ct->display('Version'), $ct->display('Author') ) ; ?> +

    display('Description'); ?>

    if ( 'themes.php' == $item[2] || 'theme-editor.php' == $item[2] ) continue; // 0 = name, 1 = capability, 2 = file - if ( ( strcmp($self, $item[2]) == 0 && empty($parent_file)) || ($parent_file && ($item[2] == $parent_file)) ) $class = ' class="current"'; - + if ( ( strcmp($self, $item[2]) == 0 && empty($parent_file)) || ($parent_file && ($item[2] == $parent_file)) ) + $class = ' class="current"'; if ( !empty($submenu[$item[2]]) ) { $submenu[$item[2]] = array_values($submenu[$item[2]]); // Re-index. $menu_hook = get_plugin_page_hook($submenu[$item[2]][0][2], $item[2]); @@ -137,8 +137,8 @@ if ( ! is_multisite() && current_user_can( 'install_themes' ) ) : ?> } echo implode ( ' | ', $options ); - if ( $ct->tags ) : ?> -

    tags); ?>

    + if ( $ct->get('Tags') ) : ?> +

    display('Tags'); ?>

    @@ -218,8 +218,7 @@ if ( ! current_user_can( 'switch_themes' ) ) { true ) ) ) { ?>

    @@ -231,20 +230,13 @@ if ( current_user_can('edit_themes') && count( $broken_themes ) ) { - $name - $description + + " . $broken_theme->get('Name') ." + " . $broken_theme->errors()->get_error_message() . " "; } ?> @@ -254,4 +246,4 @@ if ( current_user_can('edit_themes') && count( $broken_themes ) ) { ?>
    - + \ No newline at end of file diff --git a/wp-includes/class-wp-customize.php b/wp-includes/class-wp-customize.php index 39eb8cce0e..11670d2f00 100644 --- a/wp-includes/class-wp-customize.php +++ b/wp-includes/class-wp-customize.php @@ -242,17 +242,7 @@ final class WP_Customize { * @return string Theme name. */ public function current_theme( $current_theme ) { - $themes = get_themes(); - - if ( ! $themes ) - return $current_theme; - - foreach ( $themes as $theme ) { - if ( $theme['Stylesheet'] == $this->stylesheet && $theme['Template'] == $this->template ) - return $theme['Name']; - } - - return $current_theme; + return wp_get_theme( $this->stylesheet )->get('Name'); } /** diff --git a/wp-includes/class-wp-theme.php b/wp-includes/class-wp-theme.php new file mode 100644 index 0000000000..d82b105304 --- /dev/null +++ b/wp-includes/class-wp-theme.php @@ -0,0 +1,1116 @@ + 'Theme Name', + 'ThemeURI' => 'Theme URI', + 'Description' => 'Description', + 'Author' => 'Author', + 'AuthorURI' => 'Author URI', + 'Version' => 'Version', + 'Template' => 'Template', + 'Status' => 'Status', + 'Tags' => 'Tags', + 'TextDomain' => 'Text Domain', + 'DomainPath' => 'Domain Path', + ); + + /** + * Absolute path to the theme root, usually wp-content/themes + * + * @access private + * @var string + */ + private $theme_root; + + /** + * Header data from the theme's style.css file. + * + * @access private + * @var array + */ + private $headers = array(); + + /** + * Header data from the theme's style.css file after being sanitized. + * + * @access private + * @var array + */ + private $headers_sanitized; + + /** + * Header name from the theme's style.css after being translated. + * + * Cached due to sorting functions running over the translated name. + */ + private $name_translated; + + /** + * Errors encountered when initializing the theme. + * + * @access private + * @var WP_Error + */ + private $errors; + + /** + * The directory name of the theme's files, inside the theme root. + * + * In the case of a child theme, this is directory name of the the child theme. + * Otherwise, 'stylesheet' is the same as 'template'. + * + * @access private + * @var string + */ + private $stylesheet; + + /** + * The directory name of the theme's files, inside the theme root. + * + * In the case of a child theme, this is the directory name of the parent theme. + * Otherwise, 'template' is the same as 'stylesheet'. + * + * @access private + * @var string + */ + private $template; + + /** + * A reference to the parent theme, in the case of a child theme. + * + * @access private + * @var WP_Theme + */ + private $parent; + + /** + * Flag for whether the theme's textdomain is loaded. + * + * @access private + * @var bool + */ + private $textdomain_loaded; + + /** + * Flag for whether the themes cache bucket should be persistently cached. + * + * Default is false. Can be set with the wp_cache_themes_persistently filter. + * + * @access private + * @var bool + */ + private static $persistently_cache; + + /** + * Expiration time for the themes cache bucket. + * + * By default the bucket is not cached, so this value is useless. + * + * @access private + * @var bool + */ + private static $cache_expiration = 7200; + + /** + * Constructor for WP_Theme. + * + * @param string $theme_dir Directory of the theme within the theme_root. + * @param string $theme_root Theme root. + * @param WP_Error|null $child If this theme is a parent theme, the child may be passed for validation purposes. + */ + public function __construct( $theme_dir, $theme_root, $child = null ) { + + // Initialize caching on first run. + if ( ! isset( self::$persistently_cache ) ) { + self::$persistently_cache = apply_filters( 'wp_cache_themes_persistently', false, 'WP_Theme' ); + if ( self::$persistently_cache ) { + wp_cache_add_global_groups( 'themes' ); + if ( is_int( self::$persistently_cache ) ) + self::$cache_expiration = self::$persistently_cache; + } else { + wp_cache_add_non_persistent_groups( 'themes' ); + } + } + + $this->theme_root = $theme_root; + $this->stylesheet = $theme_dir; + $theme_file = $this->stylesheet . '/style.css'; + + $cache = $this->cache_get( 'theme' ); + + if ( is_array( $cache ) ) { + foreach ( array( 'errors', 'headers', 'template' ) as $key ) { + if ( isset( $cache[ $key ] ) ) + $this->$key = $cache[ $key ]; + } + if ( $this->errors ) + return; + if ( isset( $cache['theme_root_template'] ) ) + $theme_root_template = $cache['theme_root_template']; + } elseif ( ! file_exists( $this->theme_root . '/' . $theme_file ) ) { + $this->headers['Name'] = $this->stylesheet; + $this->errors = new WP_Error( 'theme_no_stylesheet', __( 'Stylesheet is missing.' ) ); + $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet ) ); + if ( ! file_exists( $this->theme_root ) ) // Don't cache this one. + $this->errors->add( 'theme_root_missing', __( 'ERROR: The themes directory is either empty or doesn’t exist. Please check your installation.' ) ); + return; + } elseif ( ! is_readable( $this->theme_root . '/' . $theme_file ) ) { + $this->headers['Name'] = $this->stylesheet; + $this->errors = new WP_Error( 'theme_stylesheet_not_readable', __( 'Stylesheet is not readable.' ) ); + $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet ) ); + return; + } else { + $this->headers = get_file_data( $this->theme_root . '/' . $theme_file, self::$file_headers, 'theme' ); + } + + // (If template is set from cache, we know it's good.) + if ( ! $this->template && ! ( $this->template = $this->get('Template') ) ) { + if ( file_exists( $this->theme_root . '/' . $this->stylesheet . '/index.php' ) ) { + $this->template = $this->stylesheet; + } else { + $this->errors = new WP_Error( 'theme_no_index', __( 'Template is missing.' ) ); + $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet ) ); + return; + } + } + + // If we got our data from cache, we can assume that 'template' is pointing to the right place. + if ( ! is_array( $cache ) && $this->template != $this->stylesheet && ! file_exists( $this->theme_root . '/' . $this->template . '/index.php' ) ) { + // If we're in a directory of themes inside /themes, look for the parent nearby. + // wp-content/themes/directory-of-themes/* + $parent_dir = dirname( $this->stylesheet ); + if ( '.' != $parent_dir && file_exists( $this->theme_root . '/' . $parent_dir . '/' . $this->template . '/index.php' ) ) { + $this->template = $parent_dir . '/' . $this->template; + } elseif ( ( $directories = search_theme_directories() ) && isset( $directories[ $this->template ] ) ) { + // Look for the template in the search_theme_directories() results, in case it is in another theme root. + // We don't look into directories of themes, just the theme root. + $theme_root_template = $directories[ $this->template ]['theme_root']; + } else { + // Parent theme is missing. + $this->errors = new WP_Error( 'theme_no_parent', sprintf( __( 'The parent theme is missing. Please install the "%s" parent theme.' ), $this->template ) ); + $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) ); + return; + } + } + + // @TODO Check for theme name collision. But guess what? We don't care anymore! We only care about clashing matches found in search_theme_directories(). + + // Set the parent, if we're a child theme. + if ( $this->template != $this->stylesheet ) { + // If we are a parent, then there is a problem. Only two generations allowed! Cancel things out. + if ( is_a( $child, 'WP_Theme' ) && $child->template == $this->stylesheet ) { + $child->parent = null; + $child->errors = new WP_Error( 'theme_parent_invalid', sprintf( __( 'The "%s" theme is not a valid parent theme.' ), $child->template ) ); + $child->cache_add( 'theme', array( 'headers' => $child->headers, 'errors' => $child->errors, 'stylesheet' => $child->stylesheet, 'template' => $child->template ) ); + // The two themes actually reference each other with the Template header. + if ( $child->stylesheet == $this->template ) { + $this->errors = new WP_Error( 'theme_parent_invalid', sprintf( __( 'The "%s" theme is not a valid parent theme.' ), $this->template ) ); + $this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) ); + } + return; + } + // Set the parent. Pass the current instance so we can do the crazy checks above and assess errors. + $this->parent = new WP_Theme( $this->template, isset( $theme_root_template ) ? $theme_root_template : $this->theme_root, $this ); + } + + // We're good. If we didn't retrieve from cache, set it. + if ( ! is_array( $cache ) ) { + $cache = array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ); + // If the parent theme is in another root, we'll want to cache this. Avoids an entire branch of filesystem calls above. + if ( isset( $theme_root_template ) ) + $cache['theme_root_template'] = $theme_root_template; + $this->cache_add( 'theme', $cache ); + } + } + + /** + * __isset() magic method for properties formerly returned by current_theme_info() + */ + public function __isset( $offset ) { + static $properties = array( + 'name', 'title', 'version', 'parent_theme', 'template_dir', 'stylesheet_dir', 'template', 'stylesheet', + 'screenshot', 'description', 'author', 'tags', 'theme_root', 'theme_root_uri', + ); + + return in_array( $offset, $properties ); + } + + /** + * __get() magic method for properties formerly returned by current_theme_info() + */ + public function __get( $offset ) { + switch ( $offset ) { + case 'name' : + case 'title' : + return $this->get('Name'); + case 'version' : + return $this->get('Version'); + case 'parent_theme' : + return $this->parent ? $this->parent->get('Name') : ''; + case 'template_dir' : + return $this->get_template_directory(); + case 'stylesheet_dir' : + return $this->get_stylesheet_directory(); + case 'template' : + return $this->get_template(); + case 'stylesheet' : + return $this->get_stylesheet(); + case 'screenshot' : + return $this->get_screenshot(); + // 'author' and 'description' did not previously return translated data. + case 'description' : + return $this->display('Description'); + case 'author' : + return $this->display('Author'); + case 'tags' : + return $this->get( 'Tags' ); + case 'theme_root' : + return $this->get_theme_root(); + case 'theme_root_uri' : + return $this->get_theme_root_uri(); + } + } + + /** + * Method to implement ArrayAccess for keys formerly returned by get_themes() + */ + public function offsetSet( $offset, $value ) {} + + /** + * Method to implement ArrayAccess for keys formerly returned by get_themes() + */ + public function offsetUnset( $offset ) {} + + /** + * Method to implement ArrayAccess for keys formerly returned by get_themes() + */ + public function offsetExists( $offset ) { + static $keys = array( + 'Name', 'Version', 'Status', 'Title', 'Author', 'Author Name', 'Author URI', 'Description', + 'Template', 'Stylesheet', 'Template Files', 'Stylesheet Files', 'Template Dir', 'Stylesheet Dir', + 'Screenshot', 'Tags', 'Theme Root', 'Theme Root URI', 'Parent Theme', + ); + + return in_array( $offset, $keys ); + } + + /** + * Method to implement ArrayAccess for keys formerly returned by get_themes() + */ + public function offsetGet( $offset ) { + switch ( $offset ) { + case 'Name' : + case 'Version' : + case 'Status' : + return $this->get( $offset ); + case 'Title' : + return $this->get('Name'); + // Author, Author Name, Author URI, and Description did not + // previously return translated data. We are doing so now. + // Title and Name could have been used as the key for get_themes(), + // so both to remain untranslated for back compatibility. + case 'Author' : + return $this->display( 'Author'); + case 'Author Name' : + return $this->display( 'Author', false); + case 'Author URI' : + return $this->display('AuthorURI'); + case 'Description' : + return $this->display( 'Description'); + case 'Template' : + return $this->get_template(); + case 'Stylesheet' : + return $this->get_stylesheet(); + case 'Template Files' : + $files = $this->get_files('php'); + foreach ( $files as &$file ) + $file = $this->theme_root . '/' . $file; + return $files; + case 'Stylesheet Files' : + $files = $this->get_files('css'); + foreach ( $files as &$file ) + $file = $this->theme_root . '/' . $file; + return $files; + case 'Template Dir' : + return $this->get_template_directory(); + case 'Stylesheet Dir' : + return $this->get_stylesheet_directory(); + case 'Screenshot' : + return $this->get_screenshot(); + case 'Tags' : + return $this->get('Tags'); + case 'Theme Root' : + return $this->get_theme_root(); + case 'Theme Root URI' : + return $this->get_theme_root_uri(); + case 'Parent Theme' : + return $this->parent ? $this->parent->get('Name') : ''; + default : + return null; + } + } + + /** + * Returns errors property. + * + * @since 3.4.0 + * @access public + * + * @return WP_Error|bool WP_Error if there are errors, or false. + */ + public function errors() { + return is_wp_error( $this->errors ) ? $this->errors : false; + } + + /** + * Returns reference to the parent theme. + * + * @since 3.4.0 + * @access public + * + * @return WP_Theme|bool Parent theme, or false if the current theme is not a child theme. + */ + public function parent() { + return isset( $this->parent ) ? $this->parent : false; + } + + /** + * Adds theme data to cache. + * + * Cache entries keyed by the theme and the type of data. + * + * @access private + * @since 3.4.0 + * + * @param string $key Type of data to store (theme, screenshot, screenshot_count, files, headers) + * @param string $data Data to store + * @return bool Return value from wp_cache_add() + */ + private function cache_add( $key, $data ) { + return wp_cache_add( $key . '-' . $this->theme_root . '/' . $this->stylesheet, $data, 'themes', self::$cache_expiration ); + } + + /** + * Gets theme data from cache. + * + * Cache entries are keyed by the theme and the type of data. + * + * @access private + * @since 3.4.0 + * + * @param string $key Type of data to retrieve (theme, screenshot, screenshot_count, files, headers) + * @return mixed Retrieved data + */ + private function cache_get( $key ) { + return wp_cache_get( $key . '-' . $this->theme_root . '/' . $this->stylesheet, 'themes' ); + } + + /** + * Clears the cache for the theme. + * + * @access public + * @since 3.4.0 + */ + public function cache_delete() { + foreach ( array( 'theme', 'screenshot', 'screenshot_count', 'files', 'headers' ) as $key ) + wp_cache_delete( $key . '-' . $this->theme_root . '/' . $this->stylesheet, 'themes' ); + } + + /** + * Gets a theme header. + * + * The header is sanitized. + * + * @access public + * @since 3.4.0 + * + * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status. + * @return string String on success, false on failure. + */ + public function get( $header ) { + if ( ! isset( $this->headers[ $header ] ) ) + return false; + return $this->sanitize_header( $header, $this->headers[ $header ] ); + + if ( ! isset( $this->headers_sanitized ) ) { + $this->headers_sanitized = $this->cache_get( 'headers' ); + if ( ! is_array( $this->headers_sanitized ) ) + $headers = array(); + } + + if ( isset( $this->headers_sanitized[ $header ] ) ) + return $this->headers_sanitized[ $header ]; + + // If an external object cache does not consider themes to be a persistent group, sanitize everything and cache it. + if ( self::$persistently_cache ) { + foreach ( array_keys( $this->headers ) as $header ) + $this->headers_sanitized[ $header ] = $this->sanitize_header( $header, $this->headers[ $header ] ); + $this->cache_add( 'headers', $this->headers_sanitized ); + } else { + $this->headers_sanitized[ $header ] = $this->sanitize_header( $header, $this->headers[ $header ] ); + } + + return $this->headers_sanitized[ $header ]; + } + + /** + * Gets a theme header ready for display (marked up, translated). + * + * @access public + * @since 3.4.0 + * + * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status. + * @param bool $markup Optional. Whether to mark up the header. Defaults to true. + * @param bool $translate Optional. Whether to translate the header. Defaults to true. + * @return string Processed header, false on failure. + */ + public function display( $header, $markup = true, $translate = true ) { + $value = $this->get( $header ); + if ( false === $value || '' === $value ) + return $value; + + if ( ! $this->load_textdomain() ) + $translate = false; + + if ( $translate ) + $value = $this->translate_header( $header, $value ); + + if ( $markup ) + $value = $this->markup_header( $header, $value, $translate ); + + return $value; + } + + /** + * Sanitize a theme header. + * + * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status. + * @param string $value Value to sanitize. + */ + private function sanitize_header( $header, $value ) { + switch ( $header ) { + case 'Status' : + if ( ! $value ) { + $value = 'public'; + break; + } + // Fall through otherwise. + case 'Name' : + case 'Author' : + static $header_tags = array( + 'abbr' => array( 'title' => true ), + 'acronym' => array( 'title' => true ), + 'code' => true, + 'em' => true, + 'strong' => true, + ); + $value = wp_kses( $value, $header_tags ); + break; + case 'Description' : + static $header_tags_with_a = array( + 'a' => array( 'href' => true, 'title' => true ), + 'abbr' => array( 'title' => true ), + 'acronym' => array( 'title' => true ), + 'code' => true, + 'em' => true, + 'strong' => true, + ); + $value = wp_kses( $value, $header_tags_with_a ); + break; + case 'ThemeURI' : + case 'AuthorURI' : + $value = esc_url( $value ); + break; + case 'Tags' : + $value = array_filter( array_map( 'trim', explode( ',', strip_tags( $value ) ) ) ); + break; + } + + return $value; + } + + /** + * Mark up a theme header. + * + * @access private + * @since 3.4.0 + * + * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status. + * @param string $value Value to mark up. + * @param string $translate Whether the header has been translated. + * @return string Value, marked up. + */ + private function markup_header( $header, $value, $translate ) { + switch ( $header ) { + case 'Description' : + $value = wptexturize( $value ); + break; + case 'Author' : + if ( $this->get('AuthorURI') ) { + static $attr = null; + if ( ! isset( $attr ) ) + $attr = esc_attr__( 'Visit author homepage' ); + $value = sprintf( '%3$s', $this->display( 'AuthorURI', true, $translate ), $attr, $value ); + } elseif ( ! $value ) { + $value = __( 'Anonymous' ); + } + break; + case 'Tags' : + static $comma = null; + if ( ! isset( $comma ) ) { + /* translators: used between list items, there is a space after the comma */ + $comma = __( ', ' ); + } + $value = implode( $comma, $value ); + break; + } + + return $value; + } + + /** + * Translate a theme header. + * + * @access private + * @since 3.4.0 + * + * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status. + * @param string $value Value to translate. + * @return string Translated value. + */ + private function translate_header( $header, $value ) { + switch ( $header ) { + case 'Name' : + // Cached for sorting reasons. + if ( isset( $this->name_translated ) ) + return $this->name_translated; + $this->name_translated = translate( $value, $this->get('TextDomain' ) ); + return $this->name_translated; + case 'Tags' : + if ( empty( $value ) ) + return $value; + + static $tags_list; + if ( ! isset( $tags_list ) ) { + $tags_list = array(); + $feature_list = get_theme_feature_list( false ); // No API + foreach ( $feature_list as $tags ) + $tags_list += $tags; + } + + foreach ( $value as &$tag ) { + if ( isset( $tags_list[ $tag ] ) ) + $tag = $tags_list[ $tag ]; + } + + return $value; + break; + default : + $value = translate( $value, $this->get('TextDomain') ); + } + return $value; + } + + /** + * The directory name of the theme's "stylesheet" files, inside the theme root. + * + * In the case of a child theme, this is directory name of the the child theme. + * Otherwise, get_stylesheet() is the same as get_template(). + * + * @since 3.4.0 + * @access public + * + * @return string Stylesheet + */ + public function get_stylesheet() { + return $this->stylesheet; + } + + /** + * The directory name of the theme's "template" files, inside the theme root. + * + * In the case of a child theme, this is the directory name of the parent theme. + * Otherwise, the get_template() is the same as get_stylesheet(). + * + * @since 3.4.0 + * @access public + * + * @return string Template + */ + public function get_template() { + return $this->template; + } + + /** + * Whether a theme is a child theme. + * + * @since 3.4.0 + * @access public + * + * @return bool True if a theme is a child theme, false otherwise. + */ + public function is_child_theme() { + return $this->template !== $this->stylesheet; + } + + /** + * Returns the absolute path to the directory of a theme's "stylesheet" files. + * + * In the case of a child theme, this is the absolute path to the directory + * of the child theme's files. + * + * @since 3.4.0 + * @access public + * + * @return string Absolute path of the stylesheet directory. + */ + public function get_stylesheet_directory() { + if ( $this->errors && in_array( 'theme_root_missing', $this->errors->get_error_codes() ) ) + return ''; + + return $this->theme_root . '/' . $this->stylesheet; + } + + /** + * Returns the absolute path to the directory of a theme's "template" files. + * + * In the case of a child theme, this is the absolute path to the directory + * of the parent theme's files. + * + * @since 3.4.0 + * @access public + * + * @return string Absolute path of the template directory. + */ + public function get_template_directory() { + if ( $this->parent ) + $theme_root = $this->parent->theme_root; + else + $theme_root = $this->theme_root; + + return $theme_root . '/' . $this->template; + } + + /** + * Returns the URL to the directory of a theme's "stylesheet" files. + * + * In the case of a child theme, this is the URL to the directory of the + * child theme's files. + * + * @since 3.4.0 + * @access public + * + * @return string URL to the stylesheet directory. + */ + public function get_stylesheet_directory_uri() { + return $this->get_theme_root_uri() . '/' . $this->stylesheet; + } + + /** + * Returns the URL to the directory of a theme's "template" files. + * + * In the case of a child theme, this is the URL to the directory of the + * parent theme's files. + * + * @since 3.4.0 + * @access public + * + * @return string URL to the template directory. + */ + public function get_template_directory_uri() { + if ( $this->parent ) + $theme_root_uri = $this->parent->get_theme_root_uri(); + else + $theme_root_uri = $this->get_theme_root_uri(); + + return $theme_root . '/' . $this->template; + } + + /** + * The absolute path to the directory of the theme root. + * + * This is typically the absolute path to wp-content/themes. + * + * @since 3.4.0 + * @access public + * + * @return string Theme root. + */ + public function get_theme_root() { + return $this->theme_root; + } + + /** + * Returns the URL to the directory of the theme root. + * + * This is typically the absolute path to wp-content/themes. + * + * @since 3.4.0 + * @access public + * + * @return string Theme root URI. + */ + public function get_theme_root_uri() { + if ( 0 === strpos( WP_CONTENT_DIR, $this->theme_root ) ) + return str_replace( WP_CONTENT_DIR, content_url(), $this->theme_root ); + // Give up, send it off to the filter. + return get_theme_root_uri( $this->stylesheet ); + } + + /** + * Returns the main screenshot file for the theme. + * + * The main screenshot is called screenshot.png. gif and jpg extensions are also allowed. + * + * Screenshots for a theme must be in the stylesheet directory. (In the case of a child + * theme, a parent theme's screenshots are inherited.) + * + * @since 3.4.0 + * @access public + * + * @param string $uri Type of URL to include, either relative or absolute. Defaults to relative. + * @return mixed Screenshot file. False if the theme does not have a screenshot. + */ + public function get_screenshot( $uri = 'relative' ) { + $screenshot = $this->cache_get( 'screenshot' ); + if ( $screenshot ) { + if ( 'absolute' == $uri ) + return $this->get_stylesheet_directory_uri() . '/' . $screenshot; + return $screenshot; + } elseif ( 0 === $screenshot ) { + return false; + } + + foreach ( array( 'png', 'gif', 'jpg', 'jpeg' ) as $ext ) { + if ( file_exists( $this->get_stylesheet_directory() . "/screenshot.$ext" ) ) { + $this->cache_add( 'screenshot', 'screenshot.' . $ext ); + if ( 'absolute' == $uri ) + return $this->get_stylesheet_directory_uri() . '/' . 'screenshot.' . $ext; + return 'screenshot.' . $ext; + } + } + + $this->cache_add( 'screenshot', 0 ); + $this->cache_add( 'screenshot_count', 0 ); + return false; + } + + /** + * Returns the number of screenshots for a theme. + * + * The first screenshot may be called screenshot.png, .gif, or .jpg. Subsequent + * screenshots can be screenshot-2.png, screenshot-3.png, etc. The count must + * be consecutive for screenshots to be counted, and all screenshots beyond the + * initial one must be image/png files. + * + * @see WP_Theme::get_screenshot() + * @since 3.4.0 + * @access public + * + * @return int Number of screenshots. Can be 0. + */ + public function get_screenshot_count() { + $screenshot_count = $this->cache_get( 'screenshot_count' ); + if ( is_numeric( $screenshot_count ) ) + return $screenshot_count; + + // This will set the screenshot cache. + // If there is no screenshot, the screenshot_count cache will also be set. + if ( ! $screenshot = $this->get_screenshot() ) + return 0; + + $prefix = $this->get_stylesheet() . '/screenshot-'; + $files = self::scandir( $this->get_stylesheet_directory(), $this->get_stylesheet(), 'png', 0 ); + + $screenshot_count = 1; + while ( in_array( $prefix . ( $screenshot_count + 1 ) . '.png', $files['png'] ) ) + $screenshot_count++; + + $this->cache_add( 'screenshot_count', $screenshot_count ); + return $screenshot_count; + } + + /** + * Returns an array of screenshot filenames. + * + * @see WP_Theme::get_screenshot() + * @see WP_Theme::get_screenshot_count() + * @since 3.4.0 + * @access public + * + * @return array Screenshots. + */ + public function get_screenshots() { + if ( ! $count = $this->get_screenshot_count() ) + return array(); + + $screenshots = array( $this->get_screenshot() ); + for ( $i = 2; $i <= $count; $i++ ) + $screenshots[] = 'screenshot-' . $i . '.png'; + return $screenshots; + } + + /** + * Return files in the template and stylesheet directories. + * + * @since 3.4.0 + * @access public + * + * @param string|null $type Optional. Type of files to return, either 'php' or 'css'. Defaults to null, for both. + * @return array If a specific $type is requested, returns an array of PHP files. If no $type is requested, + * returns an array, with the keys being the file types, and the values being an array of files for those type. + */ + public function get_files( $type = null, $include_parent_files = false ) { + $files = $this->cache_get( 'files' ); + if ( ! is_array( $files ) ) { + if ( $include_parent_files || ! $this->is_child_theme() ) + // Template files can be one level down for the purposes of the theme editor, so this should be $depth = 1. + // Todo: We ignore this for now, but this is why the branching is weird. + $files = (array) self::scandir( $this->get_template_directory(), $this->get_template(), array( 'php', 'css' ) ); + if ( $this->is_child_theme() ) + $files = array_merge_recursive( $files, (array) self::scandir( $this->get_stylesheet_directory(), $this->get_stylesheet(), array( 'php', 'css' ) ) ); + foreach ( $files as &$group ) + sort( $group ); + $this->cache_add( 'files', $files ); + } + + if ( null === $type ) + return $files; + elseif ( isset( $files[ $type ] ) ) + return $files[ $type ]; + + return array(); + } + + public function get_page_templates() { + // If you screw up your current theme and we invalidate your parent, most things still work. Let it slide. + if ( $this->errors() && $this->errors()->get_error_codes() !== array( 'theme_parent_invalid' ) ) + return array(); + + $page_templates = $this->cache_get( 'page_templates' ); + if ( is_array( $page_templates ) ) + return $page_templates; + $page_templates = array(); + + $files = (array) self::scandir( $this->get_template_directory(), $this->get_template_directory(), 'php' ); + if ( $this->is_child_theme() ) + $files = array_merge_recursive( $files, (array) self::scandir( $this->get_stylesheet_directory(), $this->get_stylesheet_directory(), 'php' ) ); + + foreach ( $files['php'] as $file ) { + $headers = get_file_data( $file, array( 'Name' => 'Template Name' ) ); + if ( empty( $headers['Name'] ) ) + continue; + $page_templates[ $headers['Name'] ] = basename( $file ); + } + + $this->cache_add( 'page_templates', $page_templates ); + return $page_templates; + } + + /** + * Scans a directory for files of a certain extension. + * + * @since 3.4.0 + * @access public + * + * @param string $path Absolute path to search. + * @param string $relative_path The basename of the absolute path. Used to control the returned path + * for the found files, particularly when this function recurses to lower depths. + * @param array|string $extensions Array of extensions to find, or string of a single extension. + * @depth int How deep to search for files. Optional, defaults to a flat scan (0 depth). + */ + private static function scandir( $path, $relative_path, $extensions, $depth = 0 ) { + if ( is_array( $extensions ) ) + $extensions = implode( '|', $extensions ); + + if ( ! is_dir( $path ) ) + return false; + + $results = scandir( $path ); + $files = array(); + + foreach ( $results as $result ) { + if ( '.' == $result || '..' == $result ) + continue; + if ( is_dir( $path . '/' . $result ) ) { + if ( ! $depth ) + continue; + $found = self::scandir( $path . '/' . $result, $relative_path . '/' . $result, $extensions, $depth - 1 ); + $files = array_merge_recursive( $files, $found ); + } elseif ( preg_match( '~\.(' . $extensions . ')$~', $result, $match ) ) { + if ( ! isset( $files[ $match[1] ] ) ) + $files[ $match[1] ] = array( $relative_path . '/'. $result ); + else + $files[ $match[1] ][] = $relative_path . '/' . $result; + } + } + return $files; + } + + /** + * Loads the theme's textdomain. + * + * Translation files are not inherited from the parent theme. Todo: if this fails for the + * child theme, it should probably try to load the parent theme's translations. + * + * @since 3.4.0 + * @access public + * + * @return True if the textdomain was successfully loaded or has already been loaded. False if + * no textdomain was specified in the file headers, or if the domain could not be loaded. + */ + public function load_textdomain() { + if ( isset( $this->textdomain_loaded ) ) + return $this->textdomain_loaded; + + $textdomain = $this->get('TextDomain'); + if ( ! $textdomain ) { + $this->textdomain_loaded = false; + return false; + } + + if ( is_textdomain_loaded( $textdomain ) ) { + $this->textdomain_loaded = true; + return true; + } + + $path = $this->get_stylesheet_directory(); + if ( $domainpath = $this->get('DomainPath') ) + $path .= $domainpath; + + $this->textdomain_loaded = load_theme_textdomain( $textdomain, $path ); + return $this->textdomain_loaded; + } + + /** + * Whether the theme is allowed (multisite only). + * + * @since 3.4.0 + * @access public + * + * @param string $check Optional. Whether to check only the 'network'-wide settings, the 'site' + * settings, or 'both'. Defaults to 'both'. + * @param int $blog_id Optional. Ignored if only network-wide settings are checked. Defaults to current blog. + * @return bool Whether the theme is allowed for the network. Returns true in single-site. + */ + public function is_allowed( $check = 'both', $blog_id = null ) { + if ( ! is_multisite() ) + return true; + + if ( 'both' == $check || 'network' == $check ) { + $allowed = self::get_allowed_on_network(); + if ( ! empty( $allowed[ $this->get_stylesheet() ] ) ) + return true; + } + + if ( 'both' == $check || 'site' == $check ) { + $allowed = self::get_allowed_on_site( $blog_id ); + if ( ! empty( $allowed[ $this->get_stylesheet() ] ) ) + return true; + } + + return false; + } + + /** + * Returns array of stylesheet names of themes allowed on the site or network. + * + * @since 3.4.0 + * @access public + * + * @param int $blog_id Optional. Defaults to current blog. + * @return array Array of stylesheet names. + */ + public static function get_allowed( $blog_id = null ) { + return array_merge( self::get_allowed_on_network(), self::get_allowed_on_site( $blog_id ) ); + } + + /** + * Returns array of stylesheet names of themes allowed on the network. + * + * @since 3.4.0 + * @access public + * + * @return array Array of stylesheet names. + */ + public static function get_allowed_on_network() { + static $allowed_themes; + if ( ! isset( $allowed_themes ) ) + $allowed_themes = (array) get_site_option( 'allowedthemes' ); + return $allowed_themes; + } + + /** + * Returns array of stylesheet names of themes allowed on the site. + * + * @since 3.4.0 + * @access public + * + * @param int $blog_id Optional. Defaults to current blog. + * @return array Array of stylesheet names. + */ + public static function get_allowed_on_site( $blog_id = null ) { + static $allowed_themes = array(); + if ( ! $blog_id ) + $blog_id = get_current_blog_id(); + + if ( ! isset( $allowed_themes[ $blog_id ] ) ) { + if ( $blog_id == get_current_blog_id() ) + $allowed_themes[ $blog_id ] = (array) get_option( 'allowedthemes' ); + else + $allowed_themes[ $blog_id ] = (array) get_blog_option( $blog_id, 'allowedthemes' ); + } + + return $allowed_themes[ $blog_id ]; + } + + /** + * Sort themes by name. + */ + public static function sort_by_name( &$themes ) { + if ( 0 === strpos( get_locale(), 'en_' ) ) { + uasort( $themes, array( 'WP_Theme', '_name_sort' ) ); + } else { + 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 + * @access public + */ + private static function _name_sort( $a, $b ) { + return strnatcasecmp( $a->headers['Name'], $b->headers['Name'] ); + } + + /** + * Name sort (with translation). + * + * @since 3.4.0 + * @access public + */ + private static function _name_sort_i18n( $a, $b ) { + // Don't mark up; Do translate. + return strnatcasecmp( $a->display( 'Name', false, true ), $b->display( 'Name', false, true ) ); + } +} diff --git a/wp-includes/deprecated.php b/wp-includes/deprecated.php index ba1a7ea20e..7863ba23c7 100644 --- a/wp-includes/deprecated.php +++ b/wp-includes/deprecated.php @@ -2906,4 +2906,50 @@ function debug_fwrite( $fp, $string ) { */ function debug_fclose( $fp ) { _deprecated_function( __FUNCTION__, 'error_log()' ); +} + +/** + * Retrieve list of themes with theme data in theme directory. + * + * The theme is broken, if it doesn't have a parent theme and is missing either + * style.css and, or index.php. If the theme has a parent theme then it is + * broken, if it is missing style.css; index.php is optional. + * + * @since 1.5.0 + * @global array $wp_themes Stores the working themes. + * + * @return array Theme list with theme data. + */ +function get_themes() { + _deprecated_function( __FUNCTION__, '3.4', 'wp_get_themes()' ); + + global $wp_themes; + if ( isset( $wp_themes ) ) + return $wp_themes; + + $themes = wp_get_themes(); + $wp_themes = array(); + + foreach ( $themes as $theme ) { + $wp_themes[ $theme->get('Name') ] = $theme; + } + + return $wp_themes; +} + +/** + * Retrieve theme data. + * + * @since 1.5.0 + * + * @param string $theme Theme name. + * @return array|null Null, if theme name does not exist. Theme data, if exists. + */ +function get_theme( $theme ) { + _deprecated_function( __FUNCTION__, '3.4', 'wp_get_theme($stylesheet)' ); + + $themes = get_themes(); + if ( is_array( $themes ) && array_key_exists( $theme, $themes ) ) + return $themes[$theme]; + return null; } \ No newline at end of file diff --git a/wp-includes/theme.php b/wp-includes/theme.php index 83865b892d..f32ff50278 100644 --- a/wp-includes/theme.php +++ b/wp-includes/theme.php @@ -6,6 +6,95 @@ * @subpackage Theme */ +/** + * Returns an array of WP_Theme objects based on the arguments. + * + * Despite advances over get_themes(), this function is still quite expensive, and grows + * linearly with additional themes. Stick to wp_get_theme() if possible. + * + * @since 3.4.0 + * + * @param array $args Arguments. Currently 'errors' (defaults to false), 'allowed' + * (true, false; null for either; defaults to null; only applies to multisite), and 'blog_id' + * (defaults to current blog; used to find allowed themes; only applies to multisite). + * @return Array of WP_Theme objects. + */ +function wp_get_themes( $args = array() ) { + global $wp_theme_directories; + + $defaults = array( 'errors' => false, 'allowed' => null, 'blog_id' => 0 ); + $args = wp_parse_args( $args, $defaults ); + + static $_themes; + if ( ! isset( $_themes ) ) { + $_themes = array(); + $theme_data = search_theme_directories(); + // Make sure the current theme wins out, in case search_theme_directories() picks the wrong + // one in the case of a conflict. (Normally, last registered theme root wins.) + $current_theme = get_stylesheet(); + $current_theme_root = get_raw_theme_root( $current_theme ); + if ( ! in_array( $current_theme_root, $wp_theme_directories ) ) + $current_theme_root = WP_CONTENT_DIR . $current_theme_root; + foreach ( (array) $theme_data as $theme_slug => $data ) { + if ( $current_theme == $theme_slug && $current_theme_root != $data['theme_root'] ) + $_themes[ $theme_slug ] = new WP_Theme( $theme_slug, $current_theme_root ); + else + $_themes[ $theme_slug ] = new WP_Theme( $theme_slug, $data['theme_root'] ); + } + } + + $themes = $_themes; + if ( empty( $themes ) ) + return $themes; + + if ( null !== $args['errors'] ) { + foreach ( $themes as $theme_slug => $theme ) { + if ( $theme->errors() != $args['errors'] ) + unset( $themes[ $theme_slug ] ); + } + } + + if ( is_multisite() && null !== $args['allowed'] ) { + if ( $allowed = $args['allowed'] ) { + if ( 'network' == $allowed ) + $themes = array_intersect_key( $themes, WP_Theme::get_allowed_on_network( $args['blog_id'] ) ); + elseif ( 'site' == $allowed ) + $themes = array_intersect_key( $themes, WP_Theme::get_allowed_on_site( $args['blog_id'] ) ); + else + $themes = array_intersect_key( $themes, WP_Theme::get_allowed( $args['blog_id'] ) ); + } else { + $themes = array_diff_key( $themes, WP_Theme::get_allowed( $args['blog_id'] ) ); + } + } + + return $themes; +} + +/** + * Gets a WP_Theme object for a theme. + * + * @since 3.4.0 + * + * @param string $stylesheet Directory name for the theme. Optional. Defaults to current theme. + * @param string $theme_root Absolute path of the theme root to look in. Optional. If not specified, get_raw_theme_root() + * is used to calculate the theme root for the $stylesheet provided (or current theme). + * @return WP_Theme + */ +function wp_get_theme( $stylesheet = null, $theme_root = null ) { + global $wp_theme_directories; + + if ( empty( $stylesheet ) ) + $stylesheet = get_stylesheet(); + + if ( empty( $theme_root ) ) { + $theme_root = get_raw_theme_root( $stylesheet ); + if ( ! in_array( $theme_root, $wp_theme_directories ) ) + $theme_root = WP_CONTENT_DIR . $theme_root; + } + + return new WP_Theme( $stylesheet, $theme_root ); +} + /** * Whether a child theme is in use. * @@ -246,218 +335,6 @@ function get_theme_data( $theme_file ) { return $theme_data; } -/** - * Retrieve list of themes with theme data in theme directory. - * - * The theme is broken, if it doesn't have a parent theme and is missing either - * style.css and, or index.php. If the theme has a parent theme then it is - * broken, if it is missing style.css; index.php is optional. The broken theme - * list is saved in the {@link $wp_broken_themes} global, which is displayed on - * the theme list in the administration panels. - * - * @since 1.5.0 - * @global array $wp_broken_themes Stores the broken themes. - * @global array $wp_themes Stores the working themes. - * - * @return array Theme list with theme data. - */ -function get_themes() { - global $wp_themes, $wp_broken_themes; - - if ( isset($wp_themes) ) - return $wp_themes; - - if ( !$theme_files = search_theme_directories() ) - return false; - - asort( $theme_files ); - - $wp_themes = array(); - - foreach ( (array) $theme_files as $theme_file ) { - $theme_root = $theme_file['theme_root']; - $theme_file = $theme_file['theme_file']; - - if ( !is_readable("$theme_root/$theme_file") ) { - $wp_broken_themes[$theme_file] = array('Name' => $theme_file, 'Title' => $theme_file, 'Description' => __('File not readable.')); - continue; - } - - $theme_data = get_theme_data("$theme_root/$theme_file"); - - $name = $theme_data['Name']; - $title = $theme_data['Title']; - $description = wptexturize($theme_data['Description']); - $version = $theme_data['Version']; - $author = $theme_data['Author']; - $template = $theme_data['Template']; - $stylesheet = dirname($theme_file); - - $screenshot = false; - foreach ( array('png', 'gif', 'jpg', 'jpeg') as $ext ) { - if (file_exists("$theme_root/$stylesheet/screenshot.$ext")) { - $screenshot = "screenshot.$ext"; - break; - } - } - - if ( empty($name) ) { - $name = dirname($theme_file); - $title = $name; - } - - $parent_template = $template; - - if ( empty($template) ) { - if ( file_exists("$theme_root/$stylesheet/index.php") ) - $template = $stylesheet; - else - continue; - } - - $template = trim( $template ); - - if ( !file_exists("$theme_root/$template/index.php") ) { - $parent_dir = dirname(dirname($theme_file)); - if ( file_exists("$theme_root/$parent_dir/$template/index.php") ) { - $template = "$parent_dir/$template"; - $template_directory = "$theme_root/$template"; - } else { - /** - * The parent theme doesn't exist in the current theme's folder or sub folder - * so lets use the theme root for the parent template. - */ - if ( isset($theme_files[$template]) && file_exists( $theme_files[$template]['theme_root'] . "/$template/index.php" ) ) { - $template_directory = $theme_files[$template]['theme_root'] . "/$template"; - } else { - if ( empty( $parent_template) ) - $wp_broken_themes[$name] = array('Name' => $name, 'Title' => $title, 'Description' => __('Template is missing.'), 'error' => 'no_template'); - else - $wp_broken_themes[$name] = array('Name' => $name, 'Title' => $title, 'Description' => sprintf( __('The parent theme is missing. Please install the "%s" parent theme.'), $parent_template ), 'error' => 'no_parent', 'parent' => $parent_template ); - continue; - } - - } - } else { - $template_directory = trim( $theme_root . '/' . $template ); - } - - $stylesheet_files = array(); - $template_files = array(); - - $stylesheet_dir = @ dir("$theme_root/$stylesheet"); - if ( $stylesheet_dir ) { - while ( ($file = $stylesheet_dir->read()) !== false ) { - if ( !preg_match('|^\.+$|', $file) ) { - if ( preg_match('|\.css$|', $file) ) - $stylesheet_files[] = "$theme_root/$stylesheet/$file"; - elseif ( preg_match('|\.php$|', $file) ) - $template_files[] = "$theme_root/$stylesheet/$file"; - } - } - @ $stylesheet_dir->close(); - } - - $template_dir = @ dir("$template_directory"); - if ( $template_dir ) { - while ( ($file = $template_dir->read()) !== false ) { - if ( preg_match('|^\.+$|', $file) ) - continue; - if ( preg_match('|\.php$|', $file) ) { - $template_files[] = "$template_directory/$file"; - } elseif ( is_dir("$template_directory/$file") ) { - $template_subdir = @ dir("$template_directory/$file"); - if ( !$template_subdir ) - continue; - while ( ($subfile = $template_subdir->read()) !== false ) { - if ( preg_match('|^\.+$|', $subfile) ) - continue; - if ( preg_match('|\.php$|', $subfile) ) - $template_files[] = "$template_directory/$file/$subfile"; - } - @ $template_subdir->close(); - } - } - @ $template_dir->close(); - } - - //Make unique and remove duplicates when stylesheet and template are the same i.e. most themes - $template_files = array_unique($template_files); - $stylesheet_files = array_unique($stylesheet_files); - - $template_dir = $template_directory; - $stylesheet_dir = $theme_root . '/' . $stylesheet; - - if ( empty($template_dir) ) - $template_dir = '/'; - if ( empty($stylesheet_dir) ) - $stylesheet_dir = '/'; - - // Check for theme name collision. This occurs if a theme is copied to - // a new theme directory and the theme header is not updated. Whichever - // theme is first keeps the name. Subsequent themes get a suffix applied. - // Default themes themes always trump their pretenders. - if ( isset($wp_themes[$name]) ) { - $trump_cards = array( - 'classic' => 'WordPress Classic', - 'default' => 'WordPress Default', - 'twentyten' => 'Twenty Ten', - 'twentyeleven' => 'Twenty Eleven', - 'twentytwelve' => 'Twenty Twelve', - ); - if ( isset( $trump_cards[ $stylesheet ] ) && $name == $trump_cards[ $stylesheet ] ) { - // If another theme has claimed to be one of our default themes, move - // them aside. - $suffix = $wp_themes[$name]['Stylesheet']; - $new_name = "$name/$suffix"; - $wp_themes[$new_name] = $wp_themes[$name]; - $wp_themes[$new_name]['Name'] = $new_name; - } else { - $name = "$name/$stylesheet"; - } - } - - $wp_themes[$name] = array( - 'Name' => $name, - 'Title' => $title, - 'Description' => $description, - 'Author' => $author, - 'Author Name' => $theme_data['AuthorName'], - 'Author URI' => $theme_data['AuthorURI'], - 'Version' => $version, - 'Template' => $template, - 'Stylesheet' => $stylesheet, - 'Template Files' => $template_files, - 'Stylesheet Files' => $stylesheet_files, - 'Template Dir' => $template_dir, - 'Stylesheet Dir' => $stylesheet_dir, - 'Status' => $theme_data['Status'], - 'Screenshot' => $screenshot, - 'Tags' => $theme_data['Tags'], - 'Theme Root' => $theme_root, - 'Theme Root URI' => str_replace( WP_CONTENT_DIR, content_url(), $theme_root ), - ); - } - - unset($theme_files); - - /* Resolve theme dependencies. */ - $theme_names = array_keys( $wp_themes ); - foreach ( (array) $theme_names as $theme_name ) { - $wp_themes[$theme_name]['Parent Theme'] = ''; - if ( $wp_themes[$theme_name]['Stylesheet'] != $wp_themes[$theme_name]['Template'] ) { - foreach ( (array) $theme_names as $parent_theme_name ) { - if ( ($wp_themes[$parent_theme_name]['Stylesheet'] == $wp_themes[$parent_theme_name]['Template']) && ($wp_themes[$parent_theme_name]['Template'] == $wp_themes[$theme_name]['Template']) ) { - $wp_themes[$theme_name]['Parent Theme'] = $wp_themes[$parent_theme_name]['Name']; - break; - } - } - } - } - - return $wp_themes; -} - /** * Retrieve theme roots. * @@ -479,23 +356,6 @@ function get_theme_roots() { return $theme_roots; } -/** - * Retrieve theme data. - * - * @since 1.5.0 - * - * @param string $theme Theme name. - * @return array|null Null, if theme name does not exist. Theme data, if exists. - */ -function get_theme($theme) { - $themes = get_themes(); - - if ( is_array( $themes ) && array_key_exists( $theme, $themes ) ) - return $themes[$theme]; - - return null; -} - /** * Retrieve current theme display name. * @@ -508,29 +368,10 @@ function get_theme($theme) { * @return string */ function get_current_theme() { - if ( $theme = get_option('current_theme') ) + if ( $theme = get_option( 'current_theme' ) ) return $theme; - $themes = get_themes(); - $current_theme = 'Twenty Eleven'; - - if ( $themes ) { - $theme_names = array_keys( $themes ); - $current_template = get_option( 'template' ); - $current_stylesheet = get_option( 'stylesheet' ); - - foreach ( (array) $theme_names as $theme_name ) { - if ( $themes[$theme_name]['Stylesheet'] == $current_stylesheet && - $themes[$theme_name]['Template'] == $current_template ) { - $current_theme = $themes[$theme_name]['Name']; - break; - } - } - } - - update_option('current_theme', $current_theme); - - return $current_theme; + return wp_get_theme()->get('Name'); } /** diff --git a/wp-includes/update.php b/wp-includes/update.php index 6cd57843c5..edb1dc549d 100644 --- a/wp-includes/update.php +++ b/wp-includes/update.php @@ -235,32 +235,29 @@ function wp_update_themes() { if ( defined( 'WP_INSTALLING' ) ) return false; - if ( !function_exists( 'get_themes' ) ) - require_once( ABSPATH . 'wp-includes/theme.php' ); - - $installed_themes = get_themes( ); + $installed_themes = wp_get_themes(); $last_update = get_site_transient( 'update_themes' ); if ( ! is_object($last_update) ) $last_update = new stdClass; $themes = array(); $checked = array(); - $exclude_fields = array('Template Files', 'Stylesheet Files', 'Status', 'Theme Root', 'Theme Root URI', 'Template Dir', 'Stylesheet Dir', 'Description', 'Tags', 'Screenshot'); // Put slug of current theme into request. $themes['current_theme'] = get_option( 'stylesheet' ); - foreach ( (array) $installed_themes as $theme_title => $theme ) { - $themes[$theme['Stylesheet']] = array(); - $checked[$theme['Stylesheet']] = $theme['Version']; + foreach ( $installed_themes as $theme ) { + $checked[ $theme->get_stylesheet() ] = $theme->get('Version'); - $themes[$theme['Stylesheet']]['Name'] = $theme['Name']; - $themes[$theme['Stylesheet']]['Version'] = $theme['Version']; - - foreach ( (array) $theme as $key => $value ) { - if ( !in_array($key, $exclude_fields) ) - $themes[$theme['Stylesheet']][$key] = $value; - } + $themes[ $theme->get_stylesheet() ] = array( + 'Name' => $theme->get('Name'), + 'Title' => $theme->get('Name'), + 'Version' => $theme->get('Version'), + 'Author' => $theme->get('Author'), + 'Author URI' => $theme->get('AuthorURI'), + 'Template' => $theme->get_template(), + 'Stylesheet' => $theme->get_stylesheet(), + ); } // Check for update on a different schedule, depending on the page. diff --git a/wp-settings.php b/wp-settings.php index 07245f9988..c18367abe9 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -112,6 +112,7 @@ require( ABSPATH . WPINC . '/formatting.php' ); require( ABSPATH . WPINC . '/capabilities.php' ); require( ABSPATH . WPINC . '/query.php' ); require( ABSPATH . WPINC . '/theme.php' ); +require( ABSPATH . WPINC . '/class-wp-theme.php' ); require( ABSPATH . WPINC . '/template.php' ); require( ABSPATH . WPINC . '/user.php' ); require( ABSPATH . WPINC . '/meta.php' );