Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
project
drupal
Commits
5bbbf10b
Commit
5bbbf10b
authored
Apr 06, 2007
by
Dries Buytaert
Browse files
- Patch
#130987
by merlinofchaos: added theme registry for easier themability.
parent
21c5b717
Changes
27
Hide whitespace changes
Inline
Side-by-side
CHANGELOG.txt
View file @
5bbbf10b
...
...
@@ -2,6 +2,7 @@
Drupal 6.0, xxxx-xx-xx (development version)
----------------------
- Added theme registry: modules can directly provide .tpl.php files for their themes without having to create theme_ functions.
- Added versioning support to node terms.
- Made it easier to theme the forum overview page.
- Drupal works with error reporting set to E_ALL.
...
...
includes/bootstrap.inc
View file @
5bbbf10b
...
...
@@ -929,6 +929,15 @@ function drupal_maintenance_theme() {
drupal_add_css
(
drupal_get_path
(
'module'
,
'system'
)
.
'/defaults.css'
,
'module'
);
drupal_add_css
(
drupal_get_path
(
'module'
,
'system'
)
.
'/system.css'
,
'module'
);
$theme
=
''
;
// Special case registry of theme functions used by the installer
$themes
=
drupal_common_themes
();
foreach
(
$themes
as
$hook
=>
$info
)
{
if
(
!
isset
(
$info
[
'file'
])
&&
!
isset
(
$info
[
'function'
]))
{
$themes
[
$hook
][
'function'
]
=
'theme_'
.
$hook
;
}
}
_theme_set_registry
(
$themes
);
}
/**
...
...
includes/common.inc
View file @
5bbbf10b
...
...
@@ -2292,3 +2292,193 @@ function element_child($key) {
function
element_children
(
$element
)
{
return
array_filter
(
array_keys
((
array
)
$element
),
'element_child'
);
}
/**
* Provide theme registration for themes across .inc files.
*/
function
drupal_common_themes
()
{
return
array
(
// theme.inc
'placeholder'
=>
array
(
'arguments'
=>
array
(
'text'
=>
NULL
)
),
'page'
=>
array
(
'arguments'
=>
array
(
'content'
=>
NULL
,
'show_blocks'
=>
TRUE
),
),
'maintenance_page'
=>
array
(
'arguments'
=>
array
(
'content'
=>
NULL
,
'messages'
=>
TRUE
),
),
'install_page'
=>
array
(
'arguments'
=>
array
(
'content'
=>
NULL
),
),
'task_list'
=>
array
(
'arguments'
=>
array
(
'items'
=>
NULL
,
'active'
=>
NULL
),
),
'status_messages'
=>
array
(
'arguments'
=>
array
(
'display'
=>
NULL
),
),
'links'
=>
array
(
'arguments'
=>
array
(
'links'
=>
NULL
,
'attributes'
=>
array
(
'class'
=>
'links'
)),
),
'image'
=>
array
(
'arguments'
=>
array
(
'path'
=>
NULL
,
'alt'
=>
''
,
'title'
=>
''
,
'attributes'
=>
NULL
,
'getsize'
=>
TRUE
),
),
'breadcrumb'
=>
array
(
'arguments'
=>
array
(
'breadcrumb'
=>
NULL
),
),
'help'
=>
array
(
'arguments'
=>
array
(),
),
'node'
=>
array
(
'arguments'
=>
array
(
'node'
=>
NULL
,
'teaser'
=>
FALSE
,
'page'
=>
FALSE
),
),
'submenu'
=>
array
(
'arguments'
=>
array
(
'links'
=>
NULL
),
),
'table'
=>
array
(
'arguments'
=>
array
(
'header'
=>
NULL
,
'rows'
=>
NULL
,
'attributes'
=>
array
(),
'caption'
=>
NULL
),
),
'table_select_header_cell'
=>
array
(
'arguments'
=>
array
(),
),
'tablesort_indicator'
=>
array
(
'arguments'
=>
array
(
'style'
=>
NULL
),
),
'box'
=>
array
(
'arguments'
=>
array
(
'title'
=>
NULL
,
'content'
=>
NULL
,
'region'
=>
'main'
),
),
'block'
=>
array
(
'arguments'
=>
array
(
'block'
=>
NULL
),
),
'mark'
=>
array
(
'arguments'
=>
array
(
'type'
=>
MARK_NEW
),
),
'item_list'
=>
array
(
'arguments'
=>
array
(
'items'
=>
array
(),
'title'
=>
NULL
,
'type'
=>
'ul'
,
'attributes'
=>
NULL
),
),
'more_help_link'
=>
array
(
'arguments'
=>
array
(
'url'
=>
NULL
),
),
'xml_icon'
=>
array
(
'arguments'
=>
array
(
'url'
=>
NULL
),
),
'feed_icon'
=>
array
(
'arguments'
=>
array
(
'url'
=>
NULL
),
),
'closure'
=>
array
(
'arguments'
=>
array
(
'main'
=>
0
),
),
'blocks'
=>
array
(
'arguments'
=>
array
(
'region'
=>
NULL
),
),
'username'
=>
array
(
'arguments'
=>
array
(
'object'
=>
NULL
),
),
'progress_bar'
=>
array
(
'arguments'
=>
array
(
'percent'
=>
NULL
,
'message'
=>
NULL
),
),
// from pager.inc
'pager'
=>
array
(
'arguments'
=>
array
(
'tags'
=>
array
(),
'limit'
=>
10
,
'element'
=>
0
,
'parameters'
=>
array
()),
),
'pager_first'
=>
array
(
'arguments'
=>
array
(
'text'
=>
NULL
,
'limit'
=>
NULL
,
'element'
=>
0
,
'parameters'
=>
array
()),
),
'pager_previous'
=>
array
(
'arguments'
=>
array
(
'text'
=>
NULL
,
'limit'
=>
NULL
,
'element'
=>
0
,
'interval'
=>
1
,
'parameters'
=>
array
()),
),
'pager_next'
=>
array
(
'arguments'
=>
array
(
'text'
=>
NULL
,
'limit'
=>
NULL
,
'element'
=>
0
,
'interval'
=>
1
,
'parameters'
=>
array
()),
),
'pager_last'
=>
array
(
'arguments'
=>
array
(
'text'
=>
NULL
,
'limit'
=>
NULL
,
'element'
=>
0
,
'parameters'
=>
array
()),
),
'pager_list'
=>
array
(
'arguments'
=>
array
(
'limit'
=>
NULL
,
'element'
=>
0
,
'quantity'
=>
5
,
'text'
=>
''
,
'parameters'
=>
array
()),
),
'pager_link'
=>
array
(
'arguments'
=>
array
(
'text'
=>
NULL
,
'page_new'
=>
NULL
,
'element'
=>
NULL
,
'parameters'
=>
array
(),
'attributes'
=>
array
()),
),
// from locale.inc
'locale_admin_manage_screen'
=>
array
(
'arguments'
=>
array
(
'form'
=>
NULL
),
),
// from menu.inc
'menu_item_link'
=>
array
(
'arguments'
=>
array
(
'item'
=>
NULL
),
),
'menu_tree'
=>
array
(
'arguments'
=>
array
(
'tree'
=>
NULL
),
),
'menu_item'
=>
array
(
'arguments'
=>
array
(
'link'
=>
NULL
,
'has_children'
=>
NULL
,
'menu'
=>
''
),
),
'menu_local_task'
=>
array
(
'arguments'
=>
array
(
'link'
=>
NULL
,
'active'
=>
FALSE
),
),
'menu_local_tasks'
=>
array
(
'arguments'
=>
array
(),
),
// from form.inc
'select'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'fieldset'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'radio'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'radios'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'password_confirm'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'date'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'item'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'checkbox'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'checkboxes'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'submit'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'button'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'hidden'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'token'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'textfield'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'form'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'textarea'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'markup'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'password'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'file'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
),
),
'form_element'
=>
array
(
'arguments'
=>
array
(
'element'
=>
NULL
,
'value'
=>
NULL
),
),
);
}
includes/form.inc
View file @
5bbbf10b
...
...
@@ -457,7 +457,9 @@ function drupal_render_form($form_id, &$form) {
// Don't override #theme if someone already set it.
if
(
!
isset
(
$form
[
'#theme'
]))
{
if
(
theme_get_function
(
$form_id
))
{
init_theme
();
$registry
=
theme_get_registry
();
if
(
isset
(
$registry
[
$form_id
]))
{
$form
[
'#theme'
]
=
$form_id
;
}
}
...
...
includes/theme.inc
View file @
5bbbf10b
...
...
@@ -72,6 +72,7 @@ function init_theme() {
if
(
strpos
(
$themes
[
$theme
]
->
filename
,
'.theme'
))
{
// file is a theme; include it
include_once
'./'
.
$themes
[
$theme
]
->
filename
;
_theme_load_registry
(
$theme
);
}
elseif
(
strpos
(
$themes
[
$theme
]
->
description
,
'.engine'
))
{
// file is a template; include its engine
...
...
@@ -80,9 +81,115 @@ function init_theme() {
if
(
function_exists
(
$theme_engine
.
'_init'
))
{
call_user_func
(
$theme_engine
.
'_init'
,
$themes
[
$theme
]);
}
_theme_load_registry
(
$theme
,
$theme_engine
);
}
}
/**
* Retrieve the stored theme registry. If the theme registry is already
* in memory it will be returned; otherwise it will attempt to load the
* registry from cache. If this fails, it will construct the registry and
* cache it.
*/
function
theme_get_registry
(
$registry
=
NULL
)
{
static
$theme_registry
=
NULL
;
if
(
isset
(
$registry
))
{
$theme_registry
=
$registry
;
}
return
$theme_registry
;
}
/**
* Store the theme registry in memory.
*/
function
_theme_set_registry
(
$registry
)
{
// Pass through for setting of static variable.
return
theme_get_registry
(
$registry
);
}
/**
* Get the theme_registry cache from the database; if it doesn't exist, build
* it.
*/
function
_theme_load_registry
(
$theme
,
$theme_engine
=
NULL
)
{
$cache
=
cache_get
(
"theme_registry:
$theme
"
,
'cache'
);
if
(
isset
(
$cache
->
data
))
{
$registry
=
unserialize
(
$cache
->
data
);
}
else
{
$registry
=
_theme_build_registry
(
$theme
,
$theme_engine
);
_theme_save_registry
(
$theme
,
$registry
);
}
_theme_set_registry
(
$registry
);
}
/**
* Write the theme_registry cache into the database.
*/
function
_theme_save_registry
(
$theme
,
$registry
)
{
cache_set
(
"theme_registry:
$theme
"
,
'cache'
,
serialize
(
$registry
));
}
/**
* Force the system to rebuild the theme registry; this should be called
* when modules are added to the system, or when a dynamic system needs
* to add more theme hooks.
*/
function
drupal_rebuild_theme_registry
()
{
cache_clear_all
(
'theme_registry'
,
'cache'
,
TRUE
);
}
/**
* Process a single invocation of the theme hook.
*/
function
_theme_process_registry
(
&
$cache
,
$name
,
$type
)
{
$function
=
$name
.
'_theme'
;
if
(
function_exists
(
$function
))
{
$result
=
$function
(
$cache
);
// Automatically find paths
$path
=
drupal_get_path
(
$type
,
$name
);
foreach
(
$result
as
$hook
=>
$info
)
{
$result
[
$hook
][
'type'
]
=
$type
;
// if function and file are left out, default to standard naming
// conventions.
if
(
!
isset
(
$info
[
'file'
])
&&
!
isset
(
$info
[
'function'
]))
{
$result
[
$hook
][
'function'
]
=
(
$type
==
'module'
?
'theme_'
:
$name
.
'_'
)
.
$hook
;
}
if
(
isset
(
$info
[
'file'
])
&&
!
isset
(
$info
[
'path'
]))
{
$result
[
$hook
][
'file'
]
=
$path
.
'/'
.
$info
[
'file'
];
}
// If 'arguments' have been defined previously, carry them forward.
// This should happen if a theme overrides a Drupal defined theme
// function, for example.
if
(
!
isset
(
$info
[
'arguments'
])
&&
isset
(
$cache
[
$hook
]))
{
$result
[
$hook
][
'arguments'
]
=
$cache
[
$hook
][
'arguments'
];
}
}
$cache
=
array_merge
(
$cache
,
$result
);
}
}
/**
* Rebuild the hook theme_registry cache.
*/
function
_theme_build_registry
(
$theme
,
$theme_engine
)
{
$cache
=
array
();
foreach
(
module_implements
(
'theme'
)
as
$module
)
{
_theme_process_registry
(
$cache
,
$module
,
'module'
);
}
if
(
$theme_engine
)
{
_theme_process_registry
(
$cache
,
$theme_engine
,
'theme_engine'
);
}
_theme_process_registry
(
$cache
,
$theme
,
'theme'
);
return
$cache
;
}
/**
* Provides a list of currently available themes.
*
...
...
@@ -140,18 +247,48 @@ function list_theme_engines($refresh = FALSE) {
}
/**
* Generate the themed representation of a Drupal object.
* Generate the themed output.
*
* All requests for theme hooks must go through this function. It examines
* the request and routes it to the appropriate theme function. The theme
* registry is checked to determine which implementation to use, which may
* be a function or a template.
*
* If the implementation is a function, it is executed and its return value
* passed along.
*
* All requests for themed functions must go through this function. It examines
* the request and routes it to the appropriate theme function. If the current
* theme does not implement the requested function, then the current theme
* engine is checked. If neither the engine nor theme implement the requested
* function, then the base theme function is called.
* If the implementation is a template, the arguments are converted to a
* $variables array. This array is then modified by the theme engine (if
* applicable) and the theme. The following functions may be used to modify
* the $variables array:
*
* For example, to retrieve the HTML that is output by theme_page($output), a
* module should call theme('page', $output).
* ENGINE_engine_variables(&$variables)
* This function should only be implemented by theme engines and is exists
* so that the theme engine can set necessary variables. It is commonly
* used to set global variables such as $directory and $is_front_page.
* ENGINE_engine_variables_HOOK(&$variables)
* This is the same as the previous function, but is called only per hook.
* ENGINE_variables_HOOK(&$variables)
* ENGINE_variables(&$variables)
* This is meant to be used by themes that utilize a theme engine; as it is
* good practice for these themes to use the theme engine's name for
* their functions so that they may share code. In PHPTemplate, these
* functions will appear in template.php
* THEME_variables_HOOK(&$variables)
* THEME_variables(&$variables)
* These functions are based upon the raw theme; they should primarily be
* used by themes that do not use an engine or by themes that need small
* changes to what has already been established in the theme engine version
* of the function.
*
* @param $function
* There are two special variables that these hooks can set:
* 'template_file' and 'template_files'. These will be merged together
* to form a list of 'suggested' alternate template files to use, in
* reverse order of priority. template_file will always be a higher
* priority than items in template_files. theme() will then look for these
* files, one at a time, and use the first one
* that exists.
* @param $hook
* The name of the theme function to call.
* @param ...
* Additional arguments to pass along to the theme function.
...
...
@@ -159,48 +296,125 @@ function list_theme_engines($refresh = FALSE) {
* An HTML string that generates the themed output.
*/
function
theme
()
{
static
$functions
;
$args
=
func_get_args
();
$
function
=
array_shift
(
$args
);
$
hook
=
array_shift
(
$args
);
if
(
!
isset
(
$functions
[
$function
]))
{
$functions
[
$function
]
=
theme_get_function
(
$function
);
static
$hooks
=
NULL
;
if
(
!
isset
(
$hooks
))
{
init_theme
();
$hooks
=
theme_get_registry
();
}
if
(
$functions
[
$function
])
{
return
call_user_func_array
(
$functions
[
$function
],
$args
);
if
(
!
isset
(
$hooks
[
$hook
]))
{
return
;
}
}
/**
* Determine if a theme function exists, and if so return which one was found.
*
* @param $function
* The name of the theme function to test.
* @return
* The name of the theme function that should be used, or FALSE if no function exists.
*/
function
theme_get_function
(
$function
)
{
global
$theme
,
$theme_engine
;
$info
=
$hooks
[
$hook
];
// Because theme() is called a lot, calling init_theme() only to have it
// smartly return is a noticeable performance hit. Don't do it.
if
(
!
isset
(
$theme
))
{
init_theme
();
if
(
isset
(
$info
[
'function'
]))
{
// The theme call is a function.
// Include a file if this theme function is held elsewhere.
if
(
!
empty
(
$info
[
'file'
]))
{
include_once
(
$info
[
'file'
]);
}
return
call_user_func_array
(
$info
[
'function'
],
$args
);
}
else
{
// The theme call is a template.
$variables
=
array
(
'template_files'
=>
array
()
);
if
(
!
empty
(
$info
[
'arguments'
]))
{
$count
=
0
;
foreach
(
$info
[
'arguments'
]
as
$name
=>
$default
)
{
$variables
[
$name
]
=
isset
(
$args
[
$count
])
?
$args
[
$count
]
:
$default
;
$count
++
;
}
}
if
((
$theme
!=
''
)
&&
function_exists
(
$theme
.
'_'
.
$function
))
{
// call theme function
return
$theme
.
'_'
.
$function
;
}
elseif
((
$theme
!=
''
)
&&
isset
(
$theme_engine
)
&&
function_exists
(
$theme_engine
.
'_'
.
$function
))
{
// call engine function
return
$theme_engine
.
'_'
.
$function
;
// default render function and extension.
$render_function
=
'theme_render_template'
;
$extension
=
'.tpl.php'
;
$variables_list
=
array
();
// Run through the theme engine variables, if necessary
global
$theme_engine
;
if
(
isset
(
$theme_engine
))
{
// Call each of our variable override functions. We allow
// several to create cleaner code.
$variables_list
[]
=
$theme_engine
.
'_engine_variables'
;
$variables_list
[]
=
$theme_engine
.
'_engine_variables_'
.
$hook
;
$variables_list
[]
=
$theme_engine
.
'_variables'
;
$variables_list
[]
=
$theme_engine
.
'_variables_'
.
$hook
;
// If theme or theme engine is implementing this, it may have
// a different extension and a different renderer.
if
(
$hooks
[
$hook
][
'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
();
}
}
}
// Add theme specific variable substitution:
global
$theme
;
$variables_list
[]
=
$theme
.
'_variables'
;
$variables_list
[]
=
$theme
.
'_variables_'
.
$hook
;
// This construct ensures that we can keep a reference through
// call_user_func_array.
$args
=
array
(
&
$variables
,
$hook
);
foreach
(
$variables_list
as
$variables_function
)
{
if
(
function_exists
(
$variables_function
))
{
call_user_func_array
(
$variables_function
,
$args
);
}
}
// Get suggestions for alternate templates out of the variables
// that were set. This lets us dynamically choose a template
// from a list. The order is FILO, so this array is ordered from
// least appropriate first to most appropriate last.
$suggestions
=
array
();
if
(
isset
(
$variables
[
'template_files'
]))
{
$suggestions
=
$variables
[
'template_files'
];
}
if
(
isset
(
$variables
[
'template_file'
]))
{
$suggestions
[]
=
$variables
[
'template_file'
];
}
if
(
$suggestions
)
{
$template_file
=
drupal_discover_template
(
$suggestions
,
$extension
);
}
if
(
empty
(
$template_file
))
{
$template_file
=
$hooks
[
$hook
][
'file'
]
.
$extension
;
if
(
isset
(
$hooks
[
$hook
][
'path'
]))
{
$template_file
=
$hooks
[
$hook
][
'path'
]
.
'/'
.
$template_file
;
}
}
return
$render_function
(
$template_file
,
$variables
);
}
elseif
(
function_exists
(
'theme_'
.
$function
)){
// call Drupal function
return
'theme_'
.
$function
;
}
/**
* Choose which template file to actually render; these are all
* suggested templates from the theme.
*/
function
drupal_discover_template
(
$suggestions
,
$extension
=
'.tpl.php'
)
{
global
$theme_engine
;
// Loop through any suggestions in FIFO order.
$suggestions
=
array_reverse
(
$suggestions
);
foreach
(
$suggestions
as
$suggestion
)
{
if
(
!
empty
(
$suggestion
)
&&
file_exists
(
$file
=
path_to_theme
()
.
'/'
.
$suggestion
.
$extension
))
{
return
$file
;
}
}
return
FALSE
;
}
/**
...
...
@@ -348,16 +562,81 @@ function theme_get_setting($setting_name, $refresh = FALSE) {
}
/**
* @defgroup themeable Themeable functions
* Render a system default template, which is essentially a PHP template.
*
* @param $file
* The filename of the template to render.
* @param $variables
* A keyed array of variables that will appear in the output.
*
* @return
* The output generated by the template.
*/
function
theme_render_template
(
$file
,
$variables
)
{
extract
(
$variables
,
EXTR_SKIP
);
// Extract the variables to a local namespace
ob_start
();
// Start output buffering
include
"./
$file
"
;
// Include the file
$contents
=
ob_get_contents
();
// Get the contents of the buffer
ob_end_clean
();
// End buffering and discard
return
$contents
;
// Return the contents
}
/**
* @defgroup themeable Default theme implementations
* @{
* Functions that display HTML, and which can be customized by themes.
* Functions and templates that present output to the user, and can be
* implemented by themes.
*
* Drupal's presentation layer is a pluggable system known as the theme
* layer. Each theme can take control over most of Drupal's output, and
* has complete control over the CSS.
*
* All functions that produce HTML for display should be themeable. This means
* that they should be named with the theme_ prefix, and invoked using theme()
* rather than being called directly. This allows themes to override the display
* of any Drupal object.
* Inside Drupal, the theme layer is utilized by the use of the theme()
* function, which is passed the name of a component (the theme hook)
* and several arguments. For example, theme('table', $header, $rows);
*
* As of Drupal 6, every theme hook is required to be registered by the
* module that owns it, so that Drupal can tell what to do with it and
* to make it simple for themes to identify and override the behavior
* for these calls.
*
* The theme hooks are registered via hook_theme(), which returns an
* array of arrays with information about the hook. It describes the
* arguments the function or template will need, and provides