Commit 03b4b58e authored by Dries's avatar Dries

- Patch #132018 by Steven et al: add .info files to themes.

parent e98c82e6
......@@ -20,6 +20,7 @@ Drupal 6.0, xxxx-xx-xx (development version)
* Language detection based on parts of the URL.
* Browser based language detection.
- Language dependent path aliases.
- Added .info files to themes and made it easier to specify regions and features.
Drupal 5.0, 2007-01-15
----------------------
......
......@@ -2482,3 +2482,114 @@ function drupal_common_themes() {
),
);
}
/**
* Parse Drupal info file format.
*
* Files should use an ini-like format to specify values.
* White-space generally doesn't matter, except inside values.
* e.g.
* key = value
* key = "value"
* key = 'value'
* key = "multi-line
*
* value"
* key = 'multi-line
*
* value'
*
* Arrays are created using a GET-like syntax:
*
* key[] = "numeric array"
* key[index] = "associative array"
* key[index][] = "nested numeric array"
* key[index][index] = "nested associative array"
*
* PHP constants are substituted in, but only when used as the entire value.
*
* Comments should start with a semi-colon at the beginning of a line.
*
* This function is NOT for placing arbitrary module-specific settings. Use
* variable_get() and variable_set() for that.
*
* Information stored in the module.info file:
* name - The real name of the module for display purposes.
* description - A brief description of the module.
* dependencies - An array of short names (shortname) of other modules this
* module depends on.
* package - The name of the package of modules this module belongs to.
*
* Example of .info file:
* name = Forum
* description = Enables threaded discussions about general topics.
* dependencies[] = taxonomy
* dependencies[] = comment
* package = Core - optional
* version = VERSION
*
* @param $filename
* The file we are parsing. Accepts file with relative or absolute path.
* @return
* The info array.
*/
function drupal_parse_info_file($filename) {
$info = array();
if (!file_exists($filename)) {
return $info;
}
$data = file_get_contents($filename);
if (preg_match_all('
@^\s* # Start at the beginning of a line, ignoring leading whitespace
((?:
[^=;\[\]]| # Key names cannot contain equal signs, semi-colons or square brackets,
\[[^\[\]]*\] # unless they are balanced and not nested
)+?)
\s*=\s* # Key/value pairs are separated by equal signs (ignoring white-space)
(?:
("(?:[^"]|(?<=\\\\)")*")| # Double-quoted string, which may contain slash-escaped quotes/slashes
(\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes
([^\r\n]*?) # Non-quoted string
)\s*$ # Stop at the next end of a line, ignoring trailing whitespace
@msx', $data, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
// Fetch the key and value string
$i = 0;
foreach (array('key', 'value1', 'value2', 'value3') as $var) {
$$var = isset($match[++$i]) ? $match[$i] : '';
}
$value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3;
// Parse array syntax
$keys = preg_split('/\]?\[/', rtrim($key, ']'));
$last = array_pop($keys);
$parent = &$info;
// Create nested arrays
foreach ($keys as $key) {
if ($key == '') {
$key = count($parent);
}
if (!isset($parent[$key]) || !is_array($parent[$key])) {
$parent[$key] = array();
}
$parent = &$parent[$key];
}
// Handle PHP constants
if (defined($value)) {
$value = constant($value);
}
// Insert actual value
if ($last == '') {
$last = count($parent);
}
$parent[$last] = $value;
}
}
return $info;
}
......@@ -311,8 +311,7 @@ function drupal_install_profile($profile, $module_list) {
module_invoke('system', 'install');
$system_versions = drupal_get_schema_versions('system');
$system_version = $system_versions ? max($system_versions) : SCHEMA_INSTALLED;
db_query("INSERT INTO {system} (filename, name, type, description, status, throttle, bootstrap, schema_version) VALUES('%s', '%s', 'module', '', 1, 0, 0, %d)", $system_path .'/system.module', 'system', $system_version);
db_query("INSERT INTO {system} (filename, name, type, owner, status, throttle, bootstrap, schema_version) VALUES('%s', '%s', 'module', '', 1, 0, 0, %d)", $system_path .'/system.module', 'system', $system_version);
// Now that we've installed things properly, bootstrap the full Drupal environment
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
......
......@@ -102,16 +102,27 @@ function module_rebuild_cache() {
ksort($files);
// Set defaults for module info
$defaults = array(
'dependencies' => array(),
'dependents' => array(),
'description' => '',
'version' => NULL
);
foreach ($files as $filename => $file) {
$file->info = _module_parse_info_file(dirname($file->filename) .'/'. $file->name .'.info');
// Look for the info file.
$file->info = drupal_parse_info_file(dirname($file->filename) .'/'. $file->name .'.info');
// Skip modules that don't provide info.
if (empty($file->info)) {
unset($files[$filename]);
continue;
}
$files[$filename]->info = $file->info;
// Merge in defaults and save.
$files[$filename]->info = $file->info + $defaults;
// log the critical hooks implemented by this module
// Log the critical hooks implemented by this module.
$bootstrap = 0;
foreach (bootstrap_hooks() as $hook) {
if (module_hook($file->name, $hook)) {
......@@ -121,15 +132,14 @@ function module_rebuild_cache() {
}
// Update the contents of the system table:
// TODO: We shouldn't actually need this description field anymore. Remove me next release.
if (isset($file->status) || (isset($file->old_filename) && $file->old_filename != $file->filename)) {
db_query("UPDATE {system} SET description = '%s', name = '%s', filename = '%s', bootstrap = %d WHERE filename = '%s'", $file->info['description'], $file->name, $file->filename, $bootstrap, $file->old_filename);
db_query("UPDATE {system} SET info = '%s', name = '%s', filename = '%s', bootstrap = %d WHERE filename = '%s'", serialize($file->info), $file->name, $file->filename, $bootstrap, $file->old_filename);
}
else {
// This is a new module.
$files[$filename]->status = 0;
$files[$filename]->throttle = 0;
db_query("INSERT INTO {system} (name, description, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", $file->name, $file->info['description'], 'module', $file->filename, 0, 0, $bootstrap);
db_query("INSERT INTO {system} (name, info, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", $file->name, serialize($file->info), 'module', $file->filename, 0, 0, $bootstrap);
}
}
$files = _module_build_dependents($files);
......@@ -159,53 +169,6 @@ function _module_build_dependents($files) {
return $files;
}
/**
* Parse Drupal info file format.
* Uses ini parser provided by php's parse_ini_file().
*
* Files should use the ini format to specify values.
* e.g.
* key = "value"
* key2 = value2
*
* Some things to be aware of:
* - This function is NOT for placing arbitrary module-specific settings. Use variable_get()
* and variable_set() for that.
* - You may not use double-quotes in a value.
*
* Information stored in the module.info file:
* name - The real name of the module for display purposes.
* description - A brief description of the module.
* dependencies - A space delimited list of the short names (shortname) of other modules this module depends on.
* package - The name of the package of modules this module belongs to.
*
* Example of .info file:
* name = Forum
* description = Enables threaded discussions about general topics.
* dependencies = taxonomy comment
* package = Core - optional
*
* @param $filename
* The file we are parsing. Accepts file with relative or absolute path.
* @return
* The info array.
*/
function _module_parse_info_file($filename) {
$info = array();
if (file_exists($filename)) {
$info = parse_ini_file($filename);
if (isset($info['dependencies'])) {
$info['dependencies'] = explode(" ", $info['dependencies']);
}
else {
$info['dependencies'] = NULL;
}
}
return $info;
}
/**
* Determine whether a given module exists.
*
......
......@@ -59,7 +59,7 @@ function init_theme() {
// File is a style; loads its CSS.
// Set theme to its template/theme
drupal_add_css($themes[$theme]->filename, 'theme');
$theme = basename(dirname($themes[$theme]->description));
$theme = basename(dirname($themes[$theme]->owner));
}
else {
// File is a template/theme
......@@ -74,10 +74,10 @@ function init_theme() {
include_once './'. $themes[$theme]->filename;
_theme_load_registry($theme);
}
elseif (strpos($themes[$theme]->description, '.engine')) {
elseif (strpos($themes[$theme]->owner, '.engine')) {
// file is a template; include its engine
include_once './'. $themes[$theme]->description;
$theme_engine = basename($themes[$theme]->description, '.engine');
include_once './'. $themes[$theme]->owner;
$theme_engine = basename($themes[$theme]->owner, '.engine');
if (function_exists($theme_engine .'_init')) {
call_user_func($theme_engine .'_init', $themes[$theme]);
}
......@@ -210,6 +210,7 @@ function list_themes($refresh = FALSE) {
$result = db_query("SELECT * FROM {system} WHERE type = 'theme'");
while ($theme = db_fetch_object($result)) {
if (file_exists($theme->filename)) {
$theme->info = unserialize($theme->info);
$list[$theme->name] = $theme;
}
}
......@@ -238,6 +239,7 @@ function list_theme_engines($refresh = FALSE) {
$result = db_query("SELECT * FROM {system} WHERE type = 'theme_engine' AND status = '1' ORDER BY name");
while ($engine = db_fetch_object($result)) {
if (file_exists($engine->filename)) {
$engine->info = unserialize($engine->info);
$list[$engine->name] = $engine;
}
}
......
......@@ -100,9 +100,9 @@ function block_menu() {
$default = variable_get('theme_default', 'garland');
foreach (list_themes() as $key => $theme) {
$items['admin/build/block/list/'. $key] = array(
'title' => t('!key settings', array('!key' => $key)),
'title' => t('!key settings', array('!key' => $theme->info['name'])),
'page arguments' => array('block_admin_display', $key),
'type' => $key== $default ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
'type' => $key == $default ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
'weight' => $key == $default ? -10 : 0,
);
}
......
; $Id$
name = Forum
description = Enables threaded discussions about general topics.
dependencies = taxonomy comment
dependencies[] = taxonomy
dependencies[] = comment
package = Core - optional
version = VERSION
......@@ -96,4 +96,17 @@ table.system-status-report tr.ok th {
}
.theme-settings-bottom {
clear: both;
}
/**
* Formatting for theme overview
*/
table.screenshot {
margin-right: 1em;
}
.theme-info h2 {
margin-bottom: 0;
}
.theme-info p {
margin-top: 0;
}
\ No newline at end of file
......@@ -494,12 +494,13 @@ function system_install() {
filename varchar(255) NOT NULL default '',
name varchar(255) NOT NULL default '',
type varchar(255) NOT NULL default '',
description varchar(255) NOT NULL default '',
owner varchar(255) NOT NULL default '',
status int NOT NULL default '0',
throttle tinyint DEFAULT '0' NOT NULL,
bootstrap int NOT NULL default '0',
schema_version smallint NOT NULL default -1,
weight int NOT NULL default '0',
info text,
PRIMARY KEY (filename),
KEY (weight)
) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
......@@ -967,12 +968,13 @@ function system_install() {
filename varchar(255) NOT NULL default '',
name varchar(255) NOT NULL default '',
type varchar(255) NOT NULL default '',
description varchar(255) NOT NULL default '',
owner varchar(255) NOT NULL default '',
status int NOT NULL default '0',
throttle smallint DEFAULT '0' NOT NULL,
bootstrap int NOT NULL default '0',
schema_version smallint NOT NULL default -1,
weight int NOT NULL default '0',
info text,
PRIMARY KEY (filename)
)");
db_query("CREATE INDEX {system}_weight_idx ON {system} (weight)");
......@@ -1081,8 +1083,8 @@ function system_install() {
break;
}
db_query("INSERT INTO {system} (filename, name, type, description, status, throttle, bootstrap, schema_version) VALUES ('themes/engines/phptemplate/phptemplate.engine', 'phptemplate', 'theme_engine', '', 1, 0, 0, 0)");
db_query("INSERT INTO {system} (filename, name, type, description, status, throttle, bootstrap, schema_version) VALUES ('themes/garland/page.tpl.php', 'garland', 'theme', 'themes/engines/phptemplate/phptemplate.engine', 1, 0, 0, 0)");
db_query("INSERT INTO {system} (filename, name, type, owner, status, throttle, bootstrap, schema_version) VALUES ('themes/engines/phptemplate/phptemplate.engine', 'phptemplate', 'theme_engine', '', 1, 0, 0, 0)");
db_query("INSERT INTO {system} (filename, name, type, owner, status, throttle, bootstrap, schema_version, info) VALUES ('themes/garland/page.tpl.php', 'garland', 'theme', 'themes/engines/phptemplate/phptemplate.engine', 1, 0, 0, 0, '%s')", serialize(drupal_parse_info_file('themes/garland/garland.info') + system_theme_default()));
db_query("INSERT INTO {users} (uid,name,mail) VALUES(0,'','')");
......@@ -3708,6 +3710,32 @@ function system_update_6007() {
return $ret;
}
/**
* Add info files to themes.
*/
function system_update_6008() {
$ret = array();
// Alter system table.
switch ($GLOBALS['db_type']) {
case 'pgsql':
db_add_column($ret, 'system', 'info', 'text');
db_change_column($ret, 'system', 'description', 'owner', 'varchar(255)', array('not null' => TRUE, 'default' => "''"));
break;
case 'mysql':
case 'mysqli':
$ret[] = update_sql("ALTER TABLE {system} ADD info longtext");
$ret[] = update_sql("ALTER TABLE {system} CHANGE description owner varchar(255) NOT NULL default ''");
break;
}
// Rebuild system table contents.
module_rebuild_cache();
system_theme_data();
return $ret;
}
/**
* @} End of "defgroup updates-5.x-to-6.x"
* The next series of updates should start at 7000.
......
......@@ -204,7 +204,7 @@ function system_menu() {
foreach (list_themes() as $theme) {
if ($theme->status) {
$items['admin/build/themes/settings/'. $theme->name] = array(
'title' => $theme->name,
'title' => $theme->info['name'],
'page arguments' => array('system_theme_settings', $theme->name),
'type' => MENU_LOCAL_TASK,
);
......@@ -454,7 +454,7 @@ function system_admin_theme_settings() {
ksort($themes);
$options[0] = t('System default');
foreach ($themes as $theme) {
$options[$theme->name] = $theme->name;
$options[$theme->name] = $theme->info['name'];
}
$form['admin_theme'] = array(
......@@ -936,6 +936,30 @@ function system_get_files_database(&$files, $type) {
}
}
function system_theme_default() {
// Prepare defaults for themes.
return array(
'regions' => array(
'left' => 'Left sidebar',
'right' => 'Right sidebar',
'content' => 'Content',
'header' => 'Header',
'footer' => 'Footer',
),
'description' => '',
'features' => array(
'comment_user_picture',
'favicon',
'mission',
'logo',
'name',
'node_user_picture',
'search',
'slogan'
),
);
}
/**
* Collect data about all currently available themes
*/
......@@ -1004,10 +1028,16 @@ function system_theme_data() {
// Extract current files from database.
system_get_files_database($themes, 'theme');
$defaults = system_theme_default();
// Read info files for the owner
foreach (array_keys($themes) as $key) {
$themes[$key]->info = drupal_parse_info_file(dirname($themes[$key]->filename) .'/'. $themes[$key]->name .'.info') + $defaults;
}
db_query("DELETE FROM {system} WHERE type = 'theme'");
foreach ($themes as $theme) {
db_query("INSERT INTO {system} (name, description, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", $theme->name, $theme->owner, 'theme', $theme->filename, isset($theme->status) ? $theme->status : 0, 0, 0);
db_query("INSERT INTO {system} (name, owner, info, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', '%s', %d, %d, %d)", $theme->name, $theme->owner, serialize($theme->info), 'theme', $theme->filename, isset($theme->status) ? $theme->status : 0, 0, 0);
}
return $themes;
......@@ -1025,32 +1055,8 @@ function system_region_list($theme_key) {
static $list = array();
if (!array_key_exists($theme_key, $list)) {
$theme = db_fetch_object(db_query("SELECT * FROM {system} WHERE type = 'theme' AND name = '%s'", $theme_key));
// Stylesheets can't have regions; use its theme.
if (strpos($theme->filename, '.css')) {
return system_region_list(basename(dirname($theme->description)));
}
// If this is a custom theme, load it in before moving on.
if (file_exists($file = dirname($theme->filename) .'/'. $theme_key .'.theme')) {
include_once "./$file";
}
$regions = array();
// This theme has defined its own regions.
if (function_exists($theme_key .'_regions')) {
$regions = call_user_func($theme_key .'_regions');
}
// File is an engine; include its regions.
else if (strpos($theme->description, '.engine')) {
include_once './'. $theme->description;
$theme_engine = basename($theme->description, '.engine');
$regions = function_exists($theme_engine .'_regions') ? call_user_func($theme_engine .'_regions') : array();
}
$list[$theme_key] = $regions;
$info = unserialize(db_result(db_query("SELECT info FROM {system} WHERE type = 'theme' AND name = '%s'", $theme_key)));
$list[$theme_key] = array_map('t', $info['regions']);
}
return $list[$theme_key];
......@@ -1176,22 +1182,22 @@ function system_themes_form() {
ksort($themes);
$status = array();
foreach ($themes as $info) {
$info->screenshot = dirname($info->filename) .'/screenshot.png';
$screenshot = file_exists($info->screenshot) ? theme('image', $info->screenshot, t('Screenshot for %theme theme', array('%theme' => $info->name)), '', array('class' => 'screenshot'), FALSE) : t('no screenshot');
$form[$info->name]['screenshot'] = array('#value' => $screenshot);
$form[$info->name]['description'] = array('#type' => 'item', '#title' => $info->name, '#value' => dirname($info->filename));
$options[$info->name] = '';
if (!empty($info->status)) {
$status[] = $info->name;
foreach ($themes as $theme) {
$theme->screenshot = dirname($theme->filename) .'/screenshot.png';
$screenshot = file_exists($theme->screenshot) ? theme('image', $theme->screenshot, t('Screenshot for %theme theme', array('%theme' => $theme->info['name'])), '', array('class' => 'screenshot'), FALSE) : t('no screenshot');
$form[$theme->name]['screenshot'] = array('#value' => $screenshot);
$form[$theme->name]['info'] = array('#type' => 'value', '#value' => $theme->info);
$options[$theme->name] = '';
if (!empty($theme->status)) {
$status[] = $theme->name;
}
if (!empty($info->status) && (function_exists($info->prefix .'_settings') || function_exists($info->prefix .'_features'))) {
$form[$info->name]['operations'] = array('#value' => l(t('configure'), 'admin/build/themes/settings/'. $info->name) );
if (!empty($theme->status)) {
$form[$theme->name]['operations'] = array('#value' => l(t('configure'), 'admin/build/themes/settings/'. $theme->name) );
}
else {
// Dummy element for drupal_render. Cleaner than adding a check in the theme function.
$form[$info->name]['operations'] = array();
$form[$theme->name]['operations'] = array();
}
}
......@@ -1205,15 +1211,25 @@ function system_themes_form() {
function theme_system_themes_form($form) {
foreach (element_children($form) as $key) {
// Only look for themes
if (!isset($form[$key]['info'])) {
continue;
}
// Fetch info
$info = $form[$key]['info']['#value'];
// Style theme info
$theme = '<div class="theme-info"><h2>'. $info['name'] .'</h2><div class="description">'. $info['description'] .'</div></div>';
// Build rows
$row = array();
if (isset($form[$key]['description']) && is_array($form[$key]['description'])) {
$row[] = drupal_render($form[$key]['screenshot']);
$row[] = drupal_render($form[$key]['description']);
$row[] = array('data' => drupal_render($form['status'][$key]), 'align' => 'center');
if ($form['theme_default']) {
$row[] = array('data' => drupal_render($form['theme_default'][$key]), 'align' => 'center');
$row[] = array('data' => drupal_render($form[$key]['operations']), 'align' => 'center');
}
$row[] = drupal_render($form[$key]['screenshot']);
$row[] = $theme;
$row[] = array('data' => drupal_render($form['status'][$key]), 'align' => 'center');
if ($form['theme_default']) {
$row[] = array('data' => drupal_render($form['theme_default'][$key]), 'align' => 'center');
$row[] = array('data' => drupal_render($form[$key]['operations']), 'align' => 'center');
}
$rows[] = $row;
}
......@@ -1292,10 +1308,6 @@ function system_modules($form_values = NULL) {
$throttle = array();
// Traverse the files retrieved and build the form.
foreach ($files as $filename => $file) {
$file->info += array(
'dependents' => array(),
'version' => NULL,
);
$form['name'][$filename] = array('#value' => $file->info['name']);
$form['version'][$filename] = array('#value' => $file->info['version']);
$form['description'][$filename] = array('#value' => t($file->info['description']));
......@@ -1636,11 +1648,11 @@ function system_modules_uninstall($form_values = NULL) {
$form = array();
// Pull all disabled modules from the system table.
$disabled_modules = db_query("SELECT name, filename FROM {system} WHERE type = 'module' AND status = 0 AND schema_version > %d ORDER BY name", SCHEMA_UNINSTALLED);
$disabled_modules = db_query("SELECT name, filename, info FROM {system} WHERE type = 'module' AND status = 0 AND schema_version > %d ORDER BY name", SCHEMA_UNINSTALLED);
while ($module = db_fetch_object($disabled_modules)) {
// Grab the .info file and set name and description.
$info = _module_parse_info_file(dirname($module->filename) .'/'. $module->name .'.info');
// Grab the module info
$info = unserialize($module->info);
// Load the .install file, and check for an uninstall hook.
// If the hook exists, the module can be uninstalled.
......@@ -1968,7 +1980,7 @@ function system_theme_settings($key = '') {
$settings = theme_get_settings($key);
$var = str_replace('/', '_', 'theme_'. $key .'_settings');
$themes = system_theme_data();
$features = function_exists($themes[$key]->prefix .'_features') ? call_user_func($themes[$key]->prefix .'_features') : array();
$features = $themes[$key]->info['features'];
}
else {
$settings = theme_get_settings('');
......@@ -2008,14 +2020,14 @@ function system_theme_settings($key = '') {
// Toggle settings
$toggles = array(
'toggle_logo' => t('Logo'),
'toggle_name' => t('Site name'),
'toggle_slogan' => t('Site slogan'),
'toggle_mission' => t('Mission statement'),
'toggle_node_user_picture' => t('User pictures in posts'),
'toggle_comment_user_picture' => t('User pictures in comments'),
'toggle_search' => t('Search box'),
'toggle_favicon' => t('Shortcut icon')
'logo' => t('Logo'),
'name' => t('Site name'),
'slogan' => t('Site slogan'),
'mission' => t('Mission statement'),
'node_user_picture' => t('User pictures in posts'),
'comment_user_picture' => t('User pictures in comments'),
'search' => t('Search box'),
'favicon' => t('Shortcut icon')
);
// Some features are not always available
......@@ -2036,9 +2048,9 @@ function system_theme_settings($key = '') {
foreach ($toggles as $name => $title) {
if ((!$key) || in_array($name, $features)) {
// disable search box if search.module is disabled
$form['theme_settings'][$name] = array('#type' => 'checkbox', '#title' => $title, '#default_value' => $settings[$name]);
$form['theme_settings']['toggle_'. $name] = array('#type' => 'checkbox', '#title' => $title, '#default_value' => $settings['toggle_'. $name]);
if (isset($disabled[$name])) {
$form['theme_settings'][$name]['#disabled'] = TRUE;
$form['theme_settings']['toggle_'. $name]['#disabled'] = TRUE;