diff --git a/includes/module.inc b/includes/module.inc index 2e25108..a068569 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -193,8 +193,11 @@ 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'; + // Add the base theme's theme engine info only when subtheme does not + // define one, this allows to use different theme engines. + if (!isset($lists['theme'][$key]->info['engine'])) { + $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. diff --git a/includes/theme.inc b/includes/theme.inc index 9b606e9..9d160fd 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,19 @@ 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 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) { + @include_once DRUPAL_ROOT . '/' . $current->owner; - // 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; - } - } - // 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 +286,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 +293,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 +302,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 +652,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 +676,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 +1145,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 +1285,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 +1336,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 +1363,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 6baf219..df8d446 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 f5ddfa9..89f02f7 100644 --- a/modules/simpletest/tests/theme.test +++ b/modules/simpletest/tests/theme.test @@ -646,3 +646,78 @@ class ThemeDebugMarkupTestCase 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_engine'); + variable_set('admin_theme', 'test_theme_nyan_cat_engine'); + + $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')); + } + + /** + * Ensures a theme's template is overridable based on the 'template' filename. + */ + function testTemplateOverride() { + variable_set('theme_default', 'test_subtheme_nyan_cat_engine'); + variable_set('admin_theme', 'test_subtheme_nyan_cat_engine'); + + $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.'); + } + + /** + * 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_engine' + $base_theme_name = $infos['test_subtheme_nyan_cat_engine']->info['base theme']; + // Get the base theme + $base_theme = $infos[$base_theme_name]; + + // Compare their theme engine. + $this->assertEqual($infos['test_subtheme_nyan_cat_engine']->info['engine'], $base_theme->info['engine']); + } + +} diff --git a/modules/simpletest/tests/theme_test.module b/modules/simpletest/tests/theme_test.module index 948d817..6518e04 100644 --- a/modules/simpletest/tests/theme_test.module +++ b/modules/simpletest/tests/theme_test.module @@ -17,6 +17,10 @@ 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' + ); return $items; } @@ -27,10 +31,20 @@ function theme_test_system_theme_info() { $themes['test_theme'] = drupal_get_path('module', 'theme_test') . '/themes/test_theme/test_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_engine'] = drupal_get_path('module', 'theme_test') . '/themes/test_theme_nyan_cat_engine/test_theme_nyan_cat_engine.info'; + $themes['test_subtheme_nyan_cat_engine'] = drupal_get_path('module', 'theme_test') . '/themes/test_subtheme_nyan_cat_engine/test_subtheme_nyan_cat_engine.info'; return $themes; } /** + * Implements hook_system_theme_info(). + */ +function theme_test_system_theme_engine_info() { + $theme_engines['nyan_cat'] = drupal_get_path('module', 'theme_test') . '/themes/engines/nyan_cat/nyan_cat.engine'; + return $theme_engines; +} + +/** * Implements hook_menu(). */ function theme_test_menu() { @@ -58,10 +72,25 @@ 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, + ); + 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()), + ); +} + +/** * Implements hook_init(). */ function theme_test_init() { diff --git a/modules/system/system.api.php b/modules/system/system.api.php index 3152139..f1855b9 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -2051,6 +2051,22 @@ function hook_system_theme_info() { } /** + * Return additional theme engines provided by modules. + * + * This hook is invoked from _system_rebuild_theme_data() and allows modules to + * register additional theme engines outside of the regular 'themes/engines' + * directories of a Drupal installation. + * + * @return + * An associative array. Each key is the system name of a theme engine and + * each value is the corresponding path to the theme engine's .engine file. + */ +function hook_system_theme_engine_info() { + $theme_engines['izumi'] = drupal_get_path('module', 'mymodule') . '/izumi/izumi.engine'; + return $theme_engines; +} + +/** * Alter the information parsed from module and theme .info files * * This hook is invoked in _system_rebuild_module_data() and in diff --git a/modules/system/system.module b/modules/system/system.module index 59087c8..4a5c0d4 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -30,7 +30,7 @@ define('DRUPAL_USER_TIMEZONE_EMPTY', 1); */ define('DRUPAL_USER_TIMEZONE_SELECT', 2); - /** +/** * Disabled option on forms and settings */ define('DRUPAL_DISABLED', 0); @@ -2041,7 +2041,7 @@ function system_user_timezone(&$form, &$form_state) { function system_block_info() { $blocks['main'] = array( 'info' => t('Main page content'), - // Cached elsewhere. + // Cached elsewhere. 'cache' => DRUPAL_NO_CACHE, // Auto-enable in 'content' region by default, which always exists. // @see system_themes_page(), drupal_render_page() @@ -2505,6 +2505,9 @@ function _system_update_bootstrap_status() { * An associative array of themes information. */ function _system_rebuild_theme_data() { + // Find theme engines + $engines = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.engine$/', 'themes/engines'); + // Find themes $themes = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info$/', 'themes'); // Allow modules to add further themes. @@ -2519,8 +2522,17 @@ function _system_rebuild_theme_data() { } } - // Find theme engines - $engines = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.engine$/', 'themes/engines'); + // As Drupal is not able to find theme engines in modules, then, we allow + // users to add theme engines through a module hook. + if ($module_themes = module_invoke_all('system_theme_engine_info')) { + foreach ($module_themes as $name => $theme_engine_path) { + $engines[$name] = (object) array( + 'uri' => $theme_engine_path, + 'filename' => basename($theme_engine_path), + 'name' => $name, + ); + } + } // Set defaults for theme info. $defaults = array( @@ -2563,6 +2575,7 @@ function _system_rebuild_theme_data() { $type = 'theme'; drupal_alter('system_info', $themes[$key]->info, $themes[$key], $type); + if (!empty($themes[$key]->info['base theme'])) { $sub_themes[] = $key; } @@ -2605,9 +2618,15 @@ function _system_rebuild_theme_data() { foreach (array_keys($themes[$key]->base_themes) as $base_theme) { $themes[$base_theme]->sub_themes[$key] = $themes[$key]->info['name']; } + + if ($themes[$key]->info['engine'] != $themes[$base_key]->info['engine']) { + $themes[$key]->info['engine'] = $themes[$base_key]->info['engine']; + unset($themes[$key]->owner); + } + // Copy the 'owner' and 'engine' over if the top level theme uses a theme - // engine. - if (isset($themes[$base_key]->owner)) { + // engine and the current theme has no engine itself. + if (isset($themes[$base_key]->owner) && !isset($themes[$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;