WP_Theme

WP_Theme Class.

Defined (1)

The class is defined in the following location(s).

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