commit 829c25f93e856f56889e91584da59084f13ac3f1 Author: Pierre Rineau Date: Sun Feb 26 14:10:33 2017 +0100 Issue #1545964 by Pol, pounard, Rene Bakx, Kars-T, SebCorbin: Do not copy over the owner and engine of a theme if the child theme uses a different engine than the base theme diff --git a/includes/module.inc b/includes/module.inc index 2e251080b7..10d1b1c49f 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -193,17 +193,12 @@ function system_list($type) { foreach (array_keys($lists['theme'][$key]->base_themes) as $base_theme) { $lists['theme'][$base_theme]->sub_themes[$key] = $lists['theme'][$key]->info['name']; } - // Add the base theme's theme engine info. - $lists['theme'][$key]->info['engine'] = isset($lists['theme'][$base_key]->info['engine']) ? $lists['theme'][$base_key]->info['engine'] : 'theme'; - } - else { - // A plain theme is its own engine. - $base_key = $key; - if (!isset($lists['theme'][$key]->info['engine'])) { - $lists['theme'][$key]->info['engine'] = 'theme'; - } } // Set the theme engine prefix. + // During 6.x to 7.x upgrade, 6.x themes may not have an 'engine' key. + if (empty($lists['theme'][$key]->info['engine'])) { + $lists['theme'][$key]->info['engine'] = 'phptemplate'; + } $lists['theme'][$key]->prefix = ($lists['theme'][$key]->info['engine'] == 'theme') ? $base_key : $lists['theme'][$key]->info['engine']; } cache_set('system_list', $lists, 'cache_bootstrap'); diff --git a/includes/theme.inc b/includes/theme.inc index 9b606e9fb1..43a75faeb4 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -139,7 +139,7 @@ function drupal_theme_initialize() { * The callback to invoke to set the theme registry. */ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callback = '_theme_load_registry') { - global $theme_info, $base_theme_info, $theme_engine, $theme_path; + global $theme_info, $base_theme_info, $theme_path; $theme_info = $theme; $base_theme_info = $base_theme; @@ -201,37 +201,24 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb drupal_add_js($script, array('group' => JS_THEME, 'every_page' => TRUE)); } - $theme_engine = NULL; - - // Initialize the theme. - if (isset($theme->engine)) { - // Include the engine. - include_once DRUPAL_ROOT . '/' . $theme->owner; - - $theme_engine = $theme->engine; - if (function_exists($theme_engine . '_init')) { - foreach ($base_theme as $base) { - call_user_func($theme_engine . '_init', $base); - } - call_user_func($theme_engine . '_init', $theme); - } - } - else { - // include non-engine theme files - foreach ($base_theme as $base) { - // Include the theme file or the engine. - if (!empty($base->owner)) { - include_once DRUPAL_ROOT . '/' . $base->owner; - } + // Initialize all themes and engines. + // Make sure of loading the current theme first so preprocess functions are + // loaded properly. + foreach (array($theme) + list_themes() as $current) { + // Do not attempt to load en empty owner, this could happen with malformed + // theme info, or missing parent theme engine; during runtime initialization + // we should not let Drupal emit warnings on a live site. + if ($current->owner) { + include_once DRUPAL_ROOT . '/' . $current->owner; } - // and our theme gets one too. - if (!empty($theme->owner)) { - include_once DRUPAL_ROOT . '/' . $theme->owner; + + if (function_exists($current->engine . '_init')) { + call_user_func($current->engine . '_init', $current); } } if (isset($registry_callback)) { - _theme_registry_callback($registry_callback, array($theme, $base_theme, $theme_engine)); + _theme_registry_callback($registry_callback, array($theme, $base_theme)); } } @@ -304,8 +291,6 @@ function _theme_registry_callback($callback = NULL, array $arguments = array()) * @param $base_theme * An array of loaded $theme objects representing the ancestor themes in * oldest first order. - * @param $theme_engine - * The name of the theme engine. * @param $complete * Whether to load the complete theme registry or an instance of the * ThemeRegistry class. @@ -313,7 +298,7 @@ function _theme_registry_callback($callback = NULL, array $arguments = array()) * @return * The theme registry array, or an instance of the ThemeRegistry class. */ -function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL, $complete = TRUE) { +function _theme_load_registry($theme, $base_theme = NULL, $complete = TRUE) { if ($complete) { // Check the theme registry cache; if it exists, use it. $cached = cache_get("theme_registry:$theme->name"); @@ -322,7 +307,7 @@ function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL, } else { // If not, build one and cache it. - $registry = _theme_build_registry($theme, $base_theme, $theme_engine); + $registry = _theme_build_registry($theme, $base_theme); // Only persist this registry if all modules are loaded. This assures a // complete set of theme hooks. if (module_load_all(NULL)) { @@ -672,10 +657,8 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) { * @param $base_theme * An array of loaded $theme objects representing the ancestor themes in * oldest first order. - * @param $theme_engine - * The name of the theme engine. */ -function _theme_build_registry($theme, $base_theme, $theme_engine) { +function _theme_build_registry($theme, $base_theme) { $cache = array(); // First, process the theme hooks advertised by modules. This will // serve as the basic registry. Since the list of enabled modules is the same @@ -698,15 +681,15 @@ function _theme_build_registry($theme, $base_theme, $theme_engine) { foreach ($base_theme as $base) { // If the base theme uses a theme engine, process its hooks. $base_path = dirname($base->filename); - if ($theme_engine) { - _theme_process_registry($cache, $theme_engine, 'base_theme_engine', $base->name, $base_path); + if (isset($base->engine)) { + _theme_process_registry($cache, $base->engine, 'base_theme_engine', $base->name, $base_path); } _theme_process_registry($cache, $base->name, 'base_theme', $base->name, $base_path); } // And then the same thing, but for the theme. - if ($theme_engine) { - _theme_process_registry($cache, $theme_engine, 'theme_engine', $theme->name, dirname($theme->filename)); + if (isset($theme->engine)) { + _theme_process_registry($cache, $theme->engine, 'theme_engine', $theme->name, dirname($theme->filename)); } // Finally, hooks provided by the theme itself. @@ -1167,16 +1150,14 @@ function theme($hook, $variables = array()) { $extension = '.tpl.php'; // The theme engine may use a different extension and a different renderer. - global $theme_engine; - if (isset($theme_engine)) { - if ($info['type'] != 'module') { - if (function_exists($theme_engine . '_render_template')) { - $render_function = $theme_engine . '_render_template'; - } - $extension_function = $theme_engine . '_extension'; - if (function_exists($extension_function)) { - $extension = $extension_function(); - } + if (isset($info['engine'])) { + $theme_engine = $info['engine']; + if (function_exists($theme_engine . '_render_template')) { + $render_function = $theme_engine . '_render_template'; + } + $extension_function = $theme_engine . '_extension'; + if (function_exists($extension_function)) { + $extension = $extension_function(); } } @@ -1309,10 +1290,21 @@ function drupal_find_theme_templates($cache, $extension, $path) { // used for filtering. This allows base themes to have sub-themes in its // folder hierarchy without affecting the base themes template discovery. $theme_paths = array(); - foreach (list_themes() as $theme_info) { + $current_theme = NULL; + $current_engine = NULL; + foreach (list_themes() as $key => $theme_info) { if (!empty($theme_info->base_theme)) { $theme_paths[$theme_info->base_theme][$theme_info->name] = dirname($theme_info->filename); } + $theme_path = dirname($theme_info->filename); + if ($path === $theme_path) { + $current_theme = $current_theme; + $current_engine = isset($theme_info->engine) ? $theme_info->engine : NULL; + } + else if (!$current_theme && 0 === strpos($theme_path, $current_theme)) { + $current_theme = $current_theme; + $current_engine = isset($theme_info->engine) ? $theme_info->engine : NULL; + } } foreach ($theme_paths as $basetheme => $subthemes) { foreach ($subthemes as $subtheme => $subtheme_path) { @@ -1349,6 +1341,7 @@ function drupal_find_theme_templates($cache, $extension, $path) { if (isset($cache[$hook])) { $implementations[$hook] = array( 'template' => $template, + 'engine' => $current_engine, 'path' => dirname($file->uri), ); } @@ -1375,6 +1368,7 @@ function drupal_find_theme_templates($cache, $extension, $path) { $arg_name = isset($info['variables']) ? 'variables' : 'render element'; $implementations[strtr($file, '-', '_')] = array( 'template' => $file, + 'engine' => $current_engine, 'path' => dirname($files[$match]->uri), $arg_name => $info[$arg_name], 'base hook' => $hook, diff --git a/includes/theme.maintenance.inc b/includes/theme.maintenance.inc index 6baf219b03..df8d4464d6 100644 --- a/includes/theme.maintenance.inc +++ b/includes/theme.maintenance.inc @@ -87,8 +87,8 @@ function _drupal_maintenance_theme() { /** * Builds the registry when the site needs to bypass any database calls. */ -function _theme_load_offline_registry($theme, $base_theme = NULL, $theme_engine = NULL) { - return _theme_build_registry($theme, $base_theme, $theme_engine); +function _theme_load_offline_registry($theme, $base_theme = NULL) { + return _theme_build_registry($theme, $base_theme); } /** diff --git a/modules/simpletest/tests/theme.test b/modules/simpletest/tests/theme.test index 5f095bd558..d2899a5d42 100644 --- a/modules/simpletest/tests/theme.test +++ b/modules/simpletest/tests/theme.test @@ -677,3 +677,122 @@ class ModuleProvidedThemeEngineTestCase extends DrupalWebTestCase { } } + +/** + * Tests the multi theme engine support. + */ +class ThemeEngineNyanCatTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Theme engine test', + 'description' => 'Tests the theme engine.', + 'group' => 'Theme', + ); + } + + function setUp() { + parent::setUp('theme_test'); + theme_enable(array('test_theme')); + } + + /** + * Ensures a theme's template is overridable based on the 'template' filename. + */ + function testTemplateOverride() { + variable_set('theme_default', 'test_theme_nyan_cat'); + variable_set('admin_theme', 'test_theme_nyan_cat'); + + $this->drupalGet('theme-test/template-test-engine'); + $this->assertText('Success: Template overridden with Nyan Cat theme. All of them', 'Template overridden by Nyan Cat file.'); + } + +} + +/** + * Tests the multi theme engine support with a subtheme. + */ +class ThemeEngineNyanCatSubThemeTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Sub theme engine test', + 'description' => 'Tests the theme engine on a subtheme.', + 'group' => 'Theme', + ); + } + + function setUp() { + parent::setUp('theme_test'); + theme_enable(array('test_theme', 'test_theme_nyan_cat', 'test_subtheme_nyan_cat')); + } + + /** + * Ensures a theme's template is overridable based on the 'template' filename. + */ + function testTemplateOverride() { + variable_set('theme_default', 'test_subtheme_nyan_cat'); + variable_set('admin_theme', 'test_subtheme_nyan_cat'); + + $this->drupalGet('theme-test/template-test-engine'); + $this->assertText('Success: Template overridden with Nyan Cat theme. All of them', 'Template overridden by Nyan Cat file.'); + } + + /** + * Ensures that a module providing both templates with different engines + * will all be correctly displayed, no matter is the theme, using their + * respective engines. + * + * Tests that phptemplate -> nyan_cat template override works. + */ + function testEngineCohabitationFromDefaultToNyan() { + variable_set('theme_default', 'test_theme'); + variable_set('admin_theme', 'test_theme'); + + $this->drupalGet('theme-test/engine-cohabitation'); + $this->assertText('Inheritance 1.'); + $this->assertText('Inheritance 2.'); + $this->assertText('Inheritance 3.'); + // This one is overriden. + $this->assertNoText('Inheritance 4.'); + $this->assertText('Inheritance 6.'); + // This one is in another theme. + $this->assertNoText('Inheritance 5.'); + } + + /** + * Ensures that a module providing both templates with different engines + * will all be correctly displayed, no matter is the theme, using their + * respective engines. + * + * Tests that nyan_cat -> phptemplate template override works. + */ + function testEngineCohabitationFromNyanToDefault() { + variable_set('theme_default', 'test_theme_nyan_cat'); + variable_set('admin_theme', 'test_theme_nyan_cat'); + + $this->drupalGet('theme-test/engine-cohabitation'); + $this->assertText('Inheritance 1.'); + $this->assertText('Inheritance 2.'); + $this->assertText('Inheritance 4.'); + // This one is overriden. + $this->assertNoText('Inheritance 3.'); + $this->assertText('Inheritance 5.'); + // This one is in another theme. + $this->assertNoText('Inheritance 6.'); + } + + /** + * Ensure the subtheme has inherited of the theme engine of it's base theme. + */ + function testEngineIsCorrectlyInherited() { + $infos = list_themes(); + + // Get the base theme name from the theme 'test_subtheme_nyan_cat' + $base_theme_name = $infos['test_subtheme_nyan_cat']->info['base theme']; + // Get the base theme + $base_theme = $infos[$base_theme_name]; + + // Compare their theme engine. + $this->assertEqual($infos['test_subtheme_nyan_cat']->info['engine'], $base_theme->info['engine']); + } + +} diff --git a/modules/simpletest/tests/theme_test.module b/modules/simpletest/tests/theme_test.module index 1dbc3b9564..f7dce8d8de 100644 --- a/modules/simpletest/tests/theme_test.module +++ b/modules/simpletest/tests/theme_test.module @@ -17,6 +17,39 @@ function theme_test_theme($existing, $type, $theme, $path) { $items['theme_test_foo'] = array( 'variables' => array('foo' => NULL), ); + $items['theme_test_template_test_engine'] = array( + 'template' => 'theme_test.template_test_engine', + 'engine' => 'nyan_cat' + ); + // The following template is phptemplate per default, and will be displayed + // correctly even when using the 'test_theme_nyan_cat' theme, which is based + // upon the nyan_cat engine. + $items['theme_test_engine_inheritance_phpt'] = array( + 'variables' => array(), + 'template' => 'theme_test_engine_inheritance_phpt', + ); + // The following template is nyan_cat per default, and will be displayed + // correctly even when using the 'test_theme' theme, which is based upon + // the phptemplate engine. + $items['theme_test_engine_inheritance_nyan'] = array( + 'variables' => array(), + 'template' => 'theme_test_engine_inheritance_nyan', + 'engine' => 'nyan_cat' + ); + // In opposition to the two templates above, this one is phptemplate but will + // be correctly overriden by the 'test_theme_nyan_cat' which has a different + // engine. + $items['theme_test_engine_inheritance_phpt_overriden'] = array( + 'variables' => array(), + 'template' => 'theme_test_engine_inheritance_phpt_overriden', + ); + // Exact same as above, this one is nyan cat but will be correctly overriden + // by the 'test_theme' which has a different engine. + $items['theme_test_engine_inheritance_nyan_overriden'] = array( + 'variables' => array(), + 'template' => 'theme_test_engine_inheritance_nyan_overriden', + 'engine' => 'nyan_cat' + ); return $items; } @@ -28,6 +61,7 @@ function theme_test_system_theme_info() { $themes['test_basetheme'] = drupal_get_path('module', 'theme_test') . '/themes/test_basetheme/test_basetheme.info'; $themes['test_subtheme'] = drupal_get_path('module', 'theme_test') . '/themes/test_subtheme/test_subtheme.info'; $themes['test_theme_nyan_cat'] = drupal_get_path('module', 'theme_test') . '/themes/test_theme_nyan_cat/test_theme_nyan_cat.info'; + $themes['test_subtheme_nyan_cat'] = drupal_get_path('module', 'theme_test') . '/themes/test_subtheme_nyan_cat/test_subtheme_nyan_cat.info'; return $themes; } @@ -73,10 +107,42 @@ function theme_test_menu() { 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); + $items['theme-test/template-test-engine'] = array( + 'page callback' => '_theme_test_template_test_engine', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + $items['theme-test/engine-cohabitation'] = array( + 'page callback' => '_theme_test_engine_inheritance', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + return $items; } /** + * Page callback, calls a theme hook suggestion. + */ +function _theme_test_template_test_engine() { + return array( + '#markup' => theme('theme_test_template_test_engine', array()), + ); +} + +/** + * Page callback, display various templates for various theme engines. + */ +function _theme_test_engine_inheritance() { + return array( + array('#theme' => 'theme_test_engine_inheritance_phpt'), + array('#theme' => 'theme_test_engine_inheritance_nyan'), + array('#theme' => 'theme_test_engine_inheritance_phpt_overriden'), + array('#theme' => 'theme_test_engine_inheritance_nyan_overriden'), + ); +} + +/** * Implements hook_init(). */ function theme_test_init() { diff --git a/modules/simpletest/tests/theme_test.template_test_engine.nyan-cat.html b/modules/simpletest/tests/theme_test.template_test_engine.nyan-cat.html new file mode 100644 index 0000000000..cb21cb5fd8 --- /dev/null +++ b/modules/simpletest/tests/theme_test.template_test_engine.nyan-cat.html @@ -0,0 +1 @@ +Success: Template overridden with Nyan Cat theme. 9kittens diff --git a/modules/simpletest/tests/theme_test_engine_inheritance_nyan.nyan-cat.html b/modules/simpletest/tests/theme_test_engine_inheritance_nyan.nyan-cat.html new file mode 100644 index 0000000000..6e195f981b --- /dev/null +++ b/modules/simpletest/tests/theme_test_engine_inheritance_nyan.nyan-cat.html @@ -0,0 +1 @@ +Inheritance 2. \ No newline at end of file diff --git a/modules/simpletest/tests/theme_test_engine_inheritance_nyan_overriden.nyan-cat.html b/modules/simpletest/tests/theme_test_engine_inheritance_nyan_overriden.nyan-cat.html new file mode 100644 index 0000000000..ba4f98b2d5 --- /dev/null +++ b/modules/simpletest/tests/theme_test_engine_inheritance_nyan_overriden.nyan-cat.html @@ -0,0 +1 @@ +Inheritance 4. \ No newline at end of file diff --git a/modules/simpletest/tests/theme_test_engine_inheritance_phpt.tpl.php b/modules/simpletest/tests/theme_test_engine_inheritance_phpt.tpl.php new file mode 100644 index 0000000000..b6a330dfed --- /dev/null +++ b/modules/simpletest/tests/theme_test_engine_inheritance_phpt.tpl.php @@ -0,0 +1 @@ +Inheritance 1. \ No newline at end of file diff --git a/modules/simpletest/tests/theme_test_engine_inheritance_phpt_overriden.tpl.php b/modules/simpletest/tests/theme_test_engine_inheritance_phpt_overriden.tpl.php new file mode 100644 index 0000000000..c4ae6e5d99 --- /dev/null +++ b/modules/simpletest/tests/theme_test_engine_inheritance_phpt_overriden.tpl.php @@ -0,0 +1 @@ +Inheritance 3. \ No newline at end of file diff --git a/modules/simpletest/tests/themes/test_subtheme_nyan_cat/test_subtheme_nyan_cat.info b/modules/simpletest/tests/themes/test_subtheme_nyan_cat/test_subtheme_nyan_cat.info new file mode 100644 index 0000000000..a656ba5671 --- /dev/null +++ b/modules/simpletest/tests/themes/test_subtheme_nyan_cat/test_subtheme_nyan_cat.info @@ -0,0 +1,5 @@ +name = 'Test subtheme with base theme and another theme engine' +description = 'Test subtheme with base theme and another theme engine.' +version = VERSION +core = 7.x +base theme = test_theme_nyan_cat diff --git a/modules/simpletest/tests/themes/test_theme/templates/theme_test_engine_inheritance_nyan_overriden.tpl.php b/modules/simpletest/tests/themes/test_theme/templates/theme_test_engine_inheritance_nyan_overriden.tpl.php new file mode 100644 index 0000000000..12bca743f6 --- /dev/null +++ b/modules/simpletest/tests/themes/test_theme/templates/theme_test_engine_inheritance_nyan_overriden.tpl.php @@ -0,0 +1 @@ +Inheritance 6. \ No newline at end of file diff --git a/modules/simpletest/tests/themes/test_theme_nyan_cat/template.theme b/modules/simpletest/tests/themes/test_theme_nyan_cat/template.theme new file mode 100644 index 0000000000..aadbce0605 --- /dev/null +++ b/modules/simpletest/tests/themes/test_theme_nyan_cat/template.theme @@ -0,0 +1,13 @@ + 'phptemplate', + 'engine' => NULL, + 'owner' => NULL, 'regions' => array( 'sidebar_first' => 'Left sidebar', 'sidebar_second' => 'Right sidebar', @@ -2560,6 +2564,13 @@ function _system_rebuild_theme_data() { $themes[$key]->filename = $theme->uri; $themes[$key]->info = drupal_parse_info_file($theme->uri) + $defaults; + // Set engine on base theme only, sub-themes are allowed to inherit from + // their parent, so it must not be populated from this point, but will be + // later by traversing the theme parenting tree. + if (empty($themes[$key]->info['base theme']) && empty($themes[$key]->info['engine'])) { + $themes[$key]->info['engine'] = 'phptemplate'; + } + // The "name" key is required, but to avoid a fatal error in the menu system // we set a reasonable default if it is not provided. $themes[$key]->info += array('name' => $key); @@ -2579,6 +2590,7 @@ function _system_rebuild_theme_data() { if ($themes[$key]->info['engine'] == 'theme') { $filename = dirname($themes[$key]->uri) . '/' . $themes[$key]->name . '.theme'; if (file_exists($filename)) { + $themes[$key]->engine = 'theme'; $themes[$key]->owner = $filename; $themes[$key]->prefix = $key; } @@ -2586,6 +2598,7 @@ function _system_rebuild_theme_data() { else { $engine = $themes[$key]->info['engine']; if (isset($engines[$engine])) { + $themes[$key]->engine = $engine; $themes[$key]->owner = $engines[$engine]->uri; $themes[$key]->prefix = $engines[$engine]->name; $themes[$key]->template = TRUE; @@ -2616,16 +2629,17 @@ function _system_rebuild_theme_data() { $themes[$base_theme]->sub_themes[$key] = $themes[$key]->info['name']; } // Copy the 'owner' and 'engine' over if the top level theme uses a theme - // engine. - if (isset($themes[$base_key]->owner)) { - if (isset($themes[$base_key]->info['engine'])) { - $themes[$key]->info['engine'] = $themes[$base_key]->info['engine']; - $themes[$key]->owner = $themes[$base_key]->owner; - $themes[$key]->prefix = $themes[$base_key]->prefix; - } - else { - $themes[$key]->prefix = $key; - } + // engine and the current theme has no engine itself. + // Note that 'engine' must be stored into the info array because it has no + // associated column in the {system} table whereas the 'owner' column + // exists. + if (isset($themes[$base_key]->engine) && !isset($themes[$key]->engine)) { + $themes[$key]->engine = $themes[$key]->info['engine'] = $themes[$base_key]->engine; + $themes[$key]->owner = $themes[$base_key]->owner; + $themes[$key]->prefix = $themes[$base_key]->prefix; + } + else { + $themes[$key]->prefix = $key; } }