Newer
Older
<?php
/*********************************************************************
class.upgrader.php
osTicket Upgrader
Peter Rotich <peter@osticket.com>
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
require_once INCLUDE_DIR.'class.setup.php';
require_once INCLUDE_DIR.'class.migrater.php';
class Upgrader {
function Upgrader($prefix, $basedir) {
global $ost;
$this->streams = array();
foreach (DatabaseMigrater::getUpgradeStreams($basedir) as $stream=>$hash) {
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
$signature = $ost->getConfig()->getSchemaSignature($stream);
$this->streams[$stream] = new StreamUpgrader($signature, $hash, $stream,
$prefix, $basedir.$stream.'/', $this);
}
//Init persistent state of upgrade.
$this->state = &$_SESSION['ost_upgrader']['state'];
$this->mode = &$_SESSION['ost_upgrader']['mode'];
$this->current = &$_SESSION['ost_upgrader']['stream'];
if (!$this->current || $this->getCurrentStream()->isFinished()) {
$streams = array_keys($this->streams);
do {
$this->current = array_shift($streams);
} while ($this->current && $this->getCurrentStream()->isFinished());
}
}
function getCurrentStream() {
return $this->streams[$this->current];
}
function isUpgradable() {
if ($this->isAborted())
return false;
foreach ($this->streams as $s)
if (!$s->isUpgradable())
return false;
return true;
}
function isAborted() {
return !strcasecmp($this->getState(), 'aborted');
}
function abort($msg, $debug=false) {
if ($this->getCurrentStream())
$this->getCurrentStream()->abort($msg, $debug);
}
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
function getState() {
return $this->state;
}
function setState($state) {
$this->state = $state;
}
function getMode() {
return $this->mode;
}
function setMode($mode) {
$this->mode = $mode;
}
function upgrade() {
if (!$this->current)
return true;
return $this->getCurrentStream()->upgrade();
}
function check_prereq() {
if ($this->getCurrentStream())
return $this->getCurrentStream()->check_prereq();
}
function check_php() {
if ($this->getCurrentStream())
return $this->getCurrentStream()->check_php();
}
function check_mysql() {
if ($this->getCurrentStream())
return $this->getCurrentStream()->check_mysql();
}
function getTask() {
if($this->getCurrentStream())
return $this->getCurrentStream()->getTask();
}
function doTask() {
return $this->getCurrentStream()->doTask();
if ($this->getCurrentStream())
return $this->getCurrentStream()->getError();
}
function getNextAction() {
if ($this->getCurrentStream())
return $this->getCurrentStream()->getNextAction();
}
function getNextVersion() {
return $this->getCurrentStream()->getNextVersion();
}
function getSchemaSignature() {
if ($this->getCurrentStream())
return $this->getCurrentStream()->getSchemaSignature();
}
function getSHash() {
if ($this->getCurrentStream())
return $this->getCurrentStream()->getSHash();
}
}
/**
* Updates a single database stream. In the classical sense, osTicket only
* maintained a single database update stream. In that model, this
* represents upgrading that single stream. In multi-stream mode,
* customizations and plugins are supported to have their own respective
* database update streams. The Upgrader class is used to coordinate updates
* for all the streams, whereas the work to upgrade each stream is done in
* this class
*/
class StreamUpgrader extends SetupWizard {
var $prefix;
var $sqldir;
var $signature;
var $state;
var $mode;
* schema_signature - (string<hash-hex>) Current database-reflected (via
* target - (stream<hash-hex>) Current stream tip, as reflected by
* streams/<stream>.sig
* stream - (string) Name of the stream (folder)
* prefix - (string) Database table prefix
* sqldir - (string<path>) Path of sql patches
* upgrader - (Upgrader) Parent coordinator of parallel stream updates
*/
function StreamUpgrader($schema_signature, $target, $stream, $prefix, $sqldir, $upgrader) {
$this->signature = $schema_signature;
$this->target = $target;
$this->prefix = $prefix;
$this->sqldir = $sqldir;
$this->errors = array();
$this->mode = 'ajax'; //
$this->upgrader = $upgrader;
$this->name = $stream;
//Disable time limit if - safe mode is set.
if(!ini_get('safe_mode'))
set_time_limit(0);
//Init the task Manager.
if(!isset($_SESSION['ost_upgrader'][$this->getShash()]))
//Tasks to perform - saved on the session.
$this->phash = &$_SESSION['ost_upgrader']['phash'];
//Database migrater
$this->migrater = null;
}
function onError($error) {
global $ost, $thisstaff;
$subject = '['.$this->name.']: Upgrader Error';
$ost->logError($subject, $error);
//Alert staff upgrading the system - if the email is not same as admin's
// admin gets alerted on error log (above)
if(!$thisstaff || !strcasecmp($thisstaff->getEmail(), $ost->getConfig()->getAdminEmail()))
return;
$email=null;
if(!($email=$ost->getConfig()->getAlertEmail()))
$email=$ost->getConfig()->getDefaultEmail(); //will take the default email.
if($email) {
$email->sendAlert($thisstaff->getEmail(), $subject, $error);
} else {//no luck - try the system mail.
Mailer::sendmail($thisstaff->getEmail(), $subject, $error, sprintf('"osTicket Alerts"<%s>', $thisstaff->getEmail()));
}
function isUpgradable() {
}
function getSchemaSignature() {
return $this->signature;
}
function getShash() {
return substr($this->getSchemaSignature(), 0, 8);
}
function getTablePrefix() {
return $this->prefix;
}
function getSQLDir() {
return $this->sqldir;
}
function getMigrater() {
if(!$this->migrater)
$this->migrater = new DatabaseMigrater($this->signature, $this->target, $this->sqldir);
return $this->migrater;
}
$patches = array();
if($this->getMigrater())
$patches = $this->getMigrater()->getPatches();
return $patches;
}
function getNextPatch() {
return (($p=$this->getPatches()) && count($p)) ? $p[0] : false;
}
function getNextVersion() {
if(!$patch=$this->getNextPatch())
return '(Latest)';
$info = $this->readPatchInfo($patch);
return $info['version'];
}
function isFinished() {
# TODO: 1. Check if current and target hashes match,
# 2. Any pending tasks
return !($this->getNextPatch() || $this->getPendingTask());
function readPatchInfo($patch) {
if (preg_match(':/\*\*(.*)\*/:s', file_get_contents($patch), $matches)) {
if (preg_match_all('/@([\w\d_-]+)\s+(.*)$/m', $matches[0],
$matches2, PREG_SET_ORDER))
}
if (!isset($info['version']))
$info['version'] = substr(basename($patch), 9, 8);
return $info;
}
function getNextAction() {
$action='Upgrade osTicket to '.$this->getVersion();
if($task=$this->getTask()) {
$action = $task->getDescription() .' ('.$task->getStatus().')';
} elseif($this->isUpgradable() && ($nextversion = $this->getNextVersion())) {
$action = "Upgrade to $nextversion";
}
if ($task=$this->getTask())
return ($task->isFinished()) ? 1 : 0;
$task_file = $this->getSQLDir() . "{$this->phash}.task.php";
if (!file_exists($task_file))
if (!isset($this->task)) {
$class = (include $task_file);
if (!is_string($class) || !class_exists($class))
return $ost->logError("{$this->phash}:{$class}: Bogus migration task");
$this->task = new $class();
if (isset($_SESSION['ost_upgrader']['task'][$this->phash]))
$this->task->wakeup($_SESSION['ost_upgrader']['task'][$this->phash]);
}
return $this->task;
if(!($task = $this->getTask()))
return false; //Nothing to do.
$ost->logDebug(
sprintf('Upgrader - %s (task pending).', $this->getShash()),
sprintf('The %s task reports there is work to do',
get_class($task))
);
if(!($max_time = ini_get('max_execution_time')))
$max_time = 30; //Default to 30 sec batches.
$task->run($max_time);
if (!$task->isFinished()) {
$_SESSION['ost_upgrader']['task'][$this->phash] = $task->sleep();
return true;
}
// Run the cleanup script, if any, and destroy the task's session
// data
$this->cleanup();
unset($_SESSION['ost_upgrader']['task'][$this->phash]);
unset($this->phash);
return false;
global $ost;
if($this->getPendingTask() || !($patches=$this->getPatches()))
$start_time = Misc::micro_time();
if(!($max_time = ini_get('max_execution_time')))
$max_time = 300; //Apache/IIS defaults.
// Apply up to five patches at a time
foreach (array_slice($patches, 0, 5) as $patch) {
//TODO: check time used vs. max execution - break if need be
if (!$this->load_sql_file($patch, $this->getTablePrefix()))
return false;
//clear previous patch info -
unset($_SESSION['ost_upgrader'][$this->getShash()]);
$shash = substr($phash, 9, 8);
$logMsg = "Patch $phash applied successfully ";
if(($info = $this->readPatchInfo($patch)) && $info['version'])
$logMsg.= ' ('.$info['version'].') ';
$ost->logDebug("Upgrader - $shash applied", $logMsg);
$this->signature = $shash; //Update signature to the *new* HEAD
//Break IF elapsed time is greater than 80% max time allowed.
if (!($task=$this->getTask())) {
$this->cleanup();
if (($elapsedtime=(Misc::micro_time()-$start_time))
&& $max_time && $elapsedtime>($max_time*0.80))
//We have work to do... set the tasks and break.
$_SESSION['ost_upgrader'][$shash]['state'] = 'upgrade';
break;
}
//Reset the migrater
$this->migrater = null;
$file = $this->getSQLDir().$this->phash.'.cleanup.sql';
if(!file_exists($file)) //No cleanup script.
return 0;
//We have a cleanup script ::XXX: Don't abort on error?
if($this->load_sql_file($file, $this->getTablePrefix(), false, true))
return 0;
$ost->logDebug('Upgrader', sprintf("%s: Unable to process cleanup file",