diff --git a/core/includes/theme.inc b/core/includes/theme.inc index b349918..fbc05f3 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -2342,6 +2342,7 @@ function drupal_common_theme() { ), 'indentation' => array( 'variables' => array('size' => 1), + 'function' => 'theme_indentation', ), // From theme.maintenance.inc. 'maintenance_page' => array( @@ -2358,9 +2359,11 @@ function drupal_common_theme() { ), 'authorize_message' => array( 'variables' => array('message' => NULL, 'success' => TRUE), + 'function' => 'theme_authorize_message', ), 'authorize_report' => array( 'variables' => array('messages' => array()), + 'function' => 'theme_authorize_report', ), // From pager.inc. 'pager' => array( @@ -2370,6 +2373,9 @@ function drupal_common_theme() { // From menu.inc. 'menu_link' => array( 'render element' => 'element', + 'function' => 'theme_menu_link', + 'path' => 'core/includes', + 'file' => 'menu.inc', ), 'menu_tree' => array( 'render element' => 'tree', diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php index 0ad005d..1c883d5 100644 --- a/core/lib/Drupal/Core/Theme/Registry.php +++ b/core/lib/Drupal/Core/Theme/Registry.php @@ -401,6 +401,9 @@ protected function build() { * @see _theme() * @see hook_theme() * @see list_themes() + * @see twig_render_template() + * + * @throws \BadFunctionCallException */ protected function processExtension(&$cache, $name, $type, $theme, $path) { $result = array(); @@ -430,12 +433,6 @@ protected function processExtension(&$cache, $name, $type, $theme, $path) { $result[$hook]['type'] = $type; $result[$hook]['theme path'] = $path; - // If function and file are omitted, default to standard naming - // conventions. - if (!isset($info['template']) && !isset($info['function'])) { - $result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name . '_') . $hook; - } - if (isset($cache[$hook]['includes'])) { $result[$hook]['includes'] = $cache[$hook]['includes']; } @@ -451,20 +448,33 @@ protected function processExtension(&$cache, $name, $type, $theme, $path) { $result[$hook]['includes'][] = $include_file; } + // Templates are the default implementation for a theme hook. If a + // theme hook provides a theme function callback, check to ensure that + // it actually exists. + if (isset($info['function']) && !function_exists($info['function'])) { + continue; + } + // Provide a default naming convention for 'template' based on the + // hook used. If the template does not exist, the theme engine used + // should throw an exception at runtime when attempting to include + // the template file. + elseif (!isset($info['template'])) { + $info['template'] = strtr($hook, '_', '-'); + $result[$hook]['template'] = $info['template']; + } + + // Prepend the current theming path when none is set. This is required + // for the default theme engine to know where the template lives. + if (isset($result[$hook]['template']) && !isset($info['path'])) { + $result[$hook]['path'] = $path . '/templates'; + } + // If the default keys are not set, use the default values registered // by the module. if (isset($cache[$hook])) { $result[$hook] += array_intersect_key($cache[$hook], $hook_defaults); } - // The following apply only to theming hooks implemented as templates. - if (isset($info['template'])) { - // Prepend the current theming path when none is set. - if (!isset($info['path'])) { - $result[$hook]['template'] = $path . '/templates/' . $info['template']; - } - } - // Preprocess variables for all theming hooks, whether the hook is // implemented as a template or as a function. Ensure they are arrays. if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) { diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module index 64eb275..5908dc0 100644 --- a/core/modules/aggregator/aggregator.module +++ b/core/modules/aggregator/aggregator.module @@ -84,10 +84,7 @@ function aggregator_theme() { 'aggregator_page_opml' => array( 'variables' => array('feeds' => NULL), 'file' => 'aggregator.theme.inc', - ), - 'aggregator_page_rss' => array( - 'variables' => array('feeds' => NULL), - 'file' => 'aggregator.theme.inc', + 'function' => 'theme_aggregator_page_opml', ), ); } diff --git a/core/modules/book/book.module b/core/modules/book/book.module index e67b279..d2ccca9 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -62,6 +62,7 @@ function book_theme() { ), 'book_link' => array( 'render element' => 'element', + 'function' => 'theme_book_link', ), 'book_export_html' => array( 'variables' => array('title' => NULL, 'contents' => NULL, 'depth' => NULL), @@ -70,6 +71,7 @@ function book_theme() { 'book_admin_table' => array( 'render element' => 'form', 'file' => 'book.admin.inc', + 'function' => 'theme_book_admin_table', ), 'book_all_books_block' => array( 'render element' => 'book_menus', diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module index 9dbcbd2..b349527 100644 --- a/core/modules/field_ui/field_ui.module +++ b/core/modules/field_ui/field_ui.module @@ -96,6 +96,7 @@ function field_ui_theme() { return array( 'field_ui_table' => array( 'render element' => 'elements', + 'function' => 'theme_field_ui_table', ), ); } diff --git a/core/modules/language/language.module b/core/modules/language/language.module index 0c37638..9843f08 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -111,10 +111,12 @@ function language_theme() { 'language_negotiation_configure_browser_form_table' => array( 'render element' => 'form', 'file' => 'language.admin.inc', + 'function' => 'theme_language_negotiation_configure_browser_form_table', ), 'language_content_settings_table' => array( 'render element' => 'element', 'file' => 'language.admin.inc', + 'function' => 'theme_language_content_settings_table', ), ); } diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 3122f59..93f4665 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -193,6 +193,7 @@ function locale_theme() { 'locale_translate_edit_form_strings' => array( 'render element' => 'form', 'file' => 'locale.pages.inc', + 'function' => 'theme_locale_translate_edit_form_strings', ), 'locale_translation_last_check' => array( 'variables' => array('last' => NULL), diff --git a/core/modules/menu_ui/menu_ui.module b/core/modules/menu_ui/menu_ui.module index dcedc00..b709865 100644 --- a/core/modules/menu_ui/menu_ui.module +++ b/core/modules/menu_ui/menu_ui.module @@ -89,6 +89,7 @@ function menu_ui_theme() { 'menu_overview_form' => array( 'file' => 'menu_ui.admin.inc', 'render element' => 'form', + 'function' => 'theme_menu_overview_form', ), ); } diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 030e62a..3dbeb2e 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -163,6 +163,7 @@ function node_theme() { ), 'node_search_admin' => array( 'render element' => 'form', + 'function' => 'theme_node_search_admin', ), 'node_add_list' => array( 'variables' => array('content' => NULL), diff --git a/core/modules/responsive_image/responsive_image.module b/core/modules/responsive_image/responsive_image.module index d8985c9..42b9926 100644 --- a/core/modules/responsive_image/responsive_image.module +++ b/core/modules/responsive_image/responsive_image.module @@ -91,6 +91,7 @@ function responsive_image_theme() { 'attributes' => array(), 'mapping_id' => array(), ), + 'function' => 'theme_responsive_image', ), 'responsive_image_formatter' => array( 'variables' => array( @@ -99,6 +100,7 @@ function responsive_image_theme() { 'image_style' => NULL, 'mapping_id' => array(), ), + 'function' => 'theme_responsive_image_formatter', ), 'responsive_image_source' => array( 'variables' => array( @@ -107,6 +109,7 @@ function responsive_image_theme() { 'dimensions' => NULL, 'media' => NULL, ), + 'function' => 'theme_responsive_image_source', ), ); } diff --git a/core/modules/system/system.module b/core/modules/system/system.module index f3b65a2..96f0458 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -180,10 +180,12 @@ function system_theme() { 'system_modules_details' => array( 'render element' => 'form', 'file' => 'system.admin.inc', + 'function' => 'theme_system_modules_details', ), 'system_modules_uninstall' => array( 'render element' => 'form', 'file' => 'system.admin.inc', + 'function' => 'theme_system_modules_uninstall', ), 'status_report' => array( 'variables' => array('requirements' => NULL), @@ -212,6 +214,7 @@ function system_theme() { ), 'system_compact_link' => array( 'variables' => array(), + 'function' => 'theme_system_compact_link', ), )); } diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module index bd17563..e23a842 100644 --- a/core/modules/system/tests/modules/common_test/common_test.module +++ b/core/modules/system/tests/modules/common_test/common_test.module @@ -123,6 +123,7 @@ function common_test_theme() { ), 'common_test_empty' => array( 'variables' => array('foo' => 'foo'), + 'function' => 'theme_common_test_empty', ), ); } diff --git a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module index e963c2f..974856a 100644 --- a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module +++ b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module @@ -12,6 +12,7 @@ function theme_suggestions_test_theme() { $items['theme_suggestions_test_include'] = array( 'file' => 'theme_suggestions_test.inc', 'variables' => array(), + 'function' => 'theme_theme_suggestions_test_include', ); return $items; } diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module index dd71fb7..8616f02 100644 --- a/core/modules/system/tests/modules/theme_test/theme_test.module +++ b/core/modules/system/tests/modules/theme_test/theme_test.module @@ -9,6 +9,7 @@ function theme_test_theme($existing, $type, $theme, $path) { $items['theme_test'] = array( 'file' => 'theme_test.inc', 'variables' => array('foo' => ''), + 'function' => 'theme_theme_test', ); $items['theme_test_template_test'] = array( 'template' => 'theme_test.template_test', @@ -34,12 +35,15 @@ function theme_test_theme($existing, $type, $theme, $path) { ); $items['theme_test_function_suggestions'] = array( 'variables' => array(), + 'function' => 'theme_theme_test_function_suggestions', ); $items['theme_test_suggestions_include'] = array( 'variables' => array(), + 'function' => 'theme_theme_test_suggestions_include', ); $items['theme_test_foo'] = array( 'variables' => array('foo' => NULL), + 'function' => 'theme_theme_test_foo', ); $items['theme_test_render_element'] = array( 'render element' => 'elements', @@ -47,9 +51,11 @@ function theme_test_theme($existing, $type, $theme, $path) { ); $items['theme_test_render_element_children'] = array( 'render element' => 'element', + 'function' => 'theme_theme_test_render_element_children', ); $items['theme_test_function_template_override'] = array( 'variables' => array(), + 'function' => 'theme_theme_test_function_template_override', ); $info['test_theme_not_existing_function'] = array( 'function' => 'test_theme_not_existing_function', diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module index 49e2530..2fa7a6e 100644 --- a/core/modules/toolbar/toolbar.module +++ b/core/modules/toolbar/toolbar.module @@ -52,9 +52,6 @@ function toolbar_theme($existing, $type, $theme, $path) { 'render element' => 'element', 'template' => 'toolbar', ); - $items['toolbar_item'] = array( - 'render element' => 'element', - ); return $items; } @@ -96,7 +93,6 @@ function toolbar_element_info() { // property contains a renderable array. $elements['toolbar_item'] = array( '#pre_render' => array('toolbar_pre_render_item'), - '#theme' => 'toolbar_item', 'tab' => array( '#type' => 'link', '#title' => NULL, diff --git a/core/modules/update/update.module b/core/modules/update/update.module index 6529a48..09ce0fc 100644 --- a/core/modules/update/update.module +++ b/core/modules/update/update.module @@ -176,24 +176,20 @@ function update_theme() { return array( 'update_last_check' => array( 'variables' => array('last' => 0), - 'template' => 'update-last-check', ), 'update_report' => array( 'variables' => array('data' => NULL), 'file' => 'update.report.inc', - 'template' => 'update-report', ), 'update_project_status' => array( 'variables' => array('project' => array(), 'includes_status' => array()), 'file' => 'update.report.inc', - 'template' => 'update-project-status', ), + // We are using template instead of '#type' => 'table' here to keep markup + // out of preprocess and allow for easier changes to markup. 'update_version' => array( 'variables' => array('version' => NULL, 'title' => NULL, 'attributes' => array()), 'file' => 'update.report.inc', - // We are using template instead of '#type' => 'table' here to keep markup - // out of preprocess and allow for easier changes to markup. - 'template' => 'update-version', ), ); } diff --git a/core/modules/views/views.module b/core/modules/views/views.module index 3ba93f5..2fb0ee0 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -155,6 +155,7 @@ function views_theme($existing, $type, $theme, $path) { // Default view themes $hooks['views_view_field'] = $base + array( 'variables' => array('view' => NULL, 'field' => NULL, 'row' => NULL), + 'function' => 'theme_views_view_field', ); $hooks['views_view_grouping'] = $base + array( 'variables' => array('view' => NULL, 'grouping' => NULL, 'grouping_level' => NULL, 'rows' => NULL, 'title' => NULL), @@ -187,35 +188,44 @@ function views_theme($existing, $type, $theme, $path) { 'variables' => $variables[$type], ); - // For the views module we ensure views.theme.inc is included. - if ($def['provider'] == 'views') { - $def['theme_file'] = 'views.theme.inc'; - } // We always use the module directory as base dir. $module_dir = drupal_get_path('module', $def['provider']); + $hooks[$def['theme']]['path'] = $module_dir; + // For the views module we ensure views.theme.inc is included. + if ($def['provider'] == 'views') { + if (!isset($hooks[$def['theme']]['includes'])) { + $hooks[$def['theme']]['includes'] = array(); + } + if (!in_array('views.theme.inc', $hooks[$def['theme']]['includes'])) { + $hooks[$def['theme']]['includes'][] = $module_dir . '/views.theme.inc'; + } + } // The theme_file definition is always relative to the modules directory. - if (isset($def['theme_file'])) { - $hooks[$def['theme']]['path'] = $module_dir; + elseif (!empty($def['theme_file'])) { $hooks[$def['theme']]['file'] = $def['theme_file']; } + // Whenever we got a theme file, we include it directly so we can // auto-detect the theme function. if (isset($def['theme_file'])) { - $include = DRUPAL_ROOT . '/' . $module_dir. '/' . $def['theme_file']; + $include = DRUPAL_ROOT . '/' . $module_dir . '/' . $def['theme_file']; if (is_file($include)) { require_once $include; } } - // If there is no theme function for the given theme definition, we assume - // a template file shall be used. By default this file is located in the - // /templates directory of the module's folder. - // If a module wants to define its own location it has to set - // register_theme of the plugin to FALSE and implement hook_theme() by - // itself. + + // If there is no theme function for the given theme definition, it must + // be a template file. By default this file is located in the /templates + // directory of the module's folder. If a module wants to define its own + // location it has to set register_theme of the plugin to FALSE and + // implement hook_theme() by itself. if (!function_exists('theme_' . $def['theme'])) { - $hooks[$def['theme']]['path'] = $module_dir; - $hooks[$def['theme']]['template'] = 'templates/' . drupal_clean_css_identifier($def['theme']); + $hooks[$def['theme']]['path'] .= '/templates'; + $hooks[$def['theme']]['template'] = drupal_clean_css_identifier($def['theme']); + } + else { + $hooks[$def['theme']]['function'] = 'theme_' . $def['theme']; } } } diff --git a/core/modules/views_ui/views_ui.module b/core/modules/views_ui/views_ui.module index 3960294..902983c 100644 --- a/core/modules/views_ui/views_ui.module +++ b/core/modules/views_ui/views_ui.module @@ -77,6 +77,7 @@ function views_ui_theme() { 'render element' => 'element', 'template' => 'views-ui-display-tab-bucket', 'file' => 'views_ui.theme.inc', + 'function' => 'theme_views_ui_rearrange_filter_form', ), 'views_ui_rearrange_filter_form' => array( 'render element' => 'form', @@ -100,6 +101,7 @@ function views_ui_theme() { 'views_ui_build_group_filter_form' => array( 'render element' => 'form', 'file' => 'views_ui.theme.inc', + 'function' => 'theme_views_ui_build_group_filter_form', ), // On behalf of a plugin diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine index e0cfcc2..06ec07c 100644 --- a/core/themes/engines/twig/twig.engine +++ b/core/themes/engines/twig/twig.engine @@ -38,23 +38,28 @@ function twig_init(Extension $theme) { * If the Twig debug setting is enabled, HTML comments including _theme() call * and template file name suggestions will surround the template markup. * - * @param $template_file + * @param string $template_file * The file name of the template to render. - * @param $variables + * @param array $variables * A keyed array of variables that will appear in the output. * - * @return + * @return string * The output generated by the template, plus any debug information. */ function twig_render_template($template_file, $variables) { /** @var \Twig_Environment $twig_service */ $twig_service = \Drupal::service('twig'); - $output = array( - 'debug_prefix' => '', - 'debug_info' => '', - 'rendered_markup' => $twig_service->loadTemplate($template_file)->render($variables), - 'debug_suffix' => '', - ); + try { + $output = array( + 'debug_prefix' => '', + 'debug_info' => '', + 'rendered_markup' => $twig_service->loadTemplate($template_file)->render($variables), + 'debug_suffix' => '', + ); + } + catch (\Twig_Error_Loader $e) { + drupal_set_message($e->getMessage(), 'error'); + } if ($twig_service->isDebug()) { $output['debug_prefix'] .= "\n\n"; $output['debug_prefix'] .= "\n";