diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php index cdd7e88..e2bf62e 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandler.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php @@ -284,19 +284,17 @@ public function rebuildThemeData() { $type = 'theme'; $this->moduleHandler->alter('system_info', $theme->info, $theme, $type); - // Defaults to 'twig' (see $defaults above). - $engine = $theme->info['engine']; - if (!isset($engines[$engine])) { - unset($themes[$key]); - continue; - } - $theme->owner = $engines[$engine]->uri; - $theme->prefix = $engines[$engine]->name; - if (!empty($theme->info['base theme'])) { $sub_themes[] = $key; } + // Defaults to 'twig' (see $defaults above). + $engine = $theme->info['engine']; + if (isset($engines[$engine])) { + $theme->owner = $engines[$engine]->uri; + $theme->prefix = $engines[$engine]->name; + } + // Prefix stylesheets, scripts, and screenshot with theme path. $path = $theme->getPath(); $theme->info['stylesheets'] = $this->themeInfoPrefixPath($theme->info['stylesheets'], $path); @@ -313,11 +311,10 @@ public function rebuildThemeData() { // The $base_themes property is optional; only set for sub themes. // @see ThemeHandlerInterface::listInfo() $sub_theme->base_themes = $this->getBaseThemes($themes, $key); - if (empty($sub_theme->base_themes)) { - // This sub-theme declared a parent theme, but the parent theme (or one - // of its parents) is not available, so this sub-theme cannot be used. - // @todo Add proper error handling for this case. - unset($themes[$key]); + // empty() cannot be used here, since ThemeHandler::doGetBaseThemes() adds + // the key of a base theme with a value of NULL in case it is not found, + // in order to prevent needless iterations. + if (!current($sub_theme->base_themes)) { continue; } // Determine the root base theme. @@ -327,9 +324,11 @@ public function rebuildThemeData() { $themes[$base_theme]->sub_themes[$key] = $sub_theme->info['name']; } // Add the theme engine info from the root base theme. - $sub_theme->info['engine'] = $themes[$root_key]->info['engine']; - $sub_theme->owner = $themes[$root_key]->owner; - $sub_theme->prefix = $themes[$root_key]->prefix; + if (isset($themes[$root_key]->owner)) { + $sub_theme->info['engine'] = $themes[$root_key]->info['engine']; + $sub_theme->owner = $themes[$root_key]->owner; + $sub_theme->prefix = $themes[$root_key]->prefix; + } } return $themes; @@ -373,23 +372,52 @@ protected function themeInfoPrefixPath(array $info, $path) { /** * {@inheritdoc} */ - public function getBaseThemes(array $themes, $key) { - $base_themes = array(); - while ($key && isset($themes[$key]->info['base theme'])) { - $base_key = $themes[$key]->info['base theme']; - if (isset($themes[$base_key])) { - $base_themes[$key] = $themes[$base_key]->info['name']; - $key = $base_key; + public function getBaseThemes(array $themes, $theme) { + return $this->doGetBaseThemes($themes, $theme); + } + + /** + * Finds the base themes for the specific theme. + * + * @param array $themes + * An array of available themes. + * @param string $theme + * The name of the theme whose base we are looking for. + * @param array $used_themes + * (optional) A recursion parameter preventing endless loops. Defaults to + * an empty array. + * + * @return array + * An array of base themes. + */ + protected function doGetBaseThemes(array $themes, $theme, $used_themes = array()) { + if (!isset($themes[$theme]->info['base theme'])) { + return array(); + } + + $base_key = $themes[$theme]->info['base theme']; + // Does the base theme exist? + if (!isset($themes[$base_key])) { + return array($base_key => NULL); + } + + $current_base_theme = array($base_key => $themes[$base_key]->info['name']); + + // Is the base theme itself a child of another theme? + if (isset($themes[$base_key]->info['base theme'])) { + // Do we already know the base themes of this theme? + if (isset($themes[$base_key]->base_themes)) { + return $themes[$base_key]->base_themes + $current_base_theme; } - else { - // If the parent theme in the chain does not exist, return an empty - // array; the sub-theme cannot be used. - // @todo Throw an exception instead and handle this error condition. - return array(); + // Prevent loops. + if (!empty($used_themes[$base_key])) { + return array($base_key => NULL); } + $used_themes[$base_key] = TRUE; + return $this->doGetBaseThemes($themes, $base_key, $used_themes) + $current_base_theme; } - // The "root" theme is expected to be returned first. - return array_reverse($base_themes); + // If we get here, then this is our parent theme. + return $current_base_theme; } /** diff --git a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php index 136a533..2ba52cd 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandlerInterface.php @@ -100,8 +100,8 @@ public function rebuildThemeData(); * The name of the theme whose base we are looking for. * * @return array - * Returns an array of all of the theme's ancestors, or an empty array in - * case a parent theme in the chain does not exist. + * Returns an array of all of the theme's ancestors; the first element's + * value will be NULL if an error occurred. */ public function getBaseThemes(array $themes, $theme); diff --git a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php index 4eab39f..b737898 100644 --- a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php @@ -244,6 +244,12 @@ public function testRebuildThemeData() { ->will($this->returnValue(array( 'seven' => new Extension('theme', DRUPAL_ROOT . '/core/themes/seven/seven.info.yml', 'seven.info.yml'), ))); + $this->extensionDiscovery->expects($this->at(1)) + ->method('scan') + ->with('theme_engine') + ->will($this->returnValue(array( + 'twig' => new Extension('theme_engine', DRUPAL_ROOT . '/core/themes/engines/twig.info.yml', 'twig.info.yml'), + ))); $this->infoParser->expects($this->once()) ->method('parse') ->with(DRUPAL_ROOT . '/core/themes/seven/seven.info.yml')