Commit 5d759ccb authored by Dries's avatar Dries

- Patch #5942 by jhriggs and Adrian:

    + added support for multi-site configurations.
    + tidied up some old cruft and added code comments.
parent 062a8abd
......@@ -14,6 +14,8 @@ Drupal x.x.x, xxxx-xx-xx
- usability:
* refactored the throttle module configuration.
* added a 'add child page' link to book pages.
- multi-site configuration:
* made it possible to run multiple sites from a single code base.
- performance:
* improved performance of the forum topics block.
* improved performance of the tracker module.
......
......@@ -103,15 +103,8 @@ INSTALLATION
4. CONNECTING DRUPAL
Drupal server options are specified in includes/conf.php.
Drupal allows for multiple virtual host installations; to configure
a virtual server host, you can create the configuration file
includes/www.example.com.php
where www.example.com is your website's URL.
The default configuration can be found in the
'sites/default/settings.php' file within your Drupal installation.
Before you can run Drupal, you must set the database URL and the
base URL to the web site. Open the configuration file and edit the
$db_url line to match the database defined in the previous steps:
......@@ -122,6 +115,61 @@ INSTALLATION
$base_url = "http://www.example.com";
In addition, a single Drupal installation can host several
Drupal-powered sites, each with its own individual configuration.
If you don't need to run multiple Drupal sites, you can skip to the
next section.
Additional site configurations are created in subdirectories within
the 'sites' directory. Each site subdirectory must have a
'settings.php' file which specifies the configuration settings. The
easiest way to create additional sites is to copy the 'default'
directory and modify the 'settings.php' file as appropriate. The new
directory name is constructed from the site's URL. The
configuration for www.example.com could be in
'sites/example.com/settings.php' (note that 'www.' should be omitted
if users can access your site at http://example.com/).
Sites do not each have to have a different domain. You can use
subdomains and subdirectories for Drupal sites also. For example,
example.com, sub.example.com, and sub.example.com/site3 can all be
defined as independent Drupal sites. The setup for a configuration
such as this would look like the following:
sites/default/settings.php
sites/example.com/settings.php
sites/sub.example.com/settings.php
sites/sub.example.com.site3/settings.php
When searching for a site configuration (for example
www.sub.example.com/site3), Drupal will search for configuration
files in the following order, using the first configuration file it
finds:
sites/www.sub.example.com.site3/settings.php
sites/sub.example.com.site3/settings.php
sites/example.com.site3/settings.php
sites/www.sub.example.com/settings.php
sites/sub.example.com/settings.php
sites/example.com/settings.php
sites/default/settings.php
Each site configuration can have its own site-specific modules and
themes that will be made available in addition to those installed
in the standard 'modules' and 'themes' directories. To use
site-specific modules or themes, simply create a 'modules' or
'themes' directory within the site configuration directory. For
example, if sub.example.dom has a custom theme and a custom module
that should not be accessible to other sites, the setup would look
like this:
sites/sub.example.com/:
settings.php
themes/:
custom_theme
modules/:
custom_module
NOTE: for more information about multiple virtual hosts or the
configuration settings, consult the Drupal handbook at drupal.org.
......
......@@ -767,11 +767,15 @@ CREATE TABLE watchdog (
INSERT INTO system VALUES ('modules/admin.module','admin','module','',1,0,0);
INSERT INTO system VALUES ('modules/block.module','block','module','',1,0,0);
INSERT INTO system VALUES ('modules/comment.module','comment','module','',1,0,0);
INSERT INTO system VALUES ('modules/filter.module','filter','module','',1,0,0);
INSERT INTO system VALUES ('modules/help.module','help','module','',1,0,0);
INSERT INTO system VALUES ('modules/node.module','node','module','',1,0,0);
INSERT INTO system VALUES ('modules/page.module','page','module','',1,0,0);
INSERT INTO system VALUES ('modules/story.module','story','module','',1,0,0);
INSERT INTO system VALUES ('modules/system.module','system','module','',1,0,0);
INSERT INTO system VALUES ('modules/taxonomy.module','taxonomy','module','',1,0,0);
INSERT INTO system VALUES ('modules/user.module','user','module','',1,0,0);
INSERT INTO system VALUES ('modules/watchdog.module','watchdog','module','',1,0,0);
INSERT INTO system VALUES ('themes/bluemarine/xtemplate.xtmpl','bluemarine','theme','themes/engines/xtemplate/xtemplate.engine',1,0,0);
INSERT INTO system VALUES ('themes/engines/xtemplate/xtemplate.engine','xtemplate','theme_engine','',1,0,0);
INSERT INTO users (uid, name, mail) VALUES ('0', '', '');
......
......@@ -751,11 +751,15 @@ CREATE TABLE watchdog (
INSERT INTO system VALUES ('modules/admin.module','admin','module','',1,0,0);
INSERT INTO system VALUES ('modules/block.module','block','module','',1,0,0);
INSERT INTO system VALUES ('modules/comment.module','comment','module','',1,0,0);
INSERT INTO system VALUES ('modules/filter.module','filter','module','',1,0,0);
INSERT INTO system VALUES ('modules/help.module','help','module','',1,0,0);
INSERT INTO system VALUES ('modules/node.module','node','module','',1,0,0);
INSERT INTO system VALUES ('modules/page.module','page','module','',1,0,0);
INSERT INTO system VALUES ('modules/story.module','story','module','',1,0,0);
INSERT INTO system VALUES ('modules/system.module','system','module','',1,0,0);
INSERT INTO system VALUES ('modules/taxonomy.module','taxonomy','module','',1,0,0);
INSERT INTO system VALUES ('modules/user.module','user','module','',1,0,0);
INSERT INTO system VALUES ('modules/watchdog.module','watchdog','module','',1,0,0);
INSERT INTO system VALUES ('themes/bluemarine/xtemplate.xtmpl','bluemarine','theme','themes/engines/xtemplate/xtemplate.engine',1,0,0);
INSERT INTO system VALUES ('themes/engines/xtemplate/xtemplate.engine','xtemplate','theme_engine','',1,0,0);
......
......@@ -12,25 +12,41 @@
/**
* Locate the appropriate configuration file.
*
* Try finding a matching configuration file by stripping the website's
* URI from left to right. If no configuration file is found, return the
* default value, "conf".
* Try finding a matching configuration directory by stripping the
* website's hostname from left to right and pathname from right to
* left. If no configuration file is found, return a default value
* '$confdir/default'. Example for a ficticious site installed at
* http://www.drupal.org/test:
*
* 1. www.drupal.org.test
* 2. drupal.org.test
* 3. www.drupal.org
* 4. drupal.org
* 5. default
*/
function conf_init() {
$uri = $_SERVER['PHP_SELF'];
static $conf = '';
$file = strtolower(strtr($_SERVER['HTTP_HOST'] . substr($uri, 0, strrpos($uri, '/')), '/:', '..'));
if ($conf) {
return $conf;
}
while (strlen($file) > 4) {
if (file_exists('includes/'. $file .'.php')) {
return $file;
}
else {
$file = substr($file, strpos($file, '.') + 1);
$uri = explode('/', $_SERVER['PHP_SELF']);
$server = explode('.', $_SERVER['HTTP_HOST']);
$confdir = 'sites';
for ($i = count($uri) - 1; $i > 0; $i--) {
for ($j = count($server); $j > 0; $j--) {
$dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i));
if (file_exists("$confdir/$dir/settings.php")) {
$conf = "$confdir/$dir";
return $conf;
}
}
}
return 'conf';
print "$confdir/default/settings.php<br />";
$conf = "$confdir/default";
return $conf;
}
/**
......@@ -442,7 +458,7 @@ function drupal_get_messages() {
unset($conf);
$config = conf_init();
include_once "includes/$config.php";
include_once "$config/settings.php";
include_once 'includes/database.inc';
include_once 'includes/session.inc';
include_once 'includes/module.inc';
......
......@@ -1836,6 +1836,100 @@ function drupal_eval($code) {
return $output;
}
/**
* Returns and optionally sets the filename for a system item (module,
* theme, etc.). The filename, whether provided, cached, or retrieved
* from the database, is only returned if the file exists.
*
* @param $type
* The type of the item (i.e. theme, theme_engine, module).
* @param $name
* The name of the item for which the filename is requested.
* @param $filename
* The filename of the item if it is to be set explicitly rather
* than by consulting the database.
*
* @return
* The filename of the requested item.
*/
function drupal_get_filename($type, $name, $filename = NULL) {
static $files = array();
if (!$files[$type]) {
$files[$type] = array();
}
if ($filename && file_exists($filename)) {
$files[$type][$name] = $filename;
}
elseif ($files[$type][$name]) {
// nothing
}
elseif (($file = db_result(db_query("SELECT filename FROM {system} WHERE name = '%s' AND type = '%s'", $name, $type))) && file_exists($file)) {
$files[$type][$name] = $file;
}
else {
$config = conf_init();
$dir = (($type == 'theme_engine') ? 'themes/engines' : "${type}s");
$file = "$name.$type";
foreach (array("$config/$dir/$file", "$config/$dir/$name/$file", "$dir/$file", "$dir/$name/$file") as $file) {
if (file_exists($file)) {
$files[$type][$name] = $file;
break;
}
}
}
return $files[$type][$name];
}
/**
* Returns the path to a system item (module, theme, etc.).
*
* @param $type
* The type of the item (i.e. theme, theme_engine, module).
* @param $name
* The name of the item for which the path is requested.
*
* @return
* The path to the requested item.
*/
function drupal_get_path($type, $name) {
return dirname(drupal_get_filename($type, $name));
}
/**
* Includes a file with the provided type and name. This prevents
* including a theme, engine, module, etc., more than once.
*
* @param $type
* The type of item to load (i.e. theme, theme_engine, module).
* @param $name
* The name of the item to load.
*
* @return
* TRUE if the item is loaded or has already been loaded.
*/
function drupal_load($type, $name) {
static $files = array();
if ($files[$type][$name]) {
return TRUE;
}
$filename = drupal_get_filename($type, $name);
if ($filename) {
include_once($filename);
$files[$type][$name] = TRUE;
return TRUE;
}
return FALSE;
}
include_once 'includes/theme.inc';
include_once 'includes/pager.inc';
include_once 'includes/menu.inc';
......
......@@ -469,39 +469,59 @@ function file_download() {
}
/**
* Finds all files that match a given mask in a given directory.
* Finds all files that match a given mask in a given
* directory.
*
* @param $dir Directory to scan
* @param $mask Regular expression to filter out files. Only filenames that
* match the mask will be returned.
* @param $nomask Array of filenames which should never be returned regardless
* if they match the $mask
* @param $callback Function to call for qualifying file.
* Set to 0 or false if you do not want callbacks.
* @param $recurse When true directory scan will recurse the entire tree starting at $dir
* @return Array of qualifying files
* @param $dir
* The base directory for the scan.
* @param $mask
* The regular expression of the files to find.
* @param $nomask
* An array of files/directories to ignore.
* @param $callback
* The callback function to call for each match.
* @param $recurse
* When TRUE, the directory scan will recurse the entire tree
* starting at the provided directory.
* @param $key
* The key to be used for the returned array of files. Possible
* values are "filename", for the path starting with $dir,
* "basename", for the basename of the file, and "name" for the name
* of the file without an extension.
*
* @return
* An associative array (keyed on the provided key) of objects with
* "path", "basename", and "name" members corresponding to the
* matching files.
*/
function file_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse = TRUE) {
function file_scan_directory($dir, $mask, $nomask = array('.', '..', 'CVS'), $callback = 0, $recurse = TRUE, $key = 'filename') {
$key = (in_array($key, array('filename', 'basename', 'name')) ? $key : 'filename');
$files = array();
if (is_dir($dir) && $handle = opendir($dir)) {
while ($file = readdir($handle)) {
if (!in_array($file, $nomask)) {
if (is_dir("$dir/$file") && $recurse) {
$files = array_merge($files, file_scan_directory("$dir/$file", $mask, $nomask, $callback));
$files = array_merge($files, file_scan_directory("$dir/$file", $mask, $nomask, $callback, $recurse, $key));
}
elseif (ereg($mask, $file)) {
$name = basename($file);
$files["$dir/$file"] = new stdClass();
$files["$dir/$file"]->filename = "$dir/$file";
$files["$dir/$file"]->name = substr($name, 0, strrpos($name, '.'));
$filename = "$dir/$file";
$basename = basename($file);
$name = substr($basename, 0, strrpos($basename, '.'));
$files[$$key] = new stdClass();
$files[$$key]->filename = $filename;
$files[$$key]->basename = $basename;
$files[$$key]->name = $name;
if ($callback) {
$callback("$dir/$file");
$callback($filename);
}
}
}
}
closedir($handle);
}
return $files;
}
......
......@@ -61,8 +61,8 @@ function module_list($refresh = FALSE, $bootstrap = FALSE) {
// variables, since throttle.module may not be loaded yet.
$throttle = ($module->throttle && variable_get('throttle_level', 0) > 0);
if (!$throttle) {
drupal_get_filename('module', $module->name, $module->filename);
$list[$module->name] = $module->name;
module_set_filename($module->name, $module->filename);
}
}
}
......@@ -71,86 +71,20 @@ function module_list($refresh = FALSE, $bootstrap = FALSE) {
return $list;
}
/**
* Set the filename of a module, for future loading through module_load()
*
* @param $module
* Name of the module which to specify the filename of.
* @param $pa
* Filename of the module named $module.
* @return
* Filename of module, if no $path has been specified.
*/
function module_set_filename($module, $path = null) {
static $list;
if ($path) {
$list[$module] = $path;
}
else {
return $list[$module] ? $list[$module] : "modules/$module.module";
}
}
/**
* Retrieve the filename of a module
*
* @param $module
* Name of the module which to retrieve the filename of.
* @return
* Filename of module.
*/
function module_get_filename($module) {
return module_set_filename($module);
}
/**
* Retrieve the path of a module
*
* @param $module
* Name of the module which to retrieve the path of.
* @return
* Path of module.
*/
function module_get_path($module) {
return dirname(module_get_filename($module));
}
/**
* Load a module into Drupal, but check first whether a module by the same name
* has been loaded, and that the filename being included exists.
* @param $module
* The name of the module to be loaded.
* @return
* TRUE if the load was successful.
*/
function module_load($module) {
static $loaded = array();
if (!$loaded[$module]) {
$filename = module_get_filename($module);
if (file_exists($filename)) {
include_once($filename);
$loaded[$module] = $filename;
return true;
}
}
return false;
}
/**
* Load all the modules that have been enabled in the system table.
*
* @return
* TRUE if all modules were loaded successfully
* TRUE if all modules were loaded successfully.
*/
function module_load_all() {
$list = module_list();
$status = true;
$status = TRUE;
foreach ($list as $module) {
$status &= module_load($module);
$status = (drupal_load('module', $module) && $status);
}
return $status;
}
......
......@@ -270,178 +270,149 @@ function system_view_general() {
return $output;
}
/**
* Inventory theme engines and insert entries for them into the system table
*/
function system_theme_engine_inventory($directory) {
$engines = array();
// Remove all theme engines from the system table
db_query('DELETE FROM {system} WHERE type = \'%s\'', 'theme_engine');
// Find theme engines in the directory and insert into database
$files = file_scan_directory($directory. '/engines', '\.engine$');
foreach ($files as $filename => $file) {
module_set_filename($file->name, $filename);
module_load($file->name);
$info = new StdClass();
$info->name = $file->name;
$info->filename = $file->filename;
$engines[$info->name] = $info;
db_query('INSERT INTO {system} (name, type, filename, status, throttle, bootstrap) VALUES (\'%s\', \'%s\', \'%s\', %d, %d, %d)', $info->name, 'theme_engine', $filename, 1, 0, 0);
}
return $engines;
}
/**
* Retrieves an array of a particular type of files (specified by $type) in a particular $directory
* and their current status in the system table.
*/
function system_get_files($search, $type, $directory) {
// Find files in the directory.
$files = file_scan_directory($directory, $search);
return $files;
}
/**
* Retrieves the current status of an array of files in the system table.
*/
function system_get_files_database(&$files, $type) {
// Extract current files from database.
$result = db_query('SELECT filename, type, status, throttle FROM {system} WHERE type = \'%s\'', $type);
$result = db_query("SELECT name, type, status, throttle FROM {system} WHERE type = '%s'", $type);
while ($file = db_fetch_object($result)) {
if (is_object($files[$file->filename])) {
if (is_object($files[$file->name])) {
foreach ($file as $key => $value) {
$files[$file->filename]->$key = $value;
if (!$files[$file->name]->$key) {
$files[$file->name]->$key = $value;
}
}
}
}
}
/**
* Obtains information about each theme in the $files array
* Also updates the system table
*/
function system_obtain_theme_info($files, $directory) {
foreach ($files as $filename => $file) {
$info = new StdClass();
if ($file->theme) {
// file is a style
$info->description = $file->theme;
$info->style = TRUE;
}
if (strpos($filename, '.theme')) {
// file is a theme
module_set_filename($file->name, $filename);
module_load($file->name);
$info->description = '';
$info->prefix = basename($filename, '.theme');
}
elseif ($info->style && !$file->engine) {
$info->prefix = $info->description;
}
else {
// file is a template
$info->description = $info->style ? $info->description : $file->engine;
$info->template = TRUE;
$info->prefix = basename($file->engine, '.engine');
}
$info->filename = $filename;
$info->path = pathinfo($info->filename);
$info->name = str_replace(array($directory .'/'), '', $info->path['dirname']);
$info->shortname = basename($info->name);
$info->screenshot = dirname($info->filename) .'/screenshot.png';
$info->status = $file->status;
$themes[$info->name] = $info;
// Update the contents of the system table:
db_query('DELETE FROM {system} WHERE filename = \'%s\' AND type = \'%s\'', $info->filename, 'theme');
db_query('INSERT INTO {system} (name, description, type, filename, status, throttle, bootstrap) VALUES (\'%s\', \'%s\', \'%s\', \'%s\', %d, %d, %d)', $info->name, $info->description, 'theme', $info->filename, $info->status, 0, 0);
}
return $themes;
}
/**
* Collect data about all currently available themes
*/
function system_theme_data($directory) {
function system_theme_data() {
// Find themes
$themes = system_listing('\.theme$', 'themes');
// Find theme engines
$engines = system_theme_engine_inventory($directory);
$engines = system_listing('\.engine$', 'themes/engines');
// can't iterate over array itself as it uses a copy of the array items
foreach (array_keys($themes) as $key) {
drupal_get_filename('theme', $themes[$key]->name, $themes[$key]->filename);
drupal_load('theme', $themes[$key]->name);
$themes[$key]->description = dirname($themes[$key]->filename);
$themes[$key]->owner = $themes[$key]->filename;
$themes[$key]->prefix = $key;
}
// Get current list of themes and their present status in the system table.
$files = system_get_files('\.theme$', 'theme', $directory);
// Remove all theme engines from the system table
db_query("DELETE FROM {system} WHERE type = 'theme_engine'");
// Add templates to the site listing
foreach ($engines as $engine) {
foreach (call_user_func($engine->name .'_templates', $directory) as $template) {
$template_files[$template->filename] = $template;
$template_files[$template->filename]->engine = $engine->filename;
foreach ($files as $file) {
// do not double-insert templates with theme files in their directory
if (dirname($template->filename) == dirname($file->filename)) {
unset($template_files[$template->filename]);
}
}
drupal_get_filename('theme_engine', $engine->name, $engine->filename);
drupal_load('theme_engine', $engine->name);
db_query("INSERT INTO {system} (name, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', %d, %d, %d)", $engine->name, 'theme_engine', $engine->filename, 1, 0, 0);
foreach (call_user_func($engine->name . '_templates') as $template) {
$template->template = TRUE;
$template->name = basename(dirname($template->filename));
$template->basename = $template->name;
$template->description = dirname($template->filename);
$template->owner = $engine->filename;
$template->prefix = $engine->name;
$themes[$template->name] = $template;
}
}
$files = array_merge($files, $template_files);
// Find styles in each theme's directory.
foreach ($files as $file) {
foreach (system_get_files("style.css$", 'theme', dirname($file->filename)) as $style) {
foreach ($themes as $theme) {
foreach (file_scan_directory(dirname($theme->filename), 'style.css$') as $style) {
// do not double-insert themes with css files in their directory
if (dirname($style->filename) != dirname($file->filename)) {
$style_files[$style->filename] = $style;
$path = pathinfo($file->filename);
$style_files[$style->filename]->theme = str_replace(array($directory .'/'), '', $path['dirname']);
if ($file->engine) {
$style_files[$style->filename]->engine = $file->engine;
}
if (dirname($style->filename) != dirname($theme->filename)) {
$style->style = TRUE;
$style->name = basename(dirname($style->filename));
$style->description = dirname($style->filename);
$style->owner = $theme->filename;
$style->prefix = $theme->name;
$themes[$style->name] = $style;
}
}
}
$files = array_merge($files, $style_files);
// Extract current files from database.
system_get_files_database($files, 'theme');
system_get_files_database($themes, 'theme');
db_query("DELETE FROM {system} WHERE type = 'theme'");
// Build an array of information about each theme for use in displaying the selection table.
return system_obtain_theme_info($files, $directory);
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, $theme->status, 0, 0);
}
return $themes;
}
/**
* Returns an array of files objects of the given type from both the
* site-wide directory (i.e. modules/) and site-specific directory
* (i.e. sites/somesite/modules/). The returned array will be keyed
* using the key specified (name, basename, filename). Using name or
* basename will cause site-specific files to shadow files in the
* default directories. That is, if a file with the same name appears
* in both location, only the site-specific version will be included.
*
* @param $mask
* The regular expression of the files to find.
* @param $directory
* The subdirectory name in which the files are found. For example,
* 'modules' will search in both modules/ and
* sites/somesite/modules/.
* @param $key
* The key to be passed to file_scan_directory().
*
* @return