'corepatch_callback', 'description' => COREPATCH_DESCRIPTION, 'options' => array( '--nobackup' => 'Skip backup of current site. No restore will be possible.', ), 'examples' => array( 'corepatch' => 'patch to latest stable release of current drupal major version', 'corepatch --nobackup ' => 'Don\'t make a backup of db and filespace.', ), 'drupal dependencies' => array(), 'core' => array('6'), ); $items['corerestore'] = array( 'callback' => 'corerestore_callback', 'description' => CORERESTORE_DESCRIPTION, 'drupal dependencies' => array(), 'core' => array('6'), ); $items['corebackup'] = array( 'callback' => 'corebackup_callback', 'description' => COREBACKUP_DESCRIPTION, 'drupal dependencies' => array(), 'core' => array('6'), ); return $items; } /** * Implementation of hook_drush_help(). * */ function corepatch_drush_help($section) { switch ($section) { case 'drush:corepatch': return dt(COREPATCH_DESCRIPTION); case 'drush:corerestore': return dt(CORERESTORE_DESCRIPTION); case 'drush:corebackup': return dt(COREBACKUP_DESCRIPTION); } } /** * corepatch_callback * * backs up and updates drupal core based on file options. * not tested on unix machines */ function corepatch_callback() { drush_include_engine('update_info', 'drupal', NULL, DRUSH_BASE_PATH . '/commands/pm/update_info'); // determine installed drupal core and latest available $core_version = drush_drupal_version(); $releases = _pm_get_update_info(); reset($releases['drupal']['releases']); list($latest_stable, $info) = each($releases['drupal']['releases']); if ($core_version == $latest_stable) { drush_print("Core version ($core_version) and latest stable release ($latest_stable) are the same. Cannot patch."); return FALSE; } $drupal_target_dir = drush_get_context('DRUSH_DRUPAL_ROOT'); $drupal_dl_dir = variable_get("file_directory_temp"); // create backup if (!drush_get_option('nobackup')) { $backup_dir = drush_get_option('backup-dir') .'/site/'. date('YmdHis'); // echo "creating backup to: $backup_dir \n"; corepatch_corebackup($drupal_target_dir, $backup_dir, _drush_sql_get_db_spec()); } // get most recent stable core @drush_op('mkdir', $drupal_dl_dir, 0777); drush_set_option('destination', $drupal_dl_dir); drush_pm_dl(); if (drush_get_error()) { drush_log(dt("Drupal download failed."), 'error'); exit; }; // echo "set offline \n"; $offline_orginal_state = variable_get('site_offline', FALSE); drush_op('variable_set', 'site_offline', TRUE); // copy downloaded core to current drupal directory per $corepatch_file_prefs corepatch_update_core_files($drupal_target_dir, $drupal_dl_dir . "/drupal-". $latest_stable , drush_get_option('corepatch_file_prefs')); // run update drush_backend_invoke("updatedb"); if (! $offline_orginal_state) { drush_op('variable_set', 'site_offline', $offline_orginal_state); } // delete download delete_dir($drupal_dl_dir); $msg = dt("Drupal version !last_version updated to !current_version in !target_dir .", array('!last_version' => $core_version, '!current_version' => $latest_stable, '!target_dir' => $drupal_target_dir ) ); drush_log($msg, 'ok'); drush_print($msg); } /** * corebackup_callback * * create backup */ function corebackup_callback() { $drupal_target_dir = drush_get_context('DRUSH_DRUPAL_ROOT'); $backup_dir = drush_get_option('backup-dir', $drupal_target_dir . '/backups'); $backup_dir .= '/site/'. date('YmdHis'); $db_spec = _drush_sql_get_db_spec(); corepatch_corebackup($drupal_target_dir, $backup_dir, $db_spec); $msg = dt("Drupal install locate at !target_dir and its database \"!db\" backed up to !target_dir.", array('!target_dir' => $drupal_target_dir, '!db' => $db_spec['database'], '!target_dir' => $backup_dir ) ); drush_log($msg, 'ok'); return TRUE; } /** * corerestore_callback * * revert to backup */ function corerestore_callback() { // todo: add ability to pass backup dir in as option so can be called // without interactive prompts // find backup directory. $backup_dir = drush_get_option('backup-dir', $drupal_target_dir . '/backups') . "/site"; $paths = array(); $i = 0; echo "Choose a backup to restore. This will restore database and files.\n"; if ($dh = opendir($backup_dir)) { while (($file_or_dir = readdir($dh)) !== false) { if ($file_or_dir == ".." || $file_or_dir == ".") continue; $i++; $paths[$i] = $backup_dir ."/". $file_or_dir; echo "[ $i ] ". substr($file_or_dir,0,4) . "-" . substr($file_or_dir,4,2) . "-" . substr($file_or_dir,6,2) . "-" . substr($file_or_dir,8) . "\n"; } } $RESPONSE = trim(fgets(STDIN)); $backup = trim($RESPONSE); if (! in_array($backup,array_keys($paths))) { echo "not a valid number"; exit; } $backup_location = $paths[$backup]; $offline_orginal_state = variable_get('site_offline', FALSE); drush_op('variable_set', 'site_offline', TRUE); // restore db $db_spec = _drush_sql_get_db_spec(); $db_backup_file = $backup_location ."/". $db_spec['database'] . ".sql" ; switch (_drush_sql_get_scheme()) { case 'mysql': // Build import command for target. No verbose here as that emits too much SQL. $send = 'mysql' . _drush_sql_get_credentials($db_spec['database']); break; case 'pgsql': $send .= 'psql -d ' . _drush_sql_get_credentials($db_spec['database']) . ' --file -'; break; } $exec = "$send < $db_backup_file"; drush_op('system', $exec); // restore files, leaving backup in place $drupal_restore_to_dir = drush_get_context('DRUSH_DRUPAL_ROOT'); $drupal_restore_from_dir = $backup_location . "/core"; corepatch_copy_dir($drupal_restore_from_dir, $drupal_restore_to_dir); if (! $offline_orginal_state) { drush_op('variable_set', 'site_offline', $offline_orginal_state); } } function corepatch_corebackup($drupal_target_dir, $backup_dir, $db_spec) { // create backup directories foreach (explode('/',$backup_dir) as $pathpart ) { $backup_root .= $pathpart; if (!is_dir($backup_root)) { @drush_op('mkdir', $backup_root, 0777); } $backup_root .= "/"; } $core_backup_dir = $backup_root ."core"; drush_op('mkdir', $core_backup_dir, 0777); $db_backup_dir = $backup_root; // $backup_root ."db"; drush_op('mkdir', $db_backup_dir, 0777); // backup filespace. todo need to add private files directory check corepatch_copy_dir($drupal_target_dir, $core_backup_dir, $options); // backup db drush_set_option('result-file', $db_backup_dir . $db_spec['database'] . ".sql"); $drush_sql_dump = drush_sql_dump_execute($db_spec); } function corepatch_copy_dir($src_dir, $dest_dir, $options) { // i'm not clear how to copy directories in php without recursion, so command line here if (corepatch_is_windows()) { $cmd = "ROBOCOPY ". corepatch_windows_path($src_dir) ." ". corepatch_windows_path($dest_dir). " /R:2 /COPYALL /DCOPY:T /MIR /NFL /NDL /NS /NC "; // /NP drush_op('system', $cmd); } else { // clean out target directory if ($dh = opendir($dest_dir)) { while (($file_or_dir = readdir($dh)) !== false) { if (is_file($file_or_dir)) { drush_op('unlink', $dest_dir ."/". $file_or_dir ); } elseif (is_dir($file_or_dir)) { drush_op('delete_dir', $dest_dir ."/". $file_or_dir); } } } $cmd = "cp -R $src_dir $dest_dir"; drush_op('system', $cmd); // there must be more unix flags to set here such as not following sym links etc. } } function corepatch_update_core_files($drupal_target_dir, $drupal_dl_dir, $corepatch_file_prefs) { if (! is_array($corepatch_file_prefs)) { // prefs only apply to top level, execept merge which is recursive but only defineable at drupal root level $corepatch_file_prefs['default_action'] = "ignore"; // ignore|replace general approach to directories and files $corepatch_file_prefs['ignore'] = array("profiles","backups","drushrc.php","CVS","sites","files", "images",".htaccess","robots.txt","web.config","favicon.ico"); $corepatch_file_prefs['replace'] = array("includes", "misc", "modules", "themes", "scripts","CHANGELOG.txt","cron.php","index.php","install.php","LICENSE.txt", "MAINTAINERS.txt","update.php","UPGRADE.txt","COPYRIGHT.txt","INSTALL.mysql.txt", "INSTALL.pgsql.txt","INSTALL.txt","xmlrpc.php" ); } if ($dh = opendir($drupal_target_dir)) { while (($file_or_dir = readdir($dh)) !== false) { $target_path = $drupal_target_dir ."/". $file_or_dir; $src_path = $drupal_dl_dir ."/". $file_or_dir; if ($drupal_dl_dir == $target_path) continue; // skip download drupal core if (in_array($file_or_dir, $corepatch_file_prefs['ignore'])) { // do nothing $action = "ignore"; } elseif(in_array($file_or_dir, $corepatch_file_prefs['replace'])) { $action = "replace"; } elseif ($file_prefs['default_action']) { $action = $corepatch_file_prefs['default_action']; } else { $action = "ignore"; // fallback if no default set. } if ($action == 'ignore') continue; if ($action == "replace" && is_dir($target_path)) { corepatch_copy_dir($src_path, $target_path); } elseif ($action == "replace" && is_file($target_path)) { drush_op('unlink', $target_path); drush_op('copy', $src_path, $target_path); } } closedir($dh); } } /** path utitlity functions **/ function corepatch_platform_adjusted_path($path) { if (corepatch_is_windows()) { return corepatch_windows_path($path); } else { return corepatch_default_os_path($path); } } function corepatch_default_os_path($path) { return str_replace("\\","/",$path); } function corepatch_windows_path($path) { return str_replace("/","\\",$path); } function corepatch_is_windows() { return (strcasecmp($_SERVER['OS'], "Windows_NT") === 0); }