| 1 |
<?php |
<?php |
| 2 |
// $Id: multisite_manager.module,v 1.7 2008/02/08 20:25:25 schuyler1d Exp $ |
// $Id: multisite_manager.module,v 1.8 2008/02/08 22:11:51 schuyler1d Exp $ |
| 3 |
|
|
| 4 |
/** |
/** |
| 5 |
* author: Schuyler Duveen |
* author: Schuyler Duveen |
| 6 |
* sponsor: Columbia Univeristy Center for New Media Teaching & Learning |
* sponsor: Columbia Univeristy Center for New Media Teaching & Learning |
| 7 |
* URL: http://drupal.org/project/multisite_manager |
* URL: http://drupal.org/project/multisite_manager |
| 8 |
* License: GPL v2 or any later version (see LICENSE.txt) |
* License: GPL v2 or any later version |
| 9 |
* |
* |
| 10 |
* TODO |
* TODO |
| 11 |
* ---- |
* ---- |
| 12 |
* test postgres |
* test postgres |
| 13 |
* actual 'management' options? |
* actual 'management' options? |
| 14 |
* update.php support |
* update.php support |
| 15 |
|
* document settings.php setup |
| 16 |
|
* configuration to run as different DB user when creating DB and granting privileges |
| 17 |
|
* hook_before_site_install($node); |
| 18 |
|
* hook_after_site_install($node); |
| 19 |
*/ |
*/ |
| 20 |
|
|
| 21 |
/** |
/** |
| 24 |
function multisite_manager_help($section) { |
function multisite_manager_help($section) { |
| 25 |
switch ($section) { |
switch ($section) { |
| 26 |
case 'admin/help#multisite_manager': |
case 'admin/help#multisite_manager': |
| 27 |
return t('Multisite Manager helps create new drupal sites from within a main drupal site. For documentation see http://code.google.com/p/drupal-multisite-manager/'); |
case 'admin/settings/multisite_manager': |
| 28 |
|
return t('Multisite Manager helps create new drupal sites from within a main drupal site. For documentation see <a href="http://drupal.org/project/multisite_manager">http://drupal.org/project/multisite_manager</a>.<p>This module is <b>NOT Plug-n-Play</b>. Before this module will work as desired follow these steps:<ol><li>Configure the Multisite Manager defaults for where new sites will be added to the database.</li><li>Make a special settings.php file in the /sites/ drupal directory. (This '. l( 'tool','admin/settings/multisite_manager/phpsettingfile') . ' will help)</li><li>Change your Apache config, probably in your Drupal .htaccess file.</li><li>For the default setup you need a symlink in your main Drupal directory. After changing to that directory, run <code>$ ln -s . site</code></li><li>Lastly, the DB account that runs the main site must have extra DB permissions to create databases and grant privileges</li></ol></p>'); |
| 29 |
} |
} |
| 30 |
} |
} |
| 31 |
|
|
| 58 |
|
|
| 59 |
// if don't run this site's cron jobs |
// if don't run this site's cron jobs |
| 60 |
if (!$node->run_cron) { |
if (!$node->run_cron) { |
| 61 |
watchdog('cron', t('Skipping cron for %title.', array('%title' => $node->title))); |
watchdog('cron', 'Skipping cron for %title.', array('%title' => $node->title)); |
| 62 |
} |
} |
| 63 |
// if run this site's cron jobs |
// if run this site's cron jobs |
| 64 |
else { |
else { |
| 77 |
* like update, etc might be possible. |
* like update, etc might be possible. |
| 78 |
*/ |
*/ |
| 79 |
|
|
| 80 |
watchdog('cron', t('Running cron for %title at "%url".', array('%title' => $node->title, '%url' => $node_cron_url))); |
watchdog('cron', 'Running cron for %title at "%url".', array('%title' => $node->title, '%url' => $node_cron_url)); |
| 81 |
$result = drupal_http_request($node_cron_url); |
$result = drupal_http_request($node_cron_url); |
| 82 |
|
|
| 83 |
if ($result->error) { |
if ($result->error) { |
| 87 |
'%url' => $node_cron_url, |
'%url' => $node_cron_url, |
| 88 |
)); |
)); |
| 89 |
drupal_set_message($message,'error'); |
drupal_set_message($message,'error'); |
| 90 |
watchdog('cron',$message, WATCHDOG_WARNING); |
watchdog('cron',$message, array(), WATCHDOG_WARNING); |
| 91 |
} // end if error |
} // end if error |
| 92 |
} |
} |
| 93 |
} // end while nodes |
} // end while nodes |
| 121 |
/** |
/** |
| 122 |
* Implementation of hook_menu(). |
* Implementation of hook_menu(). |
| 123 |
*/ |
*/ |
| 124 |
function multisite_manager_menu($may_cache) { |
function multisite_manager_menu() { |
| 125 |
$items = array(); |
$items = array(); |
| 126 |
if ($may_cache) { |
$items['admin/settings/multisite_manager'] = array( |
| 127 |
$items[] = array('path' => 'admin/settings/multisite_manager', |
'title' => t('Multisite Manager settings'), |
| 128 |
'title' => t('Multisite Manager settings'), |
'page callback' => 'drupal_get_form', |
| 129 |
'description' => t('Configure the database and url defaults for new sites.'), |
'page arguments' => array('multisite_manager_admin_settings'), |
| 130 |
'callback' => 'drupal_get_form', |
'access arguments' => array('administer multisite defaults'), |
| 131 |
'callback arguments' => array('multisite_manager_admin_settings'), |
); |
| 132 |
'access' => user_access('administer multisite defaults'), |
$items['admin/settings/multisite_manager/defaults'] = array( |
| 133 |
'type' => MENU_NORMAL_ITEM); |
'title' => t('Defaults'), |
| 134 |
|
'description' => t('Configure the database and url defaults for new sites.'), |
| 135 |
} |
'page callback' => 'drupal_get_form', |
| 136 |
|
'page arguments' => array('multisite_manager_admin_settings'), |
| 137 |
|
'access arguments' => array('administer multisite defaults'), |
| 138 |
|
'type' => MENU_DEFAULT_LOCAL_TASK, |
| 139 |
|
); |
| 140 |
|
$items['admin/settings/multisite_manager/phpsettingfile'] = array( |
| 141 |
|
'title' => t('settings.php help'), |
| 142 |
|
'type' => MENU_LOCAL_TASK, |
| 143 |
|
'description' => t('What to put in the settings.php file'), |
| 144 |
|
'page callback' => 'multisite_manager_admin_file_settingsdotphp', |
| 145 |
|
'access arguments' => array('administer multisite defaults') |
| 146 |
|
); |
| 147 |
return $items; |
return $items; |
| 148 |
} |
} |
| 149 |
|
|
| 467 |
case 'mysql': |
case 'mysql': |
| 468 |
return mysql_error(); |
return mysql_error(); |
| 469 |
case 'mysqli': |
case 'mysqli': |
| 470 |
return mysqli_error(); |
return mysqli_error(db_set_active()); |
| 471 |
case 'pgsql': |
case 'pgsql': |
| 472 |
return pg_last_error(); |
return pg_last_error(); |
| 473 |
} |
} |
| 533 |
drupal_set_message("Database already existed"); |
drupal_set_message("Database already existed"); |
| 534 |
} |
} |
| 535 |
elseif ($errors) { |
elseif ($errors) { |
| 536 |
drupal_set_message("Database error when creating database: $error", 'error'); |
//what's 1004: access denied! |
| 537 |
|
//needs GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES on *.* to 'drupaluser'@'localhost' identified by '*****' with grant option; |
| 538 |
|
drupal_set_message("Database error when creating database: $errors", 'error'); |
| 539 |
return FALSE; |
return FALSE; |
| 540 |
} |
} |
| 541 |
} |
} |
| 596 |
|
|
| 597 |
function multisite_manager_install_site($node) { |
function multisite_manager_install_site($node) { |
| 598 |
/** |
/** |
| 599 |
|
* |
| 600 |
* 1. If it's a different database and/or user from the current |
* 1. If it's a different database and/or user from the current |
| 601 |
* then creates them (assumes db access to do so) |
* then creates them (assumes db access to do so) |
| 602 |
* 2. switches to the new context (db,user,table prefix) |
* 2. switches to the new context (db,user,table prefix) |
| 603 |
* 3. installs drupal profile in new place |
* 3. installs drupal profile in new place |
| 604 |
* 4. switches back to current context |
* 4. switches back to current context |
| 605 |
*/ |
*/ |
| 606 |
global $multisite_manager_installmodules, $db_prefix, $conf; |
global $multisite_manager_installmodules, $db_prefix, $conf, $locale, $user; |
| 607 |
|
|
| 608 |
|
if (!$multisite_manager_installmodules) { |
| 609 |
|
///if this function is run by a script, then _validate() won't have |
| 610 |
|
/// been called |
| 611 |
|
$multisite_manager_installmodules = drupal_verify_profile($node->profile, $locale); |
| 612 |
|
if (!$multisite_manager_installmodules) { |
| 613 |
|
echo "\n" . t("One or more required modules are missing!") . "\n"; |
| 614 |
|
exit(); |
| 615 |
|
} |
| 616 |
|
} |
| 617 |
|
|
| 618 |
|
///get user for admin account |
| 619 |
|
$admin_how = variable_get('multisite_manager_admin_account', 'copy creator'); |
| 620 |
|
switch($admin_how) { |
| 621 |
|
case 'copy admin': |
| 622 |
|
$account = user_load(1); |
| 623 |
|
break; |
| 624 |
|
case 'copy creator': |
| 625 |
|
$account = $user; |
| 626 |
|
} |
| 627 |
|
|
| 628 |
|
#$multisite_manager_installmodules = drupal_verify_profile($node->profile, $locale); |
| 629 |
include_once './includes/install.inc'; |
include_once './includes/install.inc'; |
| 630 |
|
// |
| 631 |
|
|
| 632 |
/** |
/** |
| 633 |
* creates database and user w/ permissions if necessary |
* creates database and user w/ permissions if necessary |
| 656 |
$cur_conf = $conf; //should be an array copy here |
$cur_conf = $conf; //should be an array copy here |
| 657 |
$conf = array(); |
$conf = array(); |
| 658 |
|
|
| 659 |
drupal_install_profile($node->profile, $multisite_manager_installmodules); |
///THEFT (install.php) |
| 660 |
|
drupal_install_system(); |
| 661 |
|
#variable_set('install_profile_modules', array_diff($multisite_manager_installmodules, array('system'))); |
| 662 |
|
|
| 663 |
|
#install_tasks($node->profile, $task); //can't use this |
| 664 |
|
|
| 665 |
|
///THEFT (install.php) |
| 666 |
|
drupal_install_modules($multisite_manager_installmodules); |
| 667 |
|
|
| 668 |
|
/*BEGIN THEFT*/ |
| 669 |
|
//TODO: install locale stuff |
| 670 |
|
/*END THEFT (install.php) */ |
| 671 |
|
|
| 672 |
|
///TASK 'configure' (admin account) |
| 673 |
|
if ($admin_how !== 'do nothing') { |
| 674 |
|
///THEFT (install.php) |
| 675 |
|
$newsite_account = user_load(1); |
| 676 |
|
user_save($newsite_account, array('name' => $account->name, |
| 677 |
|
'mail' => $account->mail, |
| 678 |
|
'roles' => array(), |
| 679 |
|
'status' => 1, |
| 680 |
|
) ); |
| 681 |
|
//no save password raw function :-( |
| 682 |
|
$success = db_query("UPDATE {users} SET pass = '%s' WHERE uid = 1", array($account->pass)); |
| 683 |
|
} |
| 684 |
|
|
| 685 |
|
|
| 686 |
//after install_profile, because otherwise {cache} and {variable} don't exist yet |
//after install_profile, because otherwise {cache} and {variable} don't exist yet |
| 687 |
variable_init(); //soak up anything from {variable} |
variable_init(); //soak up anything from {variable} |
| 688 |
|
|
| 689 |
/* code ripped from install.php:install_complete */ |
/*BEGIN THEFT*/ |
| 690 |
// Store install profile for later use. |
//TODO: call profile tasks |
|
variable_set('install_profile', $node->profile); |
|
| 691 |
|
|
| 692 |
// Show profile finalization info. |
// Show profile finalization info. |
| 693 |
$function = $node->profile.'_profile_final'; |
$function = $node->profile.'_profile_tasks'; |
| 694 |
if (function_exists($function)) { |
if (function_exists($function)) { |
| 695 |
// More steps required |
// More steps required |
| 696 |
//PARANOIA: buggy set_active_db() makes us set this again, just for fun |
//PARANOIA: buggy set_active_db() makes us set this again, just for fun |
| 697 |
|
/*END THEFT*/ |
| 698 |
|
|
| 699 |
$mid_cur_db = _multisite_manager_dbswitch($new_db); |
$mid_cur_db = _multisite_manager_dbswitch($new_db); |
| 700 |
$profile_message = $function(); |
//BUG: $url here will probably mess things up if it's actually used |
| 701 |
} |
$task = 'profile'; ///is passed by reference. we should foreach the tasks |
| 702 |
|
$profile_message = $function($task, _multisite_manager_node_url($node)); |
| 703 |
|
} |
| 704 |
|
|
| 705 |
|
//PARANOIA: buggy set_active_db() makes us set this again, just for fun |
| 706 |
|
$mid_cur_db = _multisite_manager_dbswitch($new_db); |
| 707 |
|
|
| 708 |
|
///TASK 'done' |
| 709 |
|
/*BEGIN THEFT (install.php)*/ |
| 710 |
|
/* code ripped from install.php:install_tasks */ |
| 711 |
|
|
| 712 |
|
// Rebuild menu to get content type links registered by the profile, |
| 713 |
|
// and possibly any other menu items created through the tasks. |
| 714 |
|
menu_rebuild(); |
| 715 |
|
actions_synchronize(); |
| 716 |
|
// Store install profile for later use. |
| 717 |
|
variable_set('install_profile', $node->profile); |
| 718 |
|
/*END THEFT (install.php)*/ |
| 719 |
|
|
| 720 |
if ($new_db['new'] && $new_db['prefix'] != $db_prefix) { |
if ($new_db['new'] && $new_db['prefix'] != $db_prefix) { |
| 721 |
///OK, time to rename all the tables that were just created |
///OK, time to rename all the tables that were just created |
| 733 |
drupal_set_message('Something has gone horribly wrong. The database '. $mid_cur_db['url'] .' is NOT the new one. Therefore, the database was switched mysteriously some time during installation. Thus some features that were intended to be in your new database may have polluted your current database.','error'); |
drupal_set_message('Something has gone horribly wrong. The database '. $mid_cur_db['url'] .' is NOT the new one. Therefore, the database was switched mysteriously some time during installation. Thus some features that were intended to be in your new database may have polluted your current database.','error'); |
| 734 |
} |
} |
| 735 |
|
|
|
///special duty for {sequences} until it doesn't buggily store the prefix; BUG: http://drupal.org/node/168977 |
|
|
///do this BEFORE table rename or we'll have to muck with the db_prefix. |
|
|
if (db_table_exists('sequences')) { |
|
|
@db_query("UPDATE {sequences} SET name = INSERT(name,1,%d,'%s')", strlen($cur_db['prefix']), $new_db['prefix']); |
|
|
} |
|
|
|
|
| 736 |
///Actually RENAME the TABLES |
///Actually RENAME the TABLES |
| 737 |
while($table = @db_fetch_array($result)) { |
while($table = @db_fetch_array($result)) { |
| 738 |
$table_name = array_pop($table); |
$table_name = array_pop($table); |
| 756 |
if (isset($profile_message)) { |
if (isset($profile_message)) { |
| 757 |
drupal_set_message($profile_message); |
drupal_set_message($profile_message); |
| 758 |
} |
} |
| 759 |
|
|
| 760 |
} |
} |
| 761 |
|
|
| 762 |
/** |
/** |
| 862 |
} |
} |
| 863 |
} |
} |
| 864 |
|
|
| 865 |
|
/* |
| 866 |
|
* multisite_manager_admin_file_settingsdotphp |
| 867 |
|
* @return string of PHP code that would be included based on the default settings for link/database name |
| 868 |
|
*/ |
| 869 |
|
function multisite_manager_admin_file_settingsdotphp() { |
| 870 |
|
global $base_url; |
| 871 |
|
$link = variable_get('multisite_manager_link_default', ''); |
| 872 |
|
/// |
| 873 |
|
$fakenode = new stdClass(); |
| 874 |
|
$fakenode->shortname = '". $my_site_base ."'; |
| 875 |
|
$fakenode->db_prefix = variable_get('multisite_manager_dbprefix_default', ''); |
| 876 |
|
$fakenode->db_user = ''; |
| 877 |
|
$fakenode->db_pass = '<b style="color:red">DATABASE_PASSWORD</b>'; |
| 878 |
|
$fakenode->db_path = variable_get('multisite_manager_dbpath_default', ''); |
| 879 |
|
$newdb = _multisite_manager_dbobj($fakenode); |
| 880 |
|
|
| 881 |
|
$recommended_directory = ''; |
| 882 |
|
|
| 883 |
|
if (!$link || !$fakenode->db_path) { |
| 884 |
|
return "ERROR: no link or db url"; |
| 885 |
|
} |
| 886 |
|
$link = str_replace('{base_url}', $base_url, $link); |
| 887 |
|
|
| 888 |
|
//$url_pieces[2] is hostname, $url_pieces[3] is path |
| 889 |
|
$url_pieces = explode('/', $link, 4); |
| 890 |
|
|
| 891 |
|
$rv = array('<p>Based on your configuration in '.l( 'Multisite Manager Defaults','admin/settings/multisite_manager/defaults').' The following code should be added to a special settings.php file which . Note, <b>This is an experimental feature</b> and you should look over this carefully before adding it in place of the database configuration (Also, please '.l('report suggestions/issues','http://drupal.org/project/multisite_manager') .').</p><blockquote><pre><code>', |
| 892 |
|
'$matches = FALSE;'); |
| 893 |
|
|
| 894 |
|
$host_regex = str_replace('{shortname}', '(\w+)', $url_pieces[2]); |
| 895 |
|
$path_regex = str_replace('{shortname}', '(\w+)', $url_pieces[3]); |
| 896 |
|
|
| 897 |
|
//assumes shortname will never be the TLD |
| 898 |
|
$recommended_directory = preg_replace('|\(\\w\+\)[^.]*\.|','',$host_regex); |
| 899 |
|
///shortname in hostname |
| 900 |
|
if (strpos($url_pieces[2],'{shortname}') !== FALSE) { |
| 901 |
|
$rv[] = 'if (preg_match("|'. $host_regex .'|",$_SERVER["HTTP_HOST"], $matches)'; |
| 902 |
|
$rv[] = ' && preg_match("|'. $path_regex .'|",request_uri())'; |
| 903 |
|
$rv[] = ' && $matches)'; |
| 904 |
|
$rv[] = '{'; |
| 905 |
|
}///shortname in path |
| 906 |
|
elseif (strpos($url_pieces[3],'{shortname}') !== FALSE) { |
| 907 |
|
$rv[] = 'if (preg_match("|'. $host_regex .'|",$_SERVER["HTTP_HOST"])'; |
| 908 |
|
$rv[] = ' && preg_match("|'. $path_regex .'|",request_uri(), $matches)'; |
| 909 |
|
$rv[] = ' && $matches)'; |
| 910 |
|
$rv[] = '{'; |
| 911 |
|
$recommended_directory .= '.' . str_replace('/','.',preg_replace('|(/[^/]*)?\(\\\w\+\)([^/]*)|','',$path_regex)); |
| 912 |
|
} |
| 913 |
|
else { |
| 914 |
|
///{shortname} has to appear in the link somewhere! |
| 915 |
|
return "ERROR: shortname must be in the link template " .$url_pieces[2] .'XX' . $url_pieces[3]; |
| 916 |
|
} |
| 917 |
|
$rv[] = ' $my_site_base = $matches[1];'; |
| 918 |
|
|
| 919 |
|
///prefix |
| 920 |
|
$rv[] = ' $db_prefix = "' . $newdb['prefix'] .'";'; |
| 921 |
|
///needs URL structure |
| 922 |
|
$rv[] = ' $base_url = "'. str_replace('{shortname}','". $my_site_base ."',$link) .'"; // NO trailing slash!'; |
| 923 |
|
|
| 924 |
|
///needs newDB URI structure |
| 925 |
|
$rv[] = ' $db_url = "' .$newdb['url'] .'";'; |
| 926 |
|
|
| 927 |
|
$rv[] = ' ///This assumes you have a ./files directory in your base drupal directory'; |
| 928 |
|
$rv[] = ' $conf = array('; |
| 929 |
|
$rv[] = ' "file_directory_path" => "files/".$my_site_base,'; |
| 930 |
|
$rv[] = ' );'; |
| 931 |
|
$rv[] = '}'; |
| 932 |
|
|
| 933 |
|
$rv[] = "</code></pre></blockquote>"; |
| 934 |
|
$rv[] = 'Suggested directory under sites/ to include settings.php file: <b>' . $recommended_directory . "</b></p>"; |
| 935 |
|
return implode("\n",$rv); |
| 936 |
|
} |
| 937 |
|
|
| 938 |
function multisite_manager_admin_settings() { |
function multisite_manager_admin_settings() { |
| 939 |
$defaults = array('db_prefix' => '{shortname}_', |
$defaults = array('db_prefix' => '{shortname}_', |
| 940 |
'db_path' => '', |
'db_path' => '', |
| 941 |
'link' => '{base_url}/site/{shortname}', |
'link' => '{base_url}/site/{shortname}', |
| 942 |
|
'admin_account' => 'copy creator', |
| 943 |
'profile' => 'default', |
'profile' => 'default', |
| 944 |
'install_immediate' => TRUE, |
'install_immediate' => TRUE, |
| 945 |
'run_cron' => 0, |
'run_cron' => 0, |
| 979 |
'#default_value' => variable_get('multisite_manager_link_default', $defaults['link']), |
'#default_value' => variable_get('multisite_manager_link_default', $defaults['link']), |
| 980 |
'#description' => t('This is where the site will be accessible by default. If you setup your ./sites/default/settings.php correctly along with your web server (e.g. apache/htaccess) config, you can anticipate where the new site will live and forward the user to the new site location upon creation. Here, there are two dynamic variables, {base_url} and {shortname}.') |
'#description' => t('This is where the site will be accessible by default. If you setup your ./sites/default/settings.php correctly along with your web server (e.g. apache/htaccess) config, you can anticipate where the new site will live and forward the user to the new site location upon creation. Here, there are two dynamic variables, {base_url} and {shortname}.') |
| 981 |
); |
); |
| 982 |
|
$form['multisite_manager_admin_account'] = array( |
| 983 |
|
'#type' => 'radios', |
| 984 |
|
'#title' => t('Admin account on new sites'), |
| 985 |
|
'#required' => TRUE, |
| 986 |
|
'#options' => array( |
| 987 |
|
'copy admin' => t('Copy the admin account from this site'), |
| 988 |
|
'copy creator' => t('Copy the account of the creator'), |
| 989 |
|
'do nothing' => t('Do nothing (only choose this if all profiles available do something instead. the default profile does not)'), |
| 990 |
|
/*'prompt creator' => '',*/ |
| 991 |
|
), |
| 992 |
|
'#default_value' => variable_get('multisite_manager_admin_account', $defaults['admin_account']), |
| 993 |
|
); |
| 994 |
$form['multisite_manager_profile_default'] = array( |
$form['multisite_manager_profile_default'] = array( |
| 995 |
'#type' => 'radios', |
'#type' => 'radios', |
| 996 |
'#title' => t('Profile default'), |
'#title' => t('Profile default'), |
| 1019 |
} // end function _multisite_manager_node_url() |
} // end function _multisite_manager_node_url() |
| 1020 |
|
|
| 1021 |
|
|
| 1022 |
|
function multisite_manager_theme() { |
| 1023 |
|
return array('multisite_manager_sitelink' => array ('arguments'=>array('node'))); |
| 1024 |
|
} |
| 1025 |
|
|
| 1026 |
function theme_multisite_manager_sitelink($node) { |
function theme_multisite_manager_sitelink($node) { |
| 1027 |
return l($node->title .' '. t('Site'), $node->url); |
return l($node->title .' '. t('Site'), $node->url); |
| 1028 |
} |
} |