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) ...@@ -20,6 +20,7 @@ Drupal 6.0, xxxx-xx-xx (development version)
* Language detection based on parts of the URL. * Language detection based on parts of the URL.
* Browser based language detection. * Browser based language detection.
- Language dependent path aliases. - Language dependent path aliases.
- Added .info files to themes and made it easier to specify regions and features.
Drupal 5.0, 2007-01-15 Drupal 5.0, 2007-01-15
---------------------- ----------------------
......
...@@ -2482,3 +2482,114 @@ function drupal_common_themes() { ...@@ -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) { ...@@ -311,8 +311,7 @@ function drupal_install_profile($profile, $module_list) {
module_invoke('system', 'install'); module_invoke('system', 'install');
$system_versions = drupal_get_schema_versions('system'); $system_versions = drupal_get_schema_versions('system');
$system_version = $system_versions ? max($system_versions) : SCHEMA_INSTALLED; $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 // Now that we've installed things properly, bootstrap the full Drupal environment
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
......
...@@ -102,16 +102,27 @@ function module_rebuild_cache() { ...@@ -102,16 +102,27 @@ function module_rebuild_cache() {
ksort($files); ksort($files);
// Set defaults for module info
$defaults = array(
'dependencies' => array(),
'dependents' => array(),
'description' => '',
'version' => NULL
);
foreach ($files as $filename => $file) { 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. // Skip modules that don't provide info.
if (empty($file->info)) { if (empty($file->info)) {
unset($files[$filename]); unset($files[$filename]);
continue; 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; $bootstrap = 0;
foreach (bootstrap_hooks() as $hook) { foreach (bootstrap_hooks() as $hook) {
if (module_hook($file->name, $hook)) { if (module_hook($file->name, $hook)) {
...@@ -121,15 +132,14 @@ function module_rebuild_cache() { ...@@ -121,15 +132,14 @@ function module_rebuild_cache() {
} }
// Update the contents of the system table: // 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)) { 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 { else {
// This is a new module. // This is a new module.
$files[$filename]->status = 0; $files[$filename]->status = 0;
$files[$filename]->throttle = 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); $files = _module_build_dependents($files);
...@@ -159,53 +169,6 @@ function _module_build_dependents($files) { ...@@ -159,53 +169,6 @@ function _module_build_dependents($files) {
return $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. * Determine whether a given module exists.
* *
......
...@@ -59,7 +59,7 @@ function init_theme() { ...@@ -59,7 +59,7 @@ function init_theme() {
// File is a style; loads its CSS. // File is a style; loads its CSS.
// Set theme to its template/theme // Set theme to its template/theme
drupal_add_css($themes[$theme]->filename, 'theme'); drupal_add_css($themes[$theme]->filename, 'theme');
$theme = basename(dirname($themes[$theme]->description)); $theme = basename(dirname($themes[$theme]->owner));
} }
else { else {
// File is a template/theme // File is a template/theme
...@@ -74,10 +74,10 @@ function init_theme() { ...@@ -74,10 +74,10 @@ function init_theme() {
include_once './'. $themes[$theme]->filename; include_once './'. $themes[$theme]->filename;
_theme_load_registry($theme); _theme_load_registry($theme);
} }
elseif (strpos($themes[$theme]->description, '.engine')) { elseif (strpos($themes[$theme]->owner, '.engine')) {
// file is a template; include its engine // file is a template; include its engine
include_once './'. $themes[$theme]->description; include_once './'. $themes[$theme]->owner;
$theme_engine = basename($themes[$theme]->description, '.engine'); $theme_engine = basename($themes[$theme]->owner, '.engine');
if (function_exists($theme_engine .'_init')) { if (function_exists($theme_engine .'_init')) {
call_user_func($theme_engine .'_init', $themes[$theme]); call_user_func($theme_engine .'_init', $themes[$theme]);
} }
...@@ -210,6 +210,7 @@ function list_themes($refresh = FALSE) { ...@@ -210,6 +210,7 @@ function list_themes($refresh = FALSE) {
$result = db_query("SELECT * FROM {system} WHERE type = 'theme'"); $result = db_query("SELECT * FROM {system} WHERE type = 'theme'");
while ($theme = db_fetch_object($result)) { while ($theme = db_fetch_object($result)) {
if (file_exists($theme->filename)) { if (file_exists($theme->filename)) {
$theme->info = unserialize($theme->info);
$list[$theme->name] = $theme; $list[$theme->name] = $theme;
} }
} }
...@@ -238,6 +239,7 @@ function list_theme_engines($refresh = FALSE) { ...@@ -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"); $result = db_query("SELECT * FROM {system} WHERE type = 'theme_engine' AND status = '1' ORDER BY name");
while ($engine = db_fetch_object($result)) { while ($engine = db_fetch_object($result)) {
if (file_exists($engine->filename)) { if (file_exists($engine->filename)) {
$engine->info = unserialize($engine->info);
$list[$engine->name] = $engine; $list[$engine->name] = $engine;
} }
} }
......
...@@ -100,9 +100,9 @@ function block_menu() { ...@@ -100,9 +100,9 @@ function block_menu() {
$default = variable_get('theme_default', 'garland'); $default = variable_get('theme_default', 'garland');
foreach (list_themes() as $key => $theme) { foreach (list_themes() as $key => $theme) {
$items['admin/build/block/list/'. $key] = array( $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), '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, 'weight' => $key == $default ? -10 : 0,
); );
} }
......
; $Id$ ; $Id$
name = Forum name = Forum
description = Enables threaded discussions about general topics. description = Enables threaded discussions about general topics.
dependencies = taxonomy comment dependencies[] = taxonomy
dependencies[] = comment
package = Core - optional package = Core - optional
version = VERSION version = VERSION
...@@ -96,4 +96,17 @@ table.system-status-report tr.ok th { ...@@ -96,4 +96,17 @@ table.system-status-report tr.ok th {
} }
.theme-settings-bottom { .theme-settings-bottom {
clear: both; 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() { ...@@ -494,12 +494,13 @@ function system_install() {
filename varchar(255) NOT NULL default '', filename varchar(255) NOT NULL default '',
name varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '',
type 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', status int NOT NULL default '0',
throttle tinyint DEFAULT '0' NOT NULL, throttle tinyint DEFAULT '0' NOT NULL,
bootstrap int NOT NULL default '0', bootstrap int NOT NULL default '0',
schema_version smallint NOT NULL default -1, schema_version smallint NOT NULL default -1,
weight int NOT NULL default '0', weight int NOT NULL default '0',
info text,
PRIMARY KEY (filename), PRIMARY KEY (filename),
KEY (weight) KEY (weight)
) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
...@@ -967,12 +968,13 @@ function system_install() { ...@@ -967,12 +968,13 @@ function system_install() {
filename varchar(255) NOT NULL default '', filename varchar(255) NOT NULL default '',
name varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '',
type 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', status int NOT NULL default '0',
throttle smallint DEFAULT '0' NOT NULL, throttle smallint DEFAULT '0' NOT NULL,
bootstrap int NOT NULL default '0', bootstrap int NOT NULL default '0',
schema_version smallint NOT NULL default -1, schema_version smallint NOT NULL default -1,
weight int NOT NULL default '0', weight int NOT NULL default '0',
info text,
PRIMARY KEY (filename) PRIMARY KEY (filename)
)"); )");
db_query("CREATE INDEX {system}_weight_idx ON {system} (weight)"); db_query("CREATE INDEX {system}_weight_idx ON {system} (weight)");
...@@ -1081,8 +1083,8 @@ function system_install() { ...@@ -1081,8 +1083,8 @@ function system_install() {
break; 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, 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, 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, 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,'','')"); db_query("INSERT INTO {users} (uid,name,mail) VALUES(0,'','')");
...@@ -3708,6 +3710,32 @@ function system_update_6007() { ...@@ -3708,6 +3710,32 @@ function system_update_6007() {
return $ret; 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" * @} End of "defgroup updates-5.x-to-6.x"
* The next series of updates should start at 7000. * The next series of updates should start at 7000.
......
...@@ -204,7 +204,7 @@ function system_menu() { ...@@ -204,7 +204,7 @@ function system_menu() {
foreach (list_themes() as $theme) { foreach (list_themes() as $theme) {
if ($theme->status) { if ($theme->status) {
$items['admin/build/themes/settings/'. $theme->name] = array( $items['admin/build/themes/settings/'. $theme->name] = array(
'title' => $theme->name, 'title' => $theme->info['name'],
'page arguments' => array('system_theme_settings', $theme->name), 'page arguments' => array('system_theme_settings', $theme->name),
'type' => MENU_LOCAL_TASK, 'type' => MENU_LOCAL_TASK,
); );
...@@ -454,7 +454,7 @@ function system_admin_theme_settings() { ...@@ -454,7 +454,7 @@ function system_admin_theme_settings() {
ksort($themes); ksort($themes);
$options[0] = t('System default'); $options[0] = t('System default');
foreach ($themes as $theme) { foreach ($themes as $theme) {
$options[$theme->name] = $theme->name; $options[$theme->name] = $theme->info['name'];
} }
$form['admin_theme'] = array( $form['admin_theme'] = array(
...@@ -936,6 +936,30 @@ function system_get_files_database(&$files, $type) { ...@@ -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 * Collect data about all currently available themes
*/ */
...@@ -1004,10 +1028,16 @@ function system_theme_data() { ...@@ -1004,10 +1028,16 @@ function system_theme_data() {
// Extract current files from database. // Extract current files from database.
system_get_files_database($themes, 'theme'); 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'"); db_query("DELETE FROM {system} WHERE type = 'theme'");
foreach ($themes as $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; return $themes;
...@@ -1025,32 +1055,8 @@ function system_region_list($theme_key) { ...@@ -1025,32 +1055,8 @@ function system_region_list($theme_key) {
static $list = array(); static $list = array();
if (!array_key_exists($theme_key, $list)) { if (!array_key_exists($theme_key, $list)) {
$theme = db_fetch_object(db_query("SELECT * FROM {system} WHERE type = 'theme' AND name = '%s'", $theme_key)); $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']);
// 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;
} }
return $list[$theme_key]; return $list[$theme_key];
...@@ -1176,22 +1182,22 @@ function system_themes_form() { ...@@ -1176,22 +1182,22 @@ function system_themes_form() {
ksort($themes); ksort($themes);
$status = array(); $status = array();
foreach ($themes as $info) { foreach ($themes as $theme) {
$info->screenshot = dirname($info->filename) .'/screenshot.png';