| 1 |
<?php
|
| 2 |
// $Id: multisite_maintenance.module,v 1.1 2008/01/09 06:36:08 dalin Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @subpackage HOOKS
|
| 6 |
*/
|
| 7 |
|
| 8 |
/**
|
| 9 |
* Implementation of hook_help().
|
| 10 |
*/
|
| 11 |
function multisite_maintenance_help($section) {
|
| 12 |
switch ($section) {
|
| 13 |
case 'admin/help#multisite_maintenance':
|
| 14 |
return '<p>'. t('Allows batch maintenance of multisites including taking sites offline, ' .
|
| 15 |
'and running update.php.') .'</p>';
|
| 16 |
case 'admin/multisite/multisite_maintenance/site_status':
|
| 17 |
return '<p>'. t('You can alter the status of any site below. Only those sites whose databases ' .
|
| 18 |
'can be reached can be changed (Other sites may be for use on another staging or production ' .
|
| 19 |
'server). Note that the cache will also be cleared for all sites that you take offline.') .'</p>';
|
| 20 |
case 'admin/multisite/multisite_maintenance/update':
|
| 21 |
return '<p>'. t('Choose the sites that you wish to run update.php for. Only those sites whose ' .
|
| 22 |
'databases can be reached can be taken offline (Other sites may be for use on another staging ' .
|
| 23 |
'or production server).') .'</p>' .
|
| 24 |
'<h3>'. t('Technique') .'</h3>'.
|
| 25 |
'<p>'. t('For each site to be updated, $db_url is changed on the fly, global variables are ' .
|
| 26 |
'rebuilt, caches are cleared, the update functions are run (with the default "latest version" ' .
|
| 27 |
'for each updated module), and then everything is restored to the original values.') .'</p>'.
|
| 28 |
'<h3>'. t('Challenges and Limitations') .'</h3>'.
|
| 29 |
'<p>'. t('The following globals cannot be rebuilt: base_url (if not explicitly set in each ' .
|
| 30 |
'settings.php file, therefore we recommend always explicitly setting this). Also be aware ' .
|
| 31 |
'that static variables cannot be reset (see conf_path() as an example). If an update is relying ' .
|
| 32 |
'on a static variable that would be different on each site the update will be completed in error ' .
|
| 33 |
'but without any indication of such. It should be noted that this edge case should be very rare, ' .
|
| 34 |
'in theory update.php will only be making changes to the database (perhaps modifying a module\'s ' .
|
| 35 |
'tables and using variable_set).') .'</p>';
|
| 36 |
case 'admin/multisite/multisite_maintenance':
|
| 37 |
return '<p>'. t('Multisite Maintenance can make multisite Drupal installations easier to manage ' .
|
| 38 |
'by providing tools to take selected sites off-line, and to update selected sites. Note that ' .
|
| 39 |
'by visiting the status and update tabs, the code in all of your settings.php files will be ' .
|
| 40 |
'evaluated. This should not be a problem unless you have some really unusual customization in ' .
|
| 41 |
'your settings.php files.') .'</p>';
|
| 42 |
}
|
| 43 |
}
|
| 44 |
|
| 45 |
/**
|
| 46 |
* Implementation of hook_perm().
|
| 47 |
*/
|
| 48 |
function multisite_maintenance_perm() {
|
| 49 |
return array('perform maintenance on multisites');
|
| 50 |
}
|
| 51 |
|
| 52 |
|
| 53 |
/**
|
| 54 |
* Implementation of hook_menu().
|
| 55 |
*/
|
| 56 |
function multisite_maintenance_menu($may_cache) {
|
| 57 |
|
| 58 |
$items = array();
|
| 59 |
$access = user_access('perform maintenance on multisites');
|
| 60 |
|
| 61 |
if ($may_cache) {
|
| 62 |
$items[] = array(
|
| 63 |
'path' => 'admin/multisite/multisite_maintenance',
|
| 64 |
'title' => t('Multisite maintenance'),
|
| 65 |
'callback' => 'drupal_get_form',
|
| 66 |
'callback arguments' => array('multisite_maintenance_info_form'),
|
| 67 |
'access' => $access,
|
| 68 |
'type' => MENU_NORMAL_ITEM,
|
| 69 |
);
|
| 70 |
$items[] = array(
|
| 71 |
'path' => 'admin/multisite/multisite_maintenance/info',
|
| 72 |
'title' => t('Info'),
|
| 73 |
'type' => MENU_DEFAULT_LOCAL_TASK,
|
| 74 |
);
|
| 75 |
$items[] = array(
|
| 76 |
'path' => 'admin/multisite/multisite_maintenance/site_status',
|
| 77 |
'title' => t('Site Status'),
|
| 78 |
'callback' => 'drupal_get_form',
|
| 79 |
'callback arguments' => array('multisite_maintenance_status_form'),
|
| 80 |
'access' => $access,
|
| 81 |
'type' => MENU_LOCAL_TASK,
|
| 82 |
);
|
| 83 |
$items[] = array(
|
| 84 |
'path' => 'admin/multisite/multisite_maintenance/update',
|
| 85 |
'title' => t('Update Sites'),
|
| 86 |
'callback' => 'drupal_get_form',
|
| 87 |
'callback arguments' => array('multisite_maintenance_update_form'),
|
| 88 |
'access' => $access,
|
| 89 |
'type' => MENU_LOCAL_TASK,
|
| 90 |
);
|
| 91 |
}
|
| 92 |
|
| 93 |
return $items;
|
| 94 |
|
| 95 |
}
|
| 96 |
|
| 97 |
|
| 98 |
/**
|
| 99 |
* @subpackage FORMS
|
| 100 |
*/
|
| 101 |
|
| 102 |
/**
|
| 103 |
* multisite info page
|
| 104 |
*/
|
| 105 |
function multisite_maintenance_info_form() {
|
| 106 |
|
| 107 |
// test for mysqldump
|
| 108 |
$form['mysql']['#value'] = '<strong>'. t('mysqldump') .':</strong> ';
|
| 109 |
if (_multisite_maintenance_test_shell_app('mysqldump')) {
|
| 110 |
$form['mysql']['#value'] .= t('Found');
|
| 111 |
}
|
| 112 |
else {
|
| 113 |
$form['mysql']['#value'] .= t('Not found. Your database(s) will not be backed up before you run the mutlisite update. To rectify this, make sure that the path for mysqldump is included in your system\'s $PATH variable.');
|
| 114 |
}
|
| 115 |
|
| 116 |
// test for gzip
|
| 117 |
$form['gzip']['#value'] = '<br /><strong>'. t('gzip') .':</strong> ';
|
| 118 |
if (_multisite_maintenance_test_shell_app('mysqldump')) {
|
| 119 |
$form['gzip']['#value'] .= t('Found');
|
| 120 |
}
|
| 121 |
else {
|
| 122 |
$form['gzip']['#value'] .= t('Not found. Your backups will not be compressed before you run the mutlisite update. To rectify this, make sure that the path for gzip is included in your system\'s $PATH variable.');
|
| 123 |
}
|
| 124 |
|
| 125 |
// backup dir
|
| 126 |
$form['multisite_maintenance_backup_dir'] = array(
|
| 127 |
'#type' => 'textfield',
|
| 128 |
'#title' => 'Backup Directory',
|
| 129 |
'#default_value' => variable_get('multisite_maintenance_backup_dir', ''),
|
| 130 |
'#description' => 'Enter the absolute path to the directory that you wish to store your backups to. It is recommended that you store backups outside of the webroot. The directory that you choose must be writable by the apache user.',
|
| 131 |
);
|
| 132 |
|
| 133 |
// check for devel
|
| 134 |
if (module_exists('devel') && variable_get('devel_error_handler', '1') == '2') {
|
| 135 |
drupal_set_message('You have the devel module enabled with error backtrace. You may see a series of mysql errors on the subsequent pages. Switching to the standard backtrace is recommended.');
|
| 136 |
}
|
| 137 |
|
| 138 |
return system_settings_form($form);
|
| 139 |
}
|
| 140 |
|
| 141 |
function multisite_maintenance_info_form_submit($form_id, $form_values) {
|
| 142 |
if (!file_check_directory($form_values['multisite_maintenance_backup_dir'], FILE_CREATE_DIRECTORY)) {
|
| 143 |
drupal_set_message(t('The directory is not writable. Your database(s) will not be backed up before you run the mutlisite update.'), 'error');
|
| 144 |
}
|
| 145 |
else{
|
| 146 |
variable_set('multisite_maintenance_backup_dir', $form_values['multisite_maintenance_backup_dir']);
|
| 147 |
}
|
| 148 |
}
|
| 149 |
|
| 150 |
|
| 151 |
/**
|
| 152 |
* abstracted form
|
| 153 |
*/
|
| 154 |
function multisite_maintenance_form() {
|
| 155 |
$sites = multisite_api_site_list();
|
| 156 |
|
| 157 |
// create storage mechanism for all sites
|
| 158 |
$form['sites'] = array('#type' => 'value', '#value' => array());
|
| 159 |
|
| 160 |
// create storage mechanism for sites with unavailable DBs
|
| 161 |
$form['unavailable_sites'] = array('#type' => 'value', '#value' => array());
|
| 162 |
|
| 163 |
// set the timeout to be real low
|
| 164 |
if ($timeout_error = !_multisite_maintenance_mysql_timeout(3)) {
|
| 165 |
drupal_set_message('Unable to set the mysql timeout to a lower value. This means that the multisite maintenance page might take as much as 2mins * (the number of sites) to load.');
|
| 166 |
}
|
| 167 |
|
| 168 |
// Traverse the sites retrieved and build the form.
|
| 169 |
foreach ($sites as $site) {
|
| 170 |
$dir = $site['dir'];
|
| 171 |
$form['sites']['#value'][$dir] = $site;
|
| 172 |
$form['dir'][$dir] = array('#value' => $site['dir']);
|
| 173 |
|
| 174 |
// obfuscate passwords
|
| 175 |
$db_url = $site['db_url'];
|
| 176 |
$db_default_dsn = $site['db_default_dsn'];
|
| 177 |
$pass_star = str_repeat('*', strlen($db_default_dsn['pass']));
|
| 178 |
$db_url = str_replace($db_default_dsn['pass'], $pass_star, $db_url);
|
| 179 |
$db_default_dsn['pass'] = str_repeat('*', strlen($db_default_dsn['pass']));
|
| 180 |
|
| 181 |
// db information
|
| 182 |
$form['db_url'][$dir] = array('#value' => $db_url);
|
| 183 |
$form['db_prefix'][$dir] = array('#value' => print_r($site['db_prefix'], true));
|
| 184 |
$form['db_default_dsn'][$dir] = array('#value' => print_r($db_default_dsn, true));
|
| 185 |
|
| 186 |
// check if the DB can be accessed
|
| 187 |
if (!$available = _multisite_maintenance_dsn_check($site['db_default_dsn'])) {
|
| 188 |
$form['unavailable_sites']['#value'][$dir] = true;
|
| 189 |
$form['db_reachable'][$dir] = array('#value' => 'No');
|
| 190 |
}
|
| 191 |
else {
|
| 192 |
$form['db_reachable'][$dir] = array('#value' => 'Yes');
|
| 193 |
}
|
| 194 |
}
|
| 195 |
|
| 196 |
// reset timeout value
|
| 197 |
if (!$timeout_error) {
|
| 198 |
_multisite_maintenance_mysql_timeout();
|
| 199 |
}
|
| 200 |
|
| 201 |
return $form;
|
| 202 |
}
|
| 203 |
|
| 204 |
|
| 205 |
/**
|
| 206 |
* multisite status page
|
| 207 |
*/
|
| 208 |
function multisite_maintenance_status_form() {
|
| 209 |
$sites = multisite_api_site_list();
|
| 210 |
$form = multisite_maintenance_form();
|
| 211 |
|
| 212 |
$form['status'] = array('#tree' => true);
|
| 213 |
|
| 214 |
// Traverse the sites retrieved and build the form.
|
| 215 |
foreach ($sites as $site) {
|
| 216 |
$dir = $site['dir'];
|
| 217 |
|
| 218 |
// site status
|
| 219 |
$form['status']['status.'. $dir] = array(
|
| 220 |
'#type' => 'radios',
|
| 221 |
'#options' => array('Off-line', 'Online'),
|
| 222 |
'#process' => array(
|
| 223 |
'expand_radios' => array(),
|
| 224 |
),
|
| 225 |
);
|
| 226 |
|
| 227 |
// check if the DB can be accessed
|
| 228 |
if ($form['unavailable_sites']['#value'][$dir]) {
|
| 229 |
$form['status']['status.'. $dir]['#attributes']['disabled'] = 'disabled';
|
| 230 |
}
|
| 231 |
else {
|
| 232 |
// find out the current state of the site and adjust the radio accordingly
|
| 233 |
$status = _multisite_maintenance_is_site_online($site);
|
| 234 |
$form['sites']['#value'][$dir]['site_status'] = (int)$status;
|
| 235 |
$form['status']['status.'. $dir]['#default_value'] = (int)$status;
|
| 236 |
}
|
| 237 |
}
|
| 238 |
|
| 239 |
// session clear
|
| 240 |
$form['clear_sessions'] = array(
|
| 241 |
'#type' => 'checkbox',
|
| 242 |
'#default_value' => true,
|
| 243 |
'#title' => t('Clear sessions tables'),
|
| 244 |
'#description' => t('Clearing sessions will log out any users currently logged in. If this table is shared accross sites (likely situation) you will log users out of all sites. You will not logged out because your session is written back to the table after it is truncated. '),
|
| 245 |
);
|
| 246 |
|
| 247 |
// submit button
|
| 248 |
$form['submit'] = array(
|
| 249 |
'#type' => 'submit',
|
| 250 |
'#value' => t('Submit'),
|
| 251 |
);
|
| 252 |
|
| 253 |
return $form;
|
| 254 |
}
|
| 255 |
|
| 256 |
function multisite_maintenance_status_form_submit($form_id, $form_values) {
|
| 257 |
|
| 258 |
// clear any messages (such as "You are operating in offline mode", cause chances are we won't when we're done)
|
| 259 |
unset($_SESSION['messages']);
|
| 260 |
|
| 261 |
// update site status
|
| 262 |
foreach ($form_values['sites'] as $site => $site_info) {
|
| 263 |
if ($form_values['unavailable_sites'][$site] || $form_values['status']['status.'. $site] == $site_info['site_status']) {
|
| 264 |
continue;
|
| 265 |
}
|
| 266 |
|
| 267 |
$error = null;
|
| 268 |
|
| 269 |
// connect to the db, this time we can use drupal query functions
|
| 270 |
_multisite_maintenance_connect_to_db($site_info);
|
| 271 |
|
| 272 |
if (!count($error)) {
|
| 273 |
|
| 274 |
// update the variable
|
| 275 |
variable_set('site_offline', !$form_values['status']['status.'. $site]);
|
| 276 |
|
| 277 |
// truncate sessions if nec.
|
| 278 |
if ($form_values['clear_sessions'] && !db_query('TRUNCATE sessions')) {
|
| 279 |
drupal_set_message('sessions error', 'error');
|
| 280 |
$error[] = true;
|
| 281 |
}
|
| 282 |
|
| 283 |
// clear the cache
|
| 284 |
cache_clear_all('*', 'cache', TRUE);
|
| 285 |
cache_clear_all('*', 'cache_page', TRUE);
|
| 286 |
cache_clear_all('*', 'cache_menu', TRUE);
|
| 287 |
cache_clear_all('*', 'cache_filter', TRUE);
|
| 288 |
drupal_clear_css_cache();
|
| 289 |
}
|
| 290 |
else {
|
| 291 |
drupal_set_message('connection error', 'error');
|
| 292 |
}
|
| 293 |
|
| 294 |
// close the db
|
| 295 |
_multisite_maintenance_connect_to_db();
|
| 296 |
|
| 297 |
// give some feedback
|
| 298 |
$msg = $site .(count($error) ? ' could not be put ' : ' was taken ');
|
| 299 |
$msg .= ($form_values['status']['status.'. $site] ? 'on' : 'off-') .'line.';
|
| 300 |
$msg .= count($error) ? 'You\'ll need to do it manually.' : '';
|
| 301 |
drupal_set_message($msg, count($error) ? 'error' : 'status');
|
| 302 |
}
|
| 303 |
|
| 304 |
}
|
| 305 |
|
| 306 |
/**
|
| 307 |
* multisite update page
|
| 308 |
*/
|
| 309 |
function multisite_maintenance_update_form() {
|
| 310 |
$sites = multisite_api_site_list();
|
| 311 |
$form = multisite_maintenance_form();
|
| 312 |
|
| 313 |
$form['update'] = array('#tree' => true);
|
| 314 |
|
| 315 |
// Traverse the sites retrieved and build the form.
|
| 316 |
foreach ($sites as $site) {
|
| 317 |
$dir = $site['dir'];
|
| 318 |
|
| 319 |
// site update
|
| 320 |
$form['update']['update.'. $dir] = array(
|
| 321 |
'#type' => 'checkbox',
|
| 322 |
);
|
| 323 |
|
| 324 |
// check if the DB can be accessed
|
| 325 |
if ($form['unavailable_sites']['#value'][$dir]) {
|
| 326 |
$form['update']['update.'. $dir]['#attributes']['disabled'] = 'disabled';
|
| 327 |
}
|
| 328 |
else {
|
| 329 |
$form['update']['update.'.$dir]['#default_value'] = true;
|
| 330 |
}
|
| 331 |
}
|
| 332 |
|
| 333 |
// backup
|
| 334 |
if (_multisite_maintenance_test_shell_app('mysqldump') && file_check_directory(variable_get('multisite_maintenance_backup_dir', ''))) {
|
| 335 |
$file = 'host_dbname_yyyy_mm_dd.mysql';
|
| 336 |
if (_multisite_maintenance_test_shell_app('gzip')) {
|
| 337 |
$file .= '.gz';
|
| 338 |
}
|
| 339 |
$form['backup'] = array(
|
| 340 |
'#type' => 'checkbox',
|
| 341 |
'#default_value' => true,
|
| 342 |
'#title' => t('Backup database(s) before updating.'),
|
| 343 |
'#description' => t('Backups of the database(s) will be stored in %files in the form %format.', array('%files' => variable_get('multisite_maintenance_backup_dir', ''), '%format' => 'host_dbname_yyyy_mm_dd.mysql.gz')),
|
| 344 |
);
|
| 345 |
}
|
| 346 |
else {
|
| 347 |
$form['backup'] = array(
|
| 348 |
'#type' => 'checkbox',
|
| 349 |
'#title' => t('Backup database(s) before updating.'),
|
| 350 |
'#attributes' => array('disabled' => 'disabled'),
|
| 351 |
'#description' => t('Your database(s) can not be backed up. See the info tab for more information'),
|
| 352 |
);
|
| 353 |
}
|
| 354 |
|
| 355 |
// submit button
|
| 356 |
$form['submit'] = array(
|
| 357 |
'#type' => 'submit',
|
| 358 |
'#value' => t('Submit'),
|
| 359 |
);
|
| 360 |
|
| 361 |
return $form;
|
| 362 |
}
|
| 363 |
|
| 364 |
function multisite_maintenance_update_form_submit($form_id, $form_values) {
|
| 365 |
|
| 366 |
// storage mechanism for backed up sites
|
| 367 |
$backed_up = array();
|
| 368 |
|
| 369 |
// switch to user1 so we pass the access check
|
| 370 |
global $user;
|
| 371 |
$old_user = $user;
|
| 372 |
$user = user_load(array('uid' =>1));
|
| 373 |
|
| 374 |
// update sites
|
| 375 |
foreach ($form_values['sites'] as $site => $site_info) {
|
| 376 |
if ($form_values['unavailable_sites'][$site] || !$form_values['update']['update.'.$site]) {
|
| 377 |
continue;
|
| 378 |
}
|
| 379 |
|
| 380 |
// give each site 5 mins to do its thing
|
| 381 |
set_time_limit(300);
|
| 382 |
|
| 383 |
$error = null;
|
| 384 |
|
| 385 |
// backup
|
| 386 |
if ($form_values['backup'] && _multisite_maintenance_test_shell_app('mysqldump') && file_check_directory(variable_get('multisite_maintenance_backup_dir', ''))) {
|
| 387 |
|
| 388 |
// db_url
|
| 389 |
$db_key = $site_info['db_dsn']['host'] . $site_info['db_dsn']['path'];
|
| 390 |
if (!array_key_exists($db_key, $backed_up)) {
|
| 391 |
$ret = check_plain(_multisite_maintenance_backup_db($site_info['db_dsn']));
|
| 392 |
if (strlen($ret)) {
|
| 393 |
$error[] = $ret;
|
| 394 |
watchdog('multisite_maintenance', $ret);
|
| 395 |
drupal_set_message($ret .'!', 'error');
|
| 396 |
}
|
| 397 |
else {
|
| 398 |
drupal_set_message($db_key .' has been backed up');
|
| 399 |
$backed_up[$db_key] = true;
|
| 400 |
}
|
| 401 |
}
|
| 402 |
|
| 403 |
// db_default_url
|
| 404 |
$db_key = $site_info['db_default_dsn']['host'] . $site_info['db_default_dsn']['path'];
|
| 405 |
if (!array_key_exists($db_key, $backed_up)) {
|
| 406 |
$ret = check_plain(_multisite_maintenance_backup_db($site_info['db_default_dsn']));
|
| 407 |
if (strlen($ret)) {
|
| 408 |
$error[] = $ret;
|
| 409 |
watchdog('multisite_maintenance', $ret);
|
| 410 |
drupal_set_message($ret .'!', 'error');
|
| 411 |
}
|
| 412 |
else {
|
| 413 |
drupal_set_message($db_key .' has been backed up');
|
| 414 |
$backed_up[$db_key] = true;
|
| 415 |
}
|
| 416 |
}
|
| 417 |
}
|
| 418 |
|
| 419 |
//run the update
|
| 420 |
if (!count($error)) {
|
| 421 |
|
| 422 |
// switch to the other db
|
| 423 |
_multisite_maintenance_connect_to_db($site_info);
|
| 424 |
|
| 425 |
// reset the page timer
|
| 426 |
timer_start('page');
|
| 427 |
|
| 428 |
// get the list of updated versions
|
| 429 |
include_once('update.php');
|
| 430 |
$update_form = update_script_selection_form();
|
| 431 |
$_POST['start'] = array();
|
| 432 |
foreach ($update_form['start'] as $name => $module) {
|
| 433 |
if (substr($name, 0, 1) == '#') {
|
| 434 |
continue;
|
| 435 |
}
|
| 436 |
$_POST['start'][$name] = $module['#default_value'];
|
| 437 |
}
|
| 438 |
|
| 439 |
/**
|
| 440 |
* the below code is stolen from update.php update_update_page()
|
| 441 |
*/
|
| 442 |
// Set the installed version so updates start at the correct place.
|
| 443 |
foreach ($_POST['start'] as $module => $version) {
|
| 444 |
drupal_set_installed_schema_version($module, $version - 1);
|
| 445 |
$updates = drupal_get_schema_versions($module);
|
| 446 |
$max_version = max($updates);
|
| 447 |
if ($version <= $max_version) {
|
| 448 |
foreach ($updates as $update) {
|
| 449 |
if ($update >= $version) {
|
| 450 |
$_SESSION['update_remaining'][] = array('module' => $module, 'version' => $update);
|
| 451 |
}
|
| 452 |
}
|
| 453 |
}
|
| 454 |
}
|
| 455 |
// Keep track of total number of updates
|
| 456 |
if (isset($_SESSION['update_remaining'])) {
|
| 457 |
$_SESSION['update_total'] = count($_SESSION['update_remaining']);
|
| 458 |
}
|
| 459 |
|
| 460 |
/**
|
| 461 |
* the below code is stolen from update.php update_do_updates() and slightly modified
|
| 462 |
*/
|
| 463 |
while (isset($_SESSION['update_remaining']) && ($update = reset($_SESSION['update_remaining']))) {
|
| 464 |
$update_finished = update_data($update['module'], $update['version']);
|
| 465 |
if ($update_finished == 1) {
|
| 466 |
// Dequeue the completed update.
|
| 467 |
unset($_SESSION['update_remaining'][key($_SESSION['update_remaining'])]);
|
| 468 |
$update_finished = 0; // Make sure this step isn't counted double
|
| 469 |
}
|
| 470 |
/** this has been lengthened so that each site update runs for 1 min before we give up **/
|
| 471 |
if (timer_read('page') > 1000 *3600) {
|
| 472 |
break;
|
| 473 |
}
|
| 474 |
}
|
| 475 |
if ($_SESSION['update_total']) {
|
| 476 |
$percentage = floor(($_SESSION['update_total'] - count($_SESSION['update_remaining']) + $update_finished) / $_SESSION['update_total'] * 100);
|
| 477 |
}
|
| 478 |
else {
|
| 479 |
$percentage = 100;
|
| 480 |
}
|
| 481 |
|
| 482 |
// When no updates remain, clear the caches in case the data has been updated.
|
| 483 |
if (!isset($update['module'])) {
|
| 484 |
cache_clear_all('*', 'cache', TRUE);
|
| 485 |
cache_clear_all('*', 'cache_page', TRUE);
|
| 486 |
cache_clear_all('*', 'cache_menu', TRUE);
|
| 487 |
cache_clear_all('*', 'cache_filter', TRUE);
|
| 488 |
drupal_clear_css_cache();
|
| 489 |
}
|
| 490 |
|
| 491 |
// close the db
|
| 492 |
_multisite_maintenance_connect_to_db();
|
| 493 |
}
|
| 494 |
else {
|
| 495 |
drupal_set_message('backup error', 'error');
|
| 496 |
}
|
| 497 |
|
| 498 |
// give some feedback
|
| 499 |
if (count($error)) {
|
| 500 |
$msg = $site .' could not be updated. You\'ll need to do it manually.';
|
| 501 |
}
|
| 502 |
else {
|
| 503 |
$msg = $percentage .'% of '. $site .' was updated. ';
|
| 504 |
}
|
| 505 |
drupal_set_message($msg, count($error) ? 'error' : 'status');
|
| 506 |
}
|
| 507 |
|
| 508 |
// set user back
|
| 509 |
$user = $old_user;
|
| 510 |
}
|
| 511 |
|
| 512 |
/**
|
| 513 |
* @subpackage THEME FUNCTIONS
|
| 514 |
*/
|
| 515 |
function theme_multisite_maintenance_status_form($form) {
|
| 516 |
|
| 517 |
// Individual table headers.
|
| 518 |
$header = array(t('Site Online'), t('Sites Directory'), t('db_url'), t('db_prefix'), t('Default DSN'), t('DB is Reachable'));
|
| 519 |
|
| 520 |
// build table
|
| 521 |
$rows = array();
|
| 522 |
foreach($form['sites']['#value'] as $key => $site) {
|
| 523 |
if (substr($key, 0,1) == '#') {
|
| 524 |
continue;
|
| 525 |
}
|
| 526 |
$row = array();
|
| 527 |
$row[] = drupal_render($form['status']['status.'. $key]);
|
| 528 |
$row[] = '<strong>'. drupal_render($form['dir'][$key]) .'</strong>';
|
| 529 |
$row[] = drupal_render($form['db_url'][$key]);
|
| 530 |
$row[] = '<pre>'. drupal_render($form['db_prefix'][$key]) .'</pre>';
|
| 531 |
$row[] = '<pre>'. drupal_render($form['db_default_dsn'][$key]) .'</pre>';
|
| 532 |
$row[] = '<strong>'. drupal_render($form['db_reachable'][$key]) .'</strong>';
|
| 533 |
$rows[] = $row;
|
| 534 |
}
|
| 535 |
$output .= theme('table', $header, $rows, array('class' => 'package'));
|
| 536 |
|
| 537 |
// render remaining form objects and return
|
| 538 |
$output .= drupal_render($form);
|
| 539 |
return $output;
|
| 540 |
}
|
| 541 |
|
| 542 |
function theme_multisite_maintenance_update_form($form) {
|
| 543 |
|
| 544 |
// Individual table headers.
|
| 545 |
$header = array(t('Update Site'), t('Sites Directory'), t('db_url'), t('db_prefix'), t('Default DSN'), t('DB is Reachable'));
|
| 546 |
|
| 547 |
// build table
|
| 548 |
$rows = array();
|
| 549 |
foreach($form['sites']['#value'] as $key => $site) {
|
| 550 |
if (substr($key, 0,1) == '#') {
|
| 551 |
continue;
|
| 552 |
}
|
| 553 |
$row = array();
|
| 554 |
$row[] = drupal_render($form['update']['update.'. $key]);
|
| 555 |
$row[] = '<strong>'. drupal_render($form['dir'][$key]) .'</strong>';
|
| 556 |
$row[] = drupal_render($form['db_url'][$key]);
|
| 557 |
$row[] = '<pre>'. drupal_render($form['db_prefix'][$key]) .'</pre>';
|
| 558 |
$row[] = '<pre>'. drupal_render($form['db_default_dsn'][$key]) .'</pre>';
|
| 559 |
$row[] = '<strong>'. drupal_render($form['db_reachable'][$key]) .'</strong>';
|
| 560 |
$rows[] = $row;
|
| 561 |
}
|
| 562 |
$output .= theme('table', $header, $rows, array('class' => 'package'));
|
| 563 |
|
| 564 |
// render remaining form objects and return
|
| 565 |
$output .= drupal_render($form);
|
| 566 |
return $output;
|
| 567 |
}
|
| 568 |
|
| 569 |
|
| 570 |
/**
|
| 571 |
* @subpackage INTERNAL_FUNCTIONS
|
| 572 |
*/
|
| 573 |
|
| 574 |
/**
|
| 575 |
* Checks a DSN array to see if connection to the db can be made
|
| 576 |
* @param array $dsn
|
| 577 |
* an associative array of the db connection info in the form returned by parse_url()
|
| 578 |
* @return boolean
|
| 579 |
* true on success else false
|
| 580 |
* @todo postgres
|
| 581 |
* unfortunately we can't use drupal db functions here. PostgreSQL users will need to patch this
|
| 582 |
*/
|
| 583 |
function _multisite_maintenance_dsn_check($dsn) {
|
| 584 |
|
| 585 |
// test db connection
|
| 586 |
$link = @mysql_connect($dsn['host'] . ($dsn['port'] ? ':'. $dsn['port'] : ''), $dsn['user'], $dsn ['pass'], true)
|
| 587 |
or $error = true;
|
| 588 |
if (empty($link)) {
|
| 589 |
$error = true;
|
| 590 |
}
|
| 591 |
if (!$error) {
|
| 592 |
$db = substr($dsn['path'], 1);
|
| 593 |
$link_db = @mysql_select_db($db, $link)
|
| 594 |
or $error = true;
|
| 595 |
if (!$link_db) {
|
| 596 |
$error = true;
|
| 597 |
}
|
| 598 |
@mysql_close($link);
|
| 599 |
}
|
| 600 |
|
| 601 |
|
| 602 |
return !$error;
|
| 603 |
}
|
| 604 |
|
| 605 |
|
| 606 |
/**
|
| 607 |
* adjusts mysql.connect_timeout
|
| 608 |
* pass an int to set it, leave arg blank to reset it
|
| 609 |
*/
|
| 610 |
function _multisite_maintenance_mysql_timeout($new_value = null) {
|
| 611 |
static $old_timeout;
|
| 612 |
if (!is_null($new_value)) {
|
| 613 |
|
| 614 |
// store the old value
|
| 615 |
if (is_null($old_timeout)) {
|
| 616 |
if (!($old_timeout = ini_get('mysql.connect_timeout'))) {
|
| 617 |
$error = true;
|
| 618 |
}
|
| 619 |
|
| 620 |
// set the new value
|
| 621 |
if (!$error && ini_set('mysql.connect_timeout', $new_value)) {
|
| 622 |
return true;
|
| 623 |
}
|
| 624 |
return false;
|
| 625 |
}
|
| 626 |
else {
|
| 627 |
// whoops! we tried to set the timeout a second time without reseting
|
| 628 |
return false;
|
| 629 |
}
|
| 630 |
}
|
| 631 |
else {
|
| 632 |
|
| 633 |
// reset the timer
|
| 634 |
// check for saved value
|
| 635 |
if (!is_null($old_timeout) && ini_set('mysql.connect_timeout', $old_timeout)) {
|
| 636 |
$old_timeout = null;
|
| 637 |
return true;
|
| 638 |
}
|
| 639 |
return false;
|
| 640 |
}
|
| 641 |
}
|
| 642 |
|
| 643 |
|
| 644 |
/**
|
| 645 |
* -adjusts $_GLOBALS['db_url'] and $_GLOBALS['db_prefix']
|
| 646 |
* -mounts the db
|
| 647 |
* -adjusts all other globals (if possible)
|
| 648 |
* -clear cache
|
| 649 |
* @param array
|
| 650 |
* $site in the form as created by multisite_api_site_list()
|
| 651 |
* or leave null to reset to the original values
|
| 652 |
*/
|
| 653 |
function _multisite_maintenance_connect_to_db($site = null) {
|
| 654 |
|
| 655 |
global $db_url, $db_prefix, $base_url, $base_root, $base_path;
|
| 656 |
|
| 657 |
static $original_site;
|
| 658 |
|
| 659 |
if ($site) {
|
| 660 |
|
| 661 |
// store the old value
|
| 662 |
if (!isset($original_site)) {
|
| 663 |
$sites = multisite_api_site_list();
|
| 664 |
$original_site = $sites[substr(conf_path(), 6)];
|
| 665 |
}
|
| 666 |
|
| 667 |
// set the new value
|
| 668 |
if (is_array($db_url)) {
|
| 669 |
$db_url[$site['dir']] = $site['db_url'];
|
| 670 |
}
|
| 671 |
else {
|
| 672 |
$db_url = array('default' => $db_url);
|
| 673 |
$db_url[$site['dir']] = $site['db_url'];
|
| 674 |
}
|
| 675 |
$db_prefix = $site['db_prefix'];
|
| 676 |
if ($site['base_url']) {
|
| 677 |
$base_url = $site['base_url'];
|
| 678 |
}
|
| 679 |
else {
|
| 680 |
unset($base_url);
|
| 681 |
}
|
| 682 |
db_set_active($site['dir']);
|
| 683 |
|
| 684 |
}
|
| 685 |
else {
|
| 686 |
|
| 687 |
// reset the db stuff
|
| 688 |
// check for saved value
|
| 689 |
if (isset($original_site)) {
|
| 690 |
$db_url = $original_site['db_url'];
|
| 691 |
$db_prefix = $original_site['db_prefix'];
|
| 692 |
if ($original_site['base_url']) {
|
| 693 |
$base_url = $original_site['base_url'];
|
| 694 |
}
|
| 695 |
else {
|
| 696 |
unset($base_url);
|
| 697 |
}
|
| 698 |
db_set_active();
|
| 699 |
$site = $original_site;
|
| 700 |
unset($original_site);
|
| 701 |
}
|
| 702 |
else {
|
| 703 |
drupal_set_message('whoops! we tried to reset before we set', 'error');
|
| 704 |
return false;
|
| 705 |
}
|
| 706 |
}
|
| 707 |
|
| 708 |
// clear our cache for this site
|
| 709 |
cache_clear_all('*', 'cache', TRUE);
|
| 710 |
cache_clear_all('*', 'cache_page', TRUE);
|
| 711 |
cache_clear_all('*', 'cache_menu', TRUE);
|
| 712 |
cache_clear_all('*', 'cache_filter', TRUE);
|
| 713 |
drupal_clear_css_cache();
|
| 714 |
|
| 715 |
/** rebuild all important globals **/
|
| 716 |
// $base_url, $base_path, $base_root
|
| 717 |
// this code grabbed from bootsrtap.inc conf_init
|
| 718 |
if (isset($base_url)) {
|
| 719 |
// Parse fixed base URL from settings.php.
|
| 720 |
$parts = parse_url($base_url);
|
| 721 |
if (!isset($parts['path'])) {
|
| 722 |
$parts['path'] = '';
|
| 723 |
}
|
| 724 |
$base_path = $parts['path'] . '/';
|
| 725 |
// Build $base_root (everything until first slash after "scheme://").
|
| 726 |
$base_root = substr($base_url, 0, strlen($base_url) - strlen($parts['path']));
|
| 727 |
}
|
| 728 |
else {
|
| 729 |
// if $base_url is not passed to us, we cannot reverse engineer the others,
|
| 730 |
// unset them so that an error is thrown if they are used
|
| 731 |
unset ($base_root, $base_path);
|
| 732 |
}
|
| 733 |
|
| 734 |
// variables
|
| 735 |
cache_clear_all('*', 'cache', TRUE);
|
| 736 |
|
| 737 |
global $debug;
|
| 738 |
$debug = true;
|
| 739 |
unset($GLOBALS['conf']);
|
| 740 |
$GLOBALS['conf'] = variable_init((array)$site['conf']);
|
| 741 |
|
| 742 |
}
|
| 743 |
|
| 744 |
/**
|
| 745 |
* switches to the given site and asks whether it's online
|
| 746 |
*/
|
| 747 |
function _multisite_maintenance_is_site_online($site) {
|
| 748 |
|
| 749 |
// switch to the given site
|
| 750 |
_multisite_maintenance_connect_to_db($site);
|
| 751 |
|
| 752 |
// are we online?
|
| 753 |
$offline = variable_get('site_offline', 'default');
|
| 754 |
// drupal_set_message('$offline:'. $offline);
|
| 755 |
|
| 756 |
// switch back
|
| 757 |
_multisite_maintenance_connect_to_db();
|
| 758 |
|
| 759 |
return !$offline;
|
| 760 |
}
|
| 761 |
|
| 762 |
/**
|
| 763 |
* tests if a shell application exists
|
| 764 |
* @param string $app
|
| 765 |
* either 'gzip' or 'mysqldump'
|
| 766 |
* @return boolean
|
| 767 |
* true if it exists, else false
|
| 768 |
*/
|
| 769 |
function _multisite_maintenance_test_shell_app($app) {
|
| 770 |
static $gzip;
|
| 771 |
static $mysqldump;
|
| 772 |
|
| 773 |
if ($$app) {
|
| 774 |
return $app;
|
| 775 |
}
|
| 776 |
switch ($app) {
|
| 777 |
case 'gzip':
|
| 778 |
$info = shell_exec('gzip -L');
|
| 779 |
$gzip = strpos($info, 'WARRANTY') !== false;
|
| 780 |
return $gzip;
|
| 781 |
break;
|
| 782 |
case 'mysqldump':
|
| 783 |
$info = shell_exec('mysqldump -V');
|
| 784 |
$mysqldump = strpos($info, 'Ver') !== false;
|
| 785 |
return $mysqldump;
|
| 786 |
break;
|
| 787 |
}
|
| 788 |
}
|
| 789 |
|
| 790 |
/**
|
| 791 |
* backs up the given db.
|
| 792 |
* @param array
|
| 793 |
* $dsn must be in the form as returned by parse_url()
|
| 794 |
* @return string
|
| 795 |
* the output of the command
|
| 796 |
*/
|
| 797 |
function _multisite_maintenance_backup_db($dsn) {
|
| 798 |
$db = substr($dsn['path'], 1);
|
| 799 |
$gzip = _multisite_maintenance_test_shell_app('gzip');
|
| 800 |
|
| 801 |
// build filename
|
| 802 |
$ext = '.mysql'. ($gzip ? '.gz' : '');
|
| 803 |
$file = variable_get('multisite_maintenance_backup_dir', '') .'/'. $dsn['host'] .'_'. $db .'_'. date('Y_m_d');
|
| 804 |
$inc = '';
|
| 805 |
$x = 1;
|
| 806 |
|
| 807 |
// test if exists
|
| 808 |
while (file_exists($file_name =($file . $inc . $ext))) {
|
| 809 |
$inc = "__$x";
|
| 810 |
$x ++;
|
| 811 |
}
|
| 812 |
|
| 813 |
// build command
|
| 814 |
$cmd = 'mysqldump -u'. $dsn['user'] .' -p'. $dsn['pass'] .' -h'. $dsn['host'] .' '. $db;
|
| 815 |
if ($gzip) {
|
| 816 |
$cmd .= ' | gzip';
|
| 817 |
}
|
| 818 |
$cmd .= ' > '. $file_name;
|
| 819 |
|
| 820 |
// execute command
|
| 821 |
$return = shell_exec($cmd);
|
| 822 |
return $return;
|
| 823 |
}
|