web/wp-includes/class-wp-theme.php
changeset 194 32102edaa81b
child 204 09a1c134465b
equal deleted inserted replaced
193:2f6f6f7551ca 194:32102edaa81b
       
     1 <?php
       
     2 /**
       
     3  * WP_Theme Class
       
     4  *
       
     5  * @package WordPress
       
     6  * @subpackage Theme
       
     7  */
       
     8 
       
     9 final class WP_Theme implements ArrayAccess {
       
    10 
       
    11 	/**
       
    12 	 * Headers for style.css files.
       
    13 	 *
       
    14 	 * @static
       
    15 	 * @access private
       
    16 	 * @var array
       
    17 	 */
       
    18 	private static $file_headers = array(
       
    19 		'Name'        => 'Theme Name',
       
    20 		'ThemeURI'    => 'Theme URI',
       
    21 		'Description' => 'Description',
       
    22 		'Author'      => 'Author',
       
    23 		'AuthorURI'   => 'Author URI',
       
    24 		'Version'     => 'Version',
       
    25 		'Template'    => 'Template',
       
    26 		'Status'      => 'Status',
       
    27 		'Tags'        => 'Tags',
       
    28 		'TextDomain'  => 'Text Domain',
       
    29 		'DomainPath'  => 'Domain Path',
       
    30 	);
       
    31 
       
    32 	/**
       
    33 	 * Default themes.
       
    34 	 *
       
    35 	 * @static
       
    36 	 * @access private
       
    37 	 * @var array
       
    38 	 */
       
    39 	private static $default_themes = array(
       
    40 		'classic'      => 'WordPress Classic',
       
    41 		'default'      => 'WordPress Default',
       
    42 		'twentyten'    => 'Twenty Ten',
       
    43 		'twentyeleven' => 'Twenty Eleven',
       
    44 	);
       
    45 
       
    46 	/**
       
    47 	 * Absolute path to the theme root, usually wp-content/themes
       
    48 	 *
       
    49 	 * @access private
       
    50 	 * @var string
       
    51 	 */
       
    52 	private $theme_root;
       
    53 
       
    54 	/**
       
    55 	 * Header data from the theme's style.css file.
       
    56 	 *
       
    57 	 * @access private
       
    58 	 * @var array
       
    59 	 */
       
    60 	private $headers = array();
       
    61 
       
    62 	/**
       
    63 	 * Header data from the theme's style.css file after being sanitized.
       
    64 	 *
       
    65 	 * @access private
       
    66 	 * @var array
       
    67 	 */
       
    68 	private $headers_sanitized;
       
    69 
       
    70 	/**
       
    71 	 * Header name from the theme's style.css after being translated.
       
    72 	 *
       
    73 	 * Cached due to sorting functions running over the translated name.
       
    74 	 */
       
    75 	private $name_translated;
       
    76 
       
    77 	/**
       
    78 	 * Errors encountered when initializing the theme.
       
    79 	 *
       
    80 	 * @access private
       
    81 	 * @var WP_Error
       
    82 	 */
       
    83 	private $errors;
       
    84 
       
    85 	/**
       
    86 	 * The directory name of the theme's files, inside the theme root.
       
    87 	 *
       
    88 	 * In the case of a child theme, this is directory name of the the child theme.
       
    89 	 * Otherwise, 'stylesheet' is the same as 'template'.
       
    90 	 *
       
    91 	 * @access private
       
    92 	 * @var string
       
    93 	 */
       
    94 	private $stylesheet;
       
    95 
       
    96 	/**
       
    97 	 * The directory name of the theme's files, inside the theme root.
       
    98 	 *
       
    99 	 * In the case of a child theme, this is the directory name of the parent theme.
       
   100 	 * Otherwise, 'template' is the same as 'stylesheet'.
       
   101 	 *
       
   102 	 * @access private
       
   103 	 * @var string
       
   104 	 */
       
   105 	private $template;
       
   106 
       
   107 	/**
       
   108 	 * A reference to the parent theme, in the case of a child theme.
       
   109 	 *
       
   110 	 * @access private
       
   111 	 * @var WP_Theme
       
   112 	 */
       
   113 	private $parent;
       
   114 
       
   115 	/**
       
   116 	 * URL to the theme root, usually an absolute URL to wp-content/themes
       
   117 	 *
       
   118 	 * @access private
       
   119 	 * var string
       
   120 	 */
       
   121 	private $theme_root_uri;
       
   122 
       
   123 	/**
       
   124 	 * Flag for whether the theme's textdomain is loaded.
       
   125 	 *
       
   126 	 * @access private
       
   127 	 * @var bool
       
   128 	 */
       
   129 	private $textdomain_loaded;
       
   130 
       
   131 	/**
       
   132 	 * Stores an md5 hash of the theme root, to function as the cache key.
       
   133 	 *
       
   134 	 * @access private
       
   135 	 * @var string
       
   136 	 */
       
   137 	private $cache_hash;
       
   138 
       
   139 	/**
       
   140 	 * Flag for whether the themes cache bucket should be persistently cached.
       
   141 	 *
       
   142 	 * Default is false. Can be set with the wp_cache_themes_persistently filter.
       
   143 	 *
       
   144 	 * @access private
       
   145 	 * @var bool
       
   146 	 */
       
   147 	private static $persistently_cache;
       
   148 
       
   149 	/**
       
   150 	 * Expiration time for the themes cache bucket.
       
   151 	 *
       
   152 	 * By default the bucket is not cached, so this value is useless.
       
   153 	 *
       
   154 	 * @access private
       
   155 	 * @var bool
       
   156 	 */
       
   157 	private static $cache_expiration = 1800;
       
   158 
       
   159 	/**
       
   160 	 * Constructor for WP_Theme.
       
   161 	 *
       
   162 	 * @param string $theme_dir Directory of the theme within the theme_root.
       
   163 	 * @param string $theme_root Theme root.
       
   164 	 * @param WP_Error|null $_child If this theme is a parent theme, the child may be passed for validation purposes.
       
   165 	 */
       
   166 	public function __construct( $theme_dir, $theme_root, $_child = null ) {
       
   167 		global $wp_theme_directories;
       
   168 
       
   169 		// Initialize caching on first run.
       
   170 		if ( ! isset( self::$persistently_cache ) ) {
       
   171 			self::$persistently_cache = apply_filters( 'wp_cache_themes_persistently', false, 'WP_Theme' );
       
   172 			if ( self::$persistently_cache ) {
       
   173 				wp_cache_add_global_groups( 'themes' );
       
   174 				if ( is_int( self::$persistently_cache ) )
       
   175 					self::$cache_expiration = self::$persistently_cache;
       
   176 			} else {
       
   177 				wp_cache_add_non_persistent_groups( 'themes' );
       
   178 			}
       
   179 		}
       
   180 
       
   181 		$this->theme_root = $theme_root;
       
   182 		$this->stylesheet = $theme_dir;
       
   183 
       
   184 		// Correct a situation where the theme is 'some-directory/some-theme' but 'some-directory' was passed in as part of the theme root instead.
       
   185 		if ( ! in_array( $theme_root, (array) $wp_theme_directories ) && in_array( dirname( $theme_root ), (array) $wp_theme_directories ) ) {
       
   186 			$this->stylesheet = basename( $this->theme_root ) . '/' . $this->stylesheet;
       
   187 			$this->theme_root = dirname( $theme_root );
       
   188 		}
       
   189 
       
   190 		$this->cache_hash = md5( $this->theme_root . '/' . $this->stylesheet );
       
   191 		$theme_file = $this->stylesheet . '/style.css';
       
   192 
       
   193 		$cache = $this->cache_get( 'theme' );
       
   194 
       
   195 		if ( is_array( $cache ) ) {
       
   196 			foreach ( array( 'errors', 'headers', 'template' ) as $key ) {
       
   197 				if ( isset( $cache[ $key ] ) )
       
   198 					$this->$key = $cache[ $key ];
       
   199 			}
       
   200 			if ( $this->errors )
       
   201 				return;
       
   202 			if ( isset( $cache['theme_root_template'] ) )
       
   203 				$theme_root_template = $cache['theme_root_template'];
       
   204 		} elseif ( ! file_exists( $this->theme_root . '/' . $theme_file ) ) {
       
   205 			$this->headers['Name'] = $this->stylesheet;
       
   206 			if ( ! file_exists( $this->theme_root . '/' . $this->stylesheet ) )
       
   207 				$this->errors = new WP_Error( 'theme_not_found', __( 'The theme directory does not exist.' ) );
       
   208 			else
       
   209 				$this->errors = new WP_Error( 'theme_no_stylesheet', __( 'Stylesheet is missing.' ) );
       
   210 			$this->template = $this->stylesheet;
       
   211 			$this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
       
   212 			if ( ! file_exists( $this->theme_root ) ) // Don't cache this one.
       
   213 				$this->errors->add( 'theme_root_missing', __( 'ERROR: The themes directory is either empty or doesn&#8217;t exist. Please check your installation.' ) );
       
   214 			return;
       
   215 		} elseif ( ! is_readable( $this->theme_root . '/' . $theme_file ) ) {
       
   216 			$this->headers['Name'] = $this->stylesheet;
       
   217 			$this->errors = new WP_Error( 'theme_stylesheet_not_readable', __( 'Stylesheet is not readable.' ) );
       
   218 			$this->template = $this->stylesheet;
       
   219 			$this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
       
   220 			return;
       
   221 		} else {
       
   222 			$this->headers = get_file_data( $this->theme_root . '/' . $theme_file, self::$file_headers, 'theme' );
       
   223 			// Default themes always trump their pretenders.
       
   224 			// Properly identify default themes that are inside a directory within wp-content/themes.
       
   225 			if ( $default_theme_slug = array_search( $this->headers['Name'], self::$default_themes ) ) {
       
   226 				if ( basename( $this->stylesheet ) != $default_theme_slug )
       
   227 					$this->headers['Name'] .= '/' . $this->stylesheet;
       
   228 			}
       
   229 		}
       
   230 
       
   231 		// (If template is set from cache [and there are no errors], we know it's good.)
       
   232 		if ( ! $this->template && ! ( $this->template = $this->headers['Template'] ) ) {
       
   233 			$this->template = $this->stylesheet;
       
   234 			if ( ! file_exists( $this->theme_root . '/' . $this->stylesheet . '/index.php' ) ) {
       
   235 				$this->errors = new WP_Error( 'theme_no_index', __( 'Template is missing.' ) );
       
   236 				$this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
       
   237 				return;
       
   238 			}
       
   239 		}
       
   240 
       
   241 		// If we got our data from cache, we can assume that 'template' is pointing to the right place.
       
   242 		if ( ! is_array( $cache ) && $this->template != $this->stylesheet && ! file_exists( $this->theme_root . '/' . $this->template . '/index.php' ) ) {
       
   243 			// If we're in a directory of themes inside /themes, look for the parent nearby.
       
   244 			// wp-content/themes/directory-of-themes/*
       
   245 			$parent_dir = dirname( $this->stylesheet );
       
   246 			if ( '.' != $parent_dir && file_exists( $this->theme_root . '/' . $parent_dir . '/' . $this->template . '/index.php' ) ) {
       
   247 				$this->template = $parent_dir . '/' . $this->template;
       
   248 			} elseif ( ( $directories = search_theme_directories() ) && isset( $directories[ $this->template ] ) ) {
       
   249 				// Look for the template in the search_theme_directories() results, in case it is in another theme root.
       
   250 				// We don't look into directories of themes, just the theme root.
       
   251 				$theme_root_template = $directories[ $this->template ]['theme_root'];
       
   252 			} else {
       
   253 				// Parent theme is missing.
       
   254 				$this->errors = new WP_Error( 'theme_no_parent', sprintf( __( 'The parent theme is missing. Please install the "%s" parent theme.' ), $this->template ) );
       
   255 				$this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
       
   256 				return;
       
   257 			}
       
   258 		}
       
   259 
       
   260 		// Set the parent, if we're a child theme.
       
   261 		if ( $this->template != $this->stylesheet ) {
       
   262 			// If we are a parent, then there is a problem. Only two generations allowed! Cancel things out.
       
   263 			if ( is_a( $_child, 'WP_Theme' ) && $_child->template == $this->stylesheet ) {
       
   264 				$_child->parent = null;
       
   265 				$_child->errors = new WP_Error( 'theme_parent_invalid', sprintf( __( 'The "%s" theme is not a valid parent theme.' ), $_child->template ) );
       
   266 				$_child->cache_add( 'theme', array( 'headers' => $_child->headers, 'errors' => $_child->errors, 'stylesheet' => $_child->stylesheet, 'template' => $_child->template ) );
       
   267 				// The two themes actually reference each other with the Template header.
       
   268 				if ( $_child->stylesheet == $this->template ) {
       
   269 					$this->errors = new WP_Error( 'theme_parent_invalid', sprintf( __( 'The "%s" theme is not a valid parent theme.' ), $this->template ) );
       
   270 					$this->cache_add( 'theme', array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template ) );
       
   271 				}
       
   272 				return;
       
   273 			}
       
   274 			// Set the parent. Pass the current instance so we can do the crazy checks above and assess errors.
       
   275 			$this->parent = new WP_Theme( $this->template, isset( $theme_root_template ) ? $theme_root_template : $this->theme_root, $this );
       
   276 		}
       
   277 
       
   278 		// We're good. If we didn't retrieve from cache, set it.
       
   279 		if ( ! is_array( $cache ) ) {
       
   280 			$cache = array( 'headers' => $this->headers, 'errors' => $this->errors, 'stylesheet' => $this->stylesheet, 'template' => $this->template );
       
   281 			// If the parent theme is in another root, we'll want to cache this. Avoids an entire branch of filesystem calls above.
       
   282 			if ( isset( $theme_root_template ) )
       
   283 				$cache['theme_root_template'] = $theme_root_template;
       
   284 			$this->cache_add( 'theme', $cache );
       
   285 		}
       
   286 	}
       
   287 
       
   288 	/**
       
   289 	 * When converting the object to a string, the theme name is returned.
       
   290 	 *
       
   291 	 * @return string Theme name, ready for display (translated)
       
   292 	 */
       
   293 	public function __toString() {
       
   294 		return (string) $this->display('Name');
       
   295 	}
       
   296 
       
   297 	/**
       
   298 	 * __isset() magic method for properties formerly returned by current_theme_info()
       
   299 	 */
       
   300 	public function __isset( $offset ) {
       
   301 		static $properties = array(
       
   302 			'name', 'title', 'version', 'parent_theme', 'template_dir', 'stylesheet_dir', 'template', 'stylesheet',
       
   303 			'screenshot', 'description', 'author', 'tags', 'theme_root', 'theme_root_uri',
       
   304 		);
       
   305 
       
   306 		return in_array( $offset, $properties );
       
   307 	}
       
   308 
       
   309 	/**
       
   310 	 * __get() magic method for properties formerly returned by current_theme_info()
       
   311 	 */
       
   312 	public function __get( $offset ) {
       
   313 		switch ( $offset ) {
       
   314 			case 'name' :
       
   315 			case 'title' :
       
   316 				return $this->get('Name');
       
   317 			case 'version' :
       
   318 				return $this->get('Version');
       
   319 			case 'parent_theme' :
       
   320 				return $this->parent() ? $this->parent()->get('Name') : '';
       
   321 			case 'template_dir' :
       
   322 				return $this->get_template_directory();
       
   323 			case 'stylesheet_dir' :
       
   324 				return $this->get_stylesheet_directory();
       
   325 			case 'template' :
       
   326 				return $this->get_template();
       
   327 			case 'stylesheet' :
       
   328 				return $this->get_stylesheet();
       
   329 			case 'screenshot' :
       
   330 				return $this->get_screenshot( 'relative' );
       
   331 			// 'author' and 'description' did not previously return translated data.
       
   332 			case 'description' :
       
   333 				return $this->display('Description');
       
   334 			case 'author' :
       
   335 				return $this->display('Author');
       
   336 			case 'tags' :
       
   337 				return $this->get( 'Tags' );
       
   338 			case 'theme_root' :
       
   339 				return $this->get_theme_root();
       
   340 			case 'theme_root_uri' :
       
   341 				return $this->get_theme_root_uri();
       
   342 			// For cases where the array was converted to an object.
       
   343 			default :
       
   344 				return $this->offsetGet( $offset );
       
   345 		}
       
   346 	}
       
   347 
       
   348 	/**
       
   349 	 * Method to implement ArrayAccess for keys formerly returned by get_themes()
       
   350 	 */
       
   351 	public function offsetSet( $offset, $value ) {}
       
   352 
       
   353 	/**
       
   354 	 * Method to implement ArrayAccess for keys formerly returned by get_themes()
       
   355 	 */
       
   356 	public function offsetUnset( $offset ) {}
       
   357 
       
   358 	/**
       
   359 	 * Method to implement ArrayAccess for keys formerly returned by get_themes()
       
   360 	 */
       
   361 	public function offsetExists( $offset ) {
       
   362 		static $keys = array(
       
   363 			'Name', 'Version', 'Status', 'Title', 'Author', 'Author Name', 'Author URI', 'Description',
       
   364 			'Template', 'Stylesheet', 'Template Files', 'Stylesheet Files', 'Template Dir', 'Stylesheet Dir',
       
   365 			 'Screenshot', 'Tags', 'Theme Root', 'Theme Root URI', 'Parent Theme',
       
   366 		);
       
   367 
       
   368 		return in_array( $offset, $keys );
       
   369 	}
       
   370 
       
   371 	/**
       
   372 	 * Method to implement ArrayAccess for keys formerly returned by get_themes().
       
   373 	 *
       
   374 	 * Author, Author Name, Author URI, and Description did not previously return
       
   375 	 * translated data. We are doing so now as it is safe to do. However, as
       
   376 	 * Name and Title could have been used as the key for get_themes(), both remain
       
   377 	 * untranslated for back compatibility. This means that ['Name'] is not ideal,
       
   378 	 * and care should be taken to use $theme->display('Name') to get a properly
       
   379 	 * translated header.
       
   380 	 */
       
   381 	public function offsetGet( $offset ) {
       
   382 		switch ( $offset ) {
       
   383 			case 'Name' :
       
   384 			case 'Title' :
       
   385 				// See note above about using translated data. get() is not ideal.
       
   386 				// It is only for backwards compatibility. Use display().
       
   387 				return $this->get('Name');
       
   388 			case 'Author' :
       
   389 				return $this->display( 'Author');
       
   390 			case 'Author Name' :
       
   391 				return $this->display( 'Author', false);
       
   392 			case 'Author URI' :
       
   393 				return $this->display('AuthorURI');
       
   394 			case 'Description' :
       
   395 				return $this->display( 'Description');
       
   396 			case 'Version' :
       
   397 			case 'Status' :
       
   398 				return $this->get( $offset );
       
   399 			case 'Template' :
       
   400 				return $this->get_template();
       
   401 			case 'Stylesheet' :
       
   402 				return $this->get_stylesheet();
       
   403 			case 'Template Files' :
       
   404 				return $this->get_files( 'php', 1, true );
       
   405 			case 'Stylesheet Files' :
       
   406 				return $this->get_files( 'css', 0, false );
       
   407 			case 'Template Dir' :
       
   408 				return $this->get_template_directory();
       
   409 			case 'Stylesheet Dir' :
       
   410 				return $this->get_stylesheet_directory();
       
   411 			case 'Screenshot' :
       
   412 				return $this->get_screenshot( 'relative' );
       
   413 			case 'Tags' :
       
   414 				return $this->get('Tags');
       
   415 			case 'Theme Root' :
       
   416 				return $this->get_theme_root();
       
   417 			case 'Theme Root URI' :
       
   418 				return $this->get_theme_root_uri();
       
   419 			case 'Parent Theme' :
       
   420 				return $this->parent() ? $this->parent()->get('Name') : '';
       
   421 			default :
       
   422 				return null;
       
   423 		}
       
   424 	}
       
   425 
       
   426 	/**
       
   427 	 * Returns errors property.
       
   428 	 *
       
   429 	 * @since 3.4.0
       
   430 	 * @access public
       
   431 	 *
       
   432 	 * @return WP_Error|bool WP_Error if there are errors, or false.
       
   433 	 */
       
   434 	public function errors() {
       
   435 		return is_wp_error( $this->errors ) ? $this->errors : false;
       
   436 	}
       
   437 
       
   438 	/**
       
   439 	 * Whether the theme exists.
       
   440 	 *
       
   441 	 * A theme with errors exists. A theme with the error of 'theme_not_found',
       
   442 	 * meaning that the theme's directory was not found, does not exist.
       
   443 	 *
       
   444 	 * @since 3.4.0
       
   445 	 * @access public
       
   446 	 *
       
   447 	 * @return bool Whether the theme exists.
       
   448 	 */
       
   449 	public function exists() {
       
   450 		return ! ( $this->errors() && in_array( 'theme_not_found', $this->errors()->get_error_codes() ) );
       
   451 	}
       
   452 
       
   453 	/**
       
   454 	 * Returns reference to the parent theme.
       
   455 	 *
       
   456 	 * @since 3.4.0
       
   457 	 * @access public
       
   458 	 *
       
   459 	 * @return WP_Theme|bool Parent theme, or false if the current theme is not a child theme.
       
   460 	 */
       
   461 	public function parent() {
       
   462 		return isset( $this->parent ) ? $this->parent : false;
       
   463 	}
       
   464 
       
   465 	/**
       
   466 	 * Adds theme data to cache.
       
   467 	 *
       
   468 	 * Cache entries keyed by the theme and the type of data.
       
   469 	 *
       
   470 	 * @access private
       
   471 	 * @since 3.4.0
       
   472 	 *
       
   473 	 * @param string $key Type of data to store (theme, screenshot, headers, page_templates)
       
   474 	 * @param string $data Data to store
       
   475 	 * @return bool Return value from wp_cache_add()
       
   476 	 */
       
   477 	private function cache_add( $key, $data ) {
       
   478 		return wp_cache_add( $key . '-' . $this->cache_hash, $data, 'themes', self::$cache_expiration );
       
   479 	}
       
   480 
       
   481 	/**
       
   482 	 * Gets theme data from cache.
       
   483 	 *
       
   484 	 * Cache entries are keyed by the theme and the type of data.
       
   485 	 *
       
   486 	 * @access private
       
   487 	 * @since 3.4.0
       
   488 	 *
       
   489 	 * @param string $key Type of data to retrieve (theme, screenshot, headers, page_templates)
       
   490 	 * @return mixed Retrieved data
       
   491 	 */
       
   492 	private function cache_get( $key ) {
       
   493 		return wp_cache_get( $key . '-' . $this->cache_hash, 'themes' );
       
   494 	}
       
   495 
       
   496 	/**
       
   497 	 * Clears the cache for the theme.
       
   498 	 *
       
   499 	 * @access public
       
   500 	 * @since 3.4.0
       
   501 	 */
       
   502 	public function cache_delete() {
       
   503 		foreach ( array( 'theme', 'screenshot', 'headers', 'page_templates' ) as $key )
       
   504 			wp_cache_delete( $key . '-' . $this->cache_hash, 'themes' );
       
   505 		$this->template = $this->textdomain_loaded = $this->theme_root_uri = $this->parent = $this->errors = $this->headers_sanitized = $this->name_translated = null;
       
   506 		$this->headers = array();
       
   507 		$this->__construct( $this->stylesheet, $this->theme_root );
       
   508 	}
       
   509 
       
   510 	/**
       
   511 	 * Get a raw, unformatted theme header.
       
   512 	 *
       
   513 	 * The header is sanitized, but is not translated, and is not marked up for display.
       
   514 	 * To get a theme header for display, use the display() method.
       
   515 	 *
       
   516 	 * Use the get_template() method, not the 'Template' header, for finding the template.
       
   517 	 * The 'Template' header is only good for what was written in the style.css, while
       
   518 	 * get_template() takes into account where WordPress actually located the theme and
       
   519 	 * whether it is actually valid.
       
   520 	 *
       
   521 	 * @access public
       
   522 	 * @since 3.4.0
       
   523 	 *
       
   524 	 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
       
   525 	 * @return string String on success, false on failure.
       
   526 	 */
       
   527 	public function get( $header ) {
       
   528 		if ( ! isset( $this->headers[ $header ] ) )
       
   529 			return false;
       
   530 
       
   531 		if ( ! isset( $this->headers_sanitized ) ) {
       
   532 			$this->headers_sanitized = $this->cache_get( 'headers' );
       
   533 			if ( ! is_array( $this->headers_sanitized ) )
       
   534 				$this->headers_sanitized = array();
       
   535 		}
       
   536 
       
   537 		if ( isset( $this->headers_sanitized[ $header ] ) )
       
   538 			return $this->headers_sanitized[ $header ];
       
   539 
       
   540 		// If themes are a persistent group, sanitize everything and cache it. One cache add is better than many cache sets.
       
   541 		if ( self::$persistently_cache ) {
       
   542 			foreach ( array_keys( $this->headers ) as $_header )
       
   543 				$this->headers_sanitized[ $_header ] = $this->sanitize_header( $_header, $this->headers[ $_header ] );
       
   544 			$this->cache_add( 'headers', $this->headers_sanitized );
       
   545 		} else {
       
   546 			$this->headers_sanitized[ $header ] = $this->sanitize_header( $header, $this->headers[ $header ] );
       
   547 		}
       
   548 
       
   549 		return $this->headers_sanitized[ $header ];
       
   550 	}
       
   551 
       
   552 	/**
       
   553 	 * Gets a theme header, formatted and translated for display.
       
   554 	 *
       
   555 	 * @access public
       
   556 	 * @since 3.4.0
       
   557 	 *
       
   558 	 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
       
   559 	 * @param bool $markup Optional. Whether to mark up the header. Defaults to true.
       
   560 	 * @param bool $translate Optional. Whether to translate the header. Defaults to true.
       
   561 	 * @return string Processed header, false on failure.
       
   562 	 */
       
   563 	public function display( $header, $markup = true, $translate = true ) {
       
   564 		$value = $this->get( $header );
       
   565 
       
   566 		if ( $translate && ( empty( $value ) || ! $this->load_textdomain() ) )
       
   567 			$translate = false;
       
   568 
       
   569 		if ( $translate )
       
   570 			$value = $this->translate_header( $header, $value );
       
   571 
       
   572 		if ( $markup )
       
   573 			$value = $this->markup_header( $header, $value, $translate );
       
   574 
       
   575 		return $value;
       
   576 	}
       
   577 
       
   578 	/**
       
   579 	 * Sanitize a theme header.
       
   580 	 *
       
   581 	 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
       
   582 	 * @param string $value Value to sanitize.
       
   583 	 */
       
   584 	private function sanitize_header( $header, $value ) {
       
   585 		switch ( $header ) {
       
   586 			case 'Status' :
       
   587 				if ( ! $value ) {
       
   588 					$value = 'publish';
       
   589 					break;
       
   590 				}
       
   591 				// Fall through otherwise.
       
   592 			case 'Name' :
       
   593 				static $header_tags = array(
       
   594 					'abbr'    => array( 'title' => true ),
       
   595 					'acronym' => array( 'title' => true ),
       
   596 					'code'    => true,
       
   597 					'em'      => true,
       
   598 					'strong'  => true,
       
   599 				);
       
   600 				$value = wp_kses( $value, $header_tags );
       
   601 				break;
       
   602 			case 'Author' :
       
   603 				// There shouldn't be anchor tags in Author, but some themes like to be challenging.
       
   604 			case 'Description' :
       
   605 				static $header_tags_with_a = array(
       
   606 					'a'       => array( 'href' => true, 'title' => true ),
       
   607 					'abbr'    => array( 'title' => true ),
       
   608 					'acronym' => array( 'title' => true ),
       
   609 					'code'    => true,
       
   610 					'em'      => true,
       
   611 					'strong'  => true,
       
   612 				);
       
   613 				$value = wp_kses( $value, $header_tags_with_a );
       
   614 				break;
       
   615 			case 'ThemeURI' :
       
   616 			case 'AuthorURI' :
       
   617 				$value = esc_url_raw( $value );
       
   618 				break;
       
   619 			case 'Tags' :
       
   620 				$value = array_filter( array_map( 'trim', explode( ',', strip_tags( $value ) ) ) );
       
   621 				break;
       
   622 		}
       
   623 
       
   624 		return $value;
       
   625 	}
       
   626 
       
   627 	/**
       
   628 	 * Mark up a theme header.
       
   629 	 *
       
   630 	 * @access private
       
   631 	 * @since 3.4.0
       
   632 	 *
       
   633 	 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
       
   634 	 * @param string $value Value to mark up.
       
   635 	 * @param string $translate Whether the header has been translated.
       
   636 	 * @return string Value, marked up.
       
   637 	 */
       
   638 	private function markup_header( $header, $value, $translate ) {
       
   639 		switch ( $header ) {
       
   640 			case 'Name' :
       
   641 				if ( empty( $value ) )
       
   642 					$value = $this->get_stylesheet();
       
   643 				break;
       
   644 			case 'Description' :
       
   645 				$value = wptexturize( $value );
       
   646 				break;
       
   647 			case 'Author' :
       
   648 				if ( $this->get('AuthorURI') ) {
       
   649 					static $attr = null;
       
   650 					if ( ! isset( $attr ) )
       
   651 						$attr = esc_attr__( 'Visit author homepage' );
       
   652 					$value = sprintf( '<a href="%1$s" title="%2$s">%3$s</a>', $this->display( 'AuthorURI', true, $translate ), $attr, $value );
       
   653 				} elseif ( ! $value ) {
       
   654 					$value = __( 'Anonymous' );
       
   655 				}
       
   656 				break;
       
   657 			case 'Tags' :
       
   658 				static $comma = null;
       
   659 				if ( ! isset( $comma ) ) {
       
   660 					/* translators: used between list items, there is a space after the comma */
       
   661 					$comma = __( ', ' );
       
   662 				}
       
   663 				$value = implode( $comma, $value );
       
   664 				break;
       
   665 			case 'ThemeURI' :
       
   666 			case 'AuthorURI' :
       
   667 				$value = esc_url( $value );
       
   668 				break;
       
   669 		}
       
   670 
       
   671 		return $value;
       
   672 	}
       
   673 
       
   674 	/**
       
   675 	 * Translate a theme header.
       
   676 	 *
       
   677 	 * @access private
       
   678 	 * @since 3.4.0
       
   679 	 *
       
   680 	 * @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
       
   681 	 * @param string $value Value to translate.
       
   682 	 * @return string Translated value.
       
   683 	 */
       
   684 	private function translate_header( $header, $value ) {
       
   685 		switch ( $header ) {
       
   686 			case 'Name' :
       
   687 				// Cached for sorting reasons.
       
   688 				if ( isset( $this->name_translated ) )
       
   689 					return $this->name_translated;
       
   690 				$this->name_translated = translate( $value, $this->get('TextDomain' ) );
       
   691 				return $this->name_translated;
       
   692 			case 'Tags' :
       
   693 				if ( empty( $value ) || ! function_exists( 'get_theme_feature_list' ) )
       
   694 					return $value;
       
   695 
       
   696 				static $tags_list;
       
   697 				if ( ! isset( $tags_list ) ) {
       
   698 					$tags_list = array();
       
   699 					$feature_list = get_theme_feature_list( false ); // No API
       
   700 					foreach ( $feature_list as $tags )
       
   701 						$tags_list += $tags;
       
   702 				}
       
   703 
       
   704 				foreach ( $value as &$tag ) {
       
   705 					if ( isset( $tags_list[ $tag ] ) )
       
   706 						$tag = $tags_list[ $tag ];
       
   707 				}
       
   708 
       
   709 				return $value;
       
   710 				break;
       
   711 			default :
       
   712 				$value = translate( $value, $this->get('TextDomain') );
       
   713 		}
       
   714 		return $value;
       
   715 	}
       
   716 
       
   717 	/**
       
   718 	 * The directory name of the theme's "stylesheet" files, inside the theme root.
       
   719 	 *
       
   720 	 * In the case of a child theme, this is directory name of the the child theme.
       
   721 	 * Otherwise, get_stylesheet() is the same as get_template().
       
   722 	 *
       
   723 	 * @since 3.4.0
       
   724 	 * @access public
       
   725 	 *
       
   726 	 * @return string Stylesheet
       
   727 	 */
       
   728 	public function get_stylesheet() {
       
   729 		return $this->stylesheet;
       
   730 	}
       
   731 
       
   732 	/**
       
   733 	 * The directory name of the theme's "template" files, inside the theme root.
       
   734 	 *
       
   735 	 * In the case of a child theme, this is the directory name of the parent theme.
       
   736 	 * Otherwise, the get_template() is the same as get_stylesheet().
       
   737 	 *
       
   738 	 * @since 3.4.0
       
   739 	 * @access public
       
   740 	 *
       
   741 	 * @return string Template
       
   742 	 */
       
   743 	public function get_template() {
       
   744 		return $this->template;
       
   745 	}
       
   746 
       
   747 	/**
       
   748 	 * Returns the absolute path to the directory of a theme's "stylesheet" files.
       
   749 	 *
       
   750 	 * In the case of a child theme, this is the absolute path to the directory
       
   751 	 * of the child theme's files.
       
   752 	 *
       
   753 	 * @since 3.4.0
       
   754 	 * @access public
       
   755 	 *
       
   756 	 * @return string Absolute path of the stylesheet directory.
       
   757 	 */
       
   758 	public function get_stylesheet_directory() {
       
   759 		if ( $this->errors() && in_array( 'theme_root_missing', $this->errors()->get_error_codes() ) )
       
   760 			return '';
       
   761 
       
   762 		return $this->theme_root . '/' . $this->stylesheet;
       
   763 	}
       
   764 
       
   765 	/**
       
   766 	 * Returns the absolute path to the directory of a theme's "template" files.
       
   767 	 *
       
   768 	 * In the case of a child theme, this is the absolute path to the directory
       
   769 	 * of the parent theme's files.
       
   770 	 *
       
   771 	 * @since 3.4.0
       
   772 	 * @access public
       
   773 	 *
       
   774 	 * @return string Absolute path of the template directory.
       
   775 	 */
       
   776 	public function get_template_directory() {
       
   777 		if ( $this->parent() )
       
   778 			$theme_root = $this->parent()->theme_root;
       
   779 		else
       
   780 			$theme_root = $this->theme_root;
       
   781 
       
   782 		return $theme_root . '/' . $this->template;
       
   783 	}
       
   784 
       
   785 	/**
       
   786 	 * Returns the URL to the directory of a theme's "stylesheet" files.
       
   787 	 *
       
   788 	 * In the case of a child theme, this is the URL to the directory of the
       
   789 	 * child theme's files.
       
   790 	 *
       
   791 	 * @since 3.4.0
       
   792 	 * @access public
       
   793 	 *
       
   794 	 * @return string URL to the stylesheet directory.
       
   795 	 */
       
   796 	public function get_stylesheet_directory_uri() {
       
   797 		return $this->get_theme_root_uri() . '/' . str_replace( '%2F', '/', rawurlencode( $this->stylesheet ) );
       
   798 	}
       
   799 
       
   800 	/**
       
   801 	 * Returns the URL to the directory of a theme's "template" files.
       
   802 	 *
       
   803 	 * In the case of a child theme, this is the URL to the directory of the
       
   804 	 * parent theme's files.
       
   805 	 *
       
   806 	 * @since 3.4.0
       
   807 	 * @access public
       
   808 	 *
       
   809 	 * @return string URL to the template directory.
       
   810 	 */
       
   811 	public function get_template_directory_uri() {
       
   812 		if ( $this->parent() )
       
   813 			$theme_root_uri = $this->parent()->get_theme_root_uri();
       
   814 		else
       
   815 			$theme_root_uri = $this->get_theme_root_uri();
       
   816 
       
   817 		return $theme_root_uri . '/' . str_replace( '%2F', '/', rawurlencode( $this->template ) );
       
   818 	}
       
   819 
       
   820 	/**
       
   821 	 * The absolute path to the directory of the theme root.
       
   822 	 *
       
   823 	 * This is typically the absolute path to wp-content/themes.
       
   824 	 *
       
   825 	 * @since 3.4.0
       
   826 	 * @access public
       
   827 	 *
       
   828 	 * @return string Theme root.
       
   829 	 */
       
   830 	public function get_theme_root() {
       
   831 		return $this->theme_root;
       
   832 	}
       
   833 
       
   834 	/**
       
   835 	 * Returns the URL to the directory of the theme root.
       
   836 	 *
       
   837 	 * This is typically the absolute URL to wp-content/themes. This forms the basis
       
   838 	 * for all other URLs returned by WP_Theme, so we pass it to the public function
       
   839 	 * get_theme_root_uri() and allow it to run the theme_root_uri filter.
       
   840 	 *
       
   841 	 * @uses get_theme_root_uri()
       
   842 	 *
       
   843 	 * @since 3.4.0
       
   844 	 * @access public
       
   845 	 *
       
   846 	 * @return string Theme root URI.
       
   847 	 */
       
   848 	public function get_theme_root_uri() {
       
   849 		if ( ! isset( $this->theme_root_uri ) )
       
   850 			$this->theme_root_uri = get_theme_root_uri( $this->stylesheet, $this->theme_root );
       
   851 		return $this->theme_root_uri;
       
   852 	}
       
   853 
       
   854 	/**
       
   855 	 * Returns the main screenshot file for the theme.
       
   856 	 *
       
   857 	 * The main screenshot is called screenshot.png. gif and jpg extensions are also allowed.
       
   858 	 *
       
   859 	 * Screenshots for a theme must be in the stylesheet directory. (In the case of child
       
   860 	 * themes, parent theme screenshots are not inherited.)
       
   861 	 *
       
   862 	 * @since 3.4.0
       
   863 	 * @access public
       
   864 	 *
       
   865 	 * @param string $uri Type of URL to return, either 'relative' or an absolute URI. Defaults to absolute URI.
       
   866 	 * @return mixed Screenshot file. False if the theme does not have a screenshot.
       
   867 	 */
       
   868 	public function get_screenshot( $uri = 'uri' ) {
       
   869 		$screenshot = $this->cache_get( 'screenshot' );
       
   870 		if ( $screenshot ) {
       
   871 			if ( 'relative' == $uri )
       
   872 				return $screenshot;
       
   873 			return $this->get_stylesheet_directory_uri() . '/' . $screenshot;
       
   874 		} elseif ( 0 === $screenshot ) {
       
   875 			return false;
       
   876 		}
       
   877 
       
   878 		foreach ( array( 'png', 'gif', 'jpg', 'jpeg' ) as $ext ) {
       
   879 			if ( file_exists( $this->get_stylesheet_directory() . "/screenshot.$ext" ) ) {
       
   880 				$this->cache_add( 'screenshot', 'screenshot.' . $ext );
       
   881 				if ( 'relative' == $uri )
       
   882 					return 'screenshot.' . $ext;
       
   883 				return $this->get_stylesheet_directory_uri() . '/' . 'screenshot.' . $ext;
       
   884 			}
       
   885 		}
       
   886 
       
   887 		$this->cache_add( 'screenshot', 0 );
       
   888 		return false;
       
   889 	}
       
   890 
       
   891 	/**
       
   892 	 * Return files in the theme's directory.
       
   893 	 *
       
   894 	 * @since 3.4.0
       
   895 	 * @access public
       
   896 	 *
       
   897 	 * @param mixed $type Optional. Array of extensions to return. Defaults to all files (null).
       
   898 	 * @param int $depth Optional. How deep to search for files. Defaults to a flat scan (0 depth). -1 depth is infinite.
       
   899 	 * @param bool $search_parent Optional. Whether to return parent files. Defaults to false.
       
   900 	 * @return array Array of files, keyed by the path to the file relative to the theme's directory, with the values
       
   901 	 * 	being absolute paths.
       
   902 	 */
       
   903 	public function get_files( $type = null, $depth = 0, $search_parent = false ) {
       
   904 		$files = (array) self::scandir( $this->get_stylesheet_directory(), $type, $depth );
       
   905 
       
   906 		if ( $search_parent && $this->parent() )
       
   907 			$files += (array) self::scandir( $this->get_template_directory(), $type, $depth );
       
   908 
       
   909 		return $files;
       
   910 	}
       
   911 
       
   912 	/**
       
   913 	 * Returns the theme's page templates.
       
   914 	 *
       
   915 	 * @since 3.4.0
       
   916 	 * @access public
       
   917 	 *
       
   918 	 * @return array Array of page templates, keyed by filename, with the value of the translated header name.
       
   919 	 */
       
   920 	public function get_page_templates() {
       
   921 		// If you screw up your current theme and we invalidate your parent, most things still work. Let it slide.
       
   922 		if ( $this->errors() && $this->errors()->get_error_codes() !== array( 'theme_parent_invalid' ) )
       
   923 			return array();
       
   924 
       
   925 		$page_templates = $this->cache_get( 'page_templates' );
       
   926 
       
   927 		if ( ! is_array( $page_templates ) ) {
       
   928 			$page_templates = array();
       
   929 
       
   930 			$files = (array) $this->get_files( 'php', 1 );
       
   931 
       
   932 			foreach ( $files as $file => $full_path ) {
       
   933 				if ( ! preg_match( '|Template Name:(.*)$|mi', file_get_contents( $full_path ), $header ) )
       
   934 					continue;
       
   935 				$page_templates[ $file ] = _cleanup_header_comment( $header[1] );
       
   936 			}
       
   937 
       
   938 			$this->cache_add( 'page_templates', $page_templates );
       
   939 		}
       
   940 
       
   941 		if ( $this->load_textdomain() ) {
       
   942 			foreach ( $page_templates as &$page_template ) {
       
   943 				$page_template = $this->translate_header( 'Template Name', $page_template );
       
   944 			}
       
   945 		}
       
   946 
       
   947 		if ( $this->parent() )
       
   948 			$page_templates += $this->parent()->get_page_templates();
       
   949 
       
   950 		return $page_templates;
       
   951 	}
       
   952 
       
   953 	/**
       
   954 	 * Scans a directory for files of a certain extension.
       
   955 	 *
       
   956 	 * @since 3.4.0
       
   957 	 * @access private
       
   958 	 *
       
   959 	 * @param string $path Absolute path to search.
       
   960 	 * @param mixed  Array of extensions to find, string of a single extension, or null for all extensions.
       
   961 	 * @param int $depth How deep to search for files. Optional, defaults to a flat scan (0 depth). -1 depth is infinite.
       
   962 	 * @param string $relative_path The basename of the absolute path. Used to control the returned path
       
   963 	 * 	for the found files, particularly when this function recurses to lower depths.
       
   964 	 */
       
   965 	private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) {
       
   966 		if ( ! is_dir( $path ) )
       
   967 			return false;
       
   968 
       
   969 		if ( $extensions ) {
       
   970 			$extensions = (array) $extensions;
       
   971 			$_extensions = implode( '|', $extensions );
       
   972 		}
       
   973 
       
   974 		$relative_path = trailingslashit( $relative_path );
       
   975 		if ( '/' == $relative_path )
       
   976 			$relative_path = '';
       
   977 
       
   978 		$results = scandir( $path );
       
   979 		$files = array();
       
   980 
       
   981 		foreach ( $results as $result ) {
       
   982 			if ( '.' == $result[0] )
       
   983 				continue;
       
   984 			if ( is_dir( $path . '/' . $result ) ) {
       
   985 				if ( ! $depth || 'CVS' == $result )
       
   986 					continue;
       
   987 				$found = self::scandir( $path . '/' . $result, $extensions, $depth - 1 , $relative_path . $result );
       
   988 				$files = array_merge_recursive( $files, $found );
       
   989 			} elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) {
       
   990 				$files[ $relative_path . $result ] = $path . '/' . $result;
       
   991 			}
       
   992 		}
       
   993 
       
   994 		return $files;
       
   995 	}
       
   996 
       
   997 	/**
       
   998 	 * Loads the theme's textdomain.
       
   999 	 *
       
  1000 	 * Translation files are not inherited from the parent theme. Todo: if this fails for the
       
  1001 	 * child theme, it should probably try to load the parent theme's translations.
       
  1002 	 *
       
  1003 	 * @since 3.4.0
       
  1004 	 * @access public
       
  1005 	 *
       
  1006 	 * @return True if the textdomain was successfully loaded or has already been loaded. False if
       
  1007 	 * 	no textdomain was specified in the file headers, or if the domain could not be loaded.
       
  1008 	 */
       
  1009 	public function load_textdomain() {
       
  1010 		if ( isset( $this->textdomain_loaded ) )
       
  1011 			return $this->textdomain_loaded;
       
  1012 
       
  1013 		$textdomain = $this->get('TextDomain');
       
  1014 		if ( ! $textdomain ) {
       
  1015 			$this->textdomain_loaded = false;
       
  1016 			return false;
       
  1017 		}
       
  1018 
       
  1019 		if ( is_textdomain_loaded( $textdomain ) ) {
       
  1020 			$this->textdomain_loaded = true;
       
  1021 			return true;
       
  1022 		}
       
  1023 
       
  1024 		$path = $this->get_stylesheet_directory();
       
  1025 		if ( $domainpath = $this->get('DomainPath') )
       
  1026 			$path .= $domainpath;
       
  1027 		else
       
  1028 			$path .= '/languages';
       
  1029 
       
  1030 		$this->textdomain_loaded = load_theme_textdomain( $textdomain, $path );
       
  1031 		return $this->textdomain_loaded;
       
  1032 	}
       
  1033 
       
  1034 	/**
       
  1035 	 * Whether the theme is allowed (multisite only).
       
  1036 	 *
       
  1037 	 * @since 3.4.0
       
  1038 	 * @access public
       
  1039 	 *
       
  1040 	 * @param string $check Optional. Whether to check only the 'network'-wide settings, the 'site'
       
  1041 	 * 	settings, or 'both'. Defaults to 'both'.
       
  1042 	 * @param int $blog_id Optional. Ignored if only network-wide settings are checked. Defaults to current blog.
       
  1043 	 * @return bool Whether the theme is allowed for the network. Returns true in single-site.
       
  1044 	 */
       
  1045 	public function is_allowed( $check = 'both', $blog_id = null ) {
       
  1046 		if ( ! is_multisite() )
       
  1047 			return true;
       
  1048 
       
  1049 		if ( 'both' == $check || 'network' == $check ) {
       
  1050 			$allowed = self::get_allowed_on_network();
       
  1051 			if ( ! empty( $allowed[ $this->get_stylesheet() ] ) )
       
  1052 				return true;
       
  1053 		}
       
  1054 
       
  1055 		if ( 'both' == $check || 'site' == $check ) {
       
  1056 			$allowed = self::get_allowed_on_site( $blog_id );
       
  1057 			if ( ! empty( $allowed[ $this->get_stylesheet() ] ) )
       
  1058 				return true;
       
  1059 		}
       
  1060 
       
  1061 		return false;
       
  1062 	}
       
  1063 
       
  1064 	/**
       
  1065 	 * Returns array of stylesheet names of themes allowed on the site or network.
       
  1066 	 *
       
  1067 	 * @since 3.4.0
       
  1068 	 * @access public
       
  1069 	 *
       
  1070 	 * @param int $blog_id Optional. Defaults to current blog.
       
  1071 	 * @return array Array of stylesheet names.
       
  1072 	 */
       
  1073 	public static function get_allowed( $blog_id = null ) {
       
  1074 		$network = (array) apply_filters( 'allowed_themes', self::get_allowed_on_network() );
       
  1075 		return $network + self::get_allowed_on_site( $blog_id );
       
  1076 	}
       
  1077 
       
  1078 	/**
       
  1079 	 * Returns array of stylesheet names of themes allowed on the network.
       
  1080 	 *
       
  1081 	 * @since 3.4.0
       
  1082 	 * @access public
       
  1083 	 *
       
  1084 	 * @return array Array of stylesheet names.
       
  1085 	 */
       
  1086 	public static function get_allowed_on_network() {
       
  1087 		static $allowed_themes;
       
  1088 		if ( ! isset( $allowed_themes ) )
       
  1089 			$allowed_themes = (array) get_site_option( 'allowedthemes' );
       
  1090 		return $allowed_themes;
       
  1091 	}
       
  1092 
       
  1093 	/**
       
  1094 	 * Returns array of stylesheet names of themes allowed on the site.
       
  1095 	 *
       
  1096 	 * @since 3.4.0
       
  1097 	 * @access public
       
  1098 	 *
       
  1099 	 * @param int $blog_id Optional. Defaults to current blog.
       
  1100 	 * @return array Array of stylesheet names.
       
  1101 	 */
       
  1102 	public static function get_allowed_on_site( $blog_id = null ) {
       
  1103 		static $allowed_themes = array();
       
  1104 
       
  1105 		if ( ! $blog_id )
       
  1106 			$blog_id = get_current_blog_id();
       
  1107 
       
  1108 		if ( isset( $allowed_themes[ $blog_id ] ) )
       
  1109 			return $allowed_themes[ $blog_id ];
       
  1110 
       
  1111 		$current = $blog_id == get_current_blog_id();
       
  1112 
       
  1113 		if ( $current )
       
  1114 			$allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
       
  1115 		else
       
  1116 			$allowed_themes[ $blog_id ] = get_blog_option( $blog_id, 'allowedthemes' );
       
  1117 
       
  1118 		// This is all super old MU back compat joy.
       
  1119 		// 'allowedthemes' keys things by stylesheet. 'allowed_themes' keyed things by name.
       
  1120 		if ( false === $allowed_themes[ $blog_id ] ) {
       
  1121 			if ( $current )
       
  1122 				$allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
       
  1123 			else
       
  1124 				$allowed_themes[ $blog_id ] = get_blog_option( $blog_id, 'allowed_themes' );
       
  1125 
       
  1126 			if ( ! is_array( $allowed_themes[ $blog_id ] ) || empty( $allowed_themes[ $blog_id ] ) ) {
       
  1127 				$allowed_themes[ $blog_id ] = array();
       
  1128 			} else {
       
  1129 				$converted = array();
       
  1130 				$themes = wp_get_themes();
       
  1131 				foreach ( $themes as $stylesheet => $theme_data ) {
       
  1132 					if ( isset( $allowed_themes[ $blog_id ][ $theme_data->get('Name') ] ) )
       
  1133 						$converted[ $stylesheet ] = true;
       
  1134 				}
       
  1135 				$allowed_themes[ $blog_id ] = $converted;
       
  1136 			}
       
  1137 			// Set the option so we never have to go through this pain again.
       
  1138 			if ( is_admin() && $allowed_themes[ $blog_id ] ) {
       
  1139 				if ( $current ) {
       
  1140 					update_option( 'allowedthemes', $allowed_themes[ $blog_id ] );
       
  1141 					delete_option( 'allowed_themes' );
       
  1142 				} else {
       
  1143 					update_blog_option( $blog_id, 'allowedthemes', $allowed_themes[ $blog_id ] );
       
  1144 					delete_blog_option( $blog_id, 'allowed_themes' );
       
  1145 				}
       
  1146 			}
       
  1147 		}
       
  1148 
       
  1149 		return (array) $allowed_themes[ $blog_id ];
       
  1150 	}
       
  1151 
       
  1152 	/**
       
  1153 	 * Sort themes by name.
       
  1154 	 *
       
  1155 	 * @since 3.4.0
       
  1156 	 * @access public
       
  1157 	 */
       
  1158 	public static function sort_by_name( &$themes ) {
       
  1159 		if ( 0 === strpos( get_locale(), 'en_' ) ) {
       
  1160 			uasort( $themes, array( 'WP_Theme', '_name_sort' ) );
       
  1161 		} else {
       
  1162 			uasort( $themes, array( 'WP_Theme', '_name_sort_i18n' ) );
       
  1163 		}
       
  1164 	}
       
  1165 
       
  1166 	/**
       
  1167 	 * Callback function for usort() to naturally sort themes by name.
       
  1168 	 *
       
  1169 	 * Accesses the Name header directly from the class for maximum speed.
       
  1170 	 * Would choke on HTML but we don't care enough to slow it down with strip_tags().
       
  1171 	 *
       
  1172 	 * @since 3.4.0
       
  1173 	 * @access private
       
  1174 	 */
       
  1175 	private static function _name_sort( $a, $b ) {
       
  1176 		return strnatcasecmp( $a->headers['Name'], $b->headers['Name'] );
       
  1177 	}
       
  1178 
       
  1179 	/**
       
  1180 	 * Name sort (with translation).
       
  1181 	 *
       
  1182 	 * @since 3.4.0
       
  1183 	 * @access private
       
  1184 	 */
       
  1185 	private static function _name_sort_i18n( $a, $b ) {
       
  1186 		// Don't mark up; Do translate.
       
  1187 		return strnatcasecmp( $a->display( 'Name', false, true ), $b->display( 'Name', false, true ) );
       
  1188 	}
       
  1189 }