home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / class-wp-theme.php < prev    next >
Encoding:
PHP Script  |  2017-11-26  |  46.1 KB  |  1,475 lines

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