/include/class.upgrader.php
PHP | 433 lines | 354 code | 37 blank | 42 comment | 20 complexity | a66ef3f4e77debf5c7257bc778b948b0 MD5 | raw file
- <?php
- /*********************************************************************
- class.upgrader.php
- osTicket Upgrader
- Peter Rotich <peter@osticket.com>
- Copyright (c) 2006-2013 osTicket
- 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 __construct($prefix, $basedir) {
- global $ost;
- $this->streams = array();
- foreach (DatabaseMigrater::getUpgradeStreams($basedir) as $stream=>$hash) {
- $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);
- }
- function getState() {
- return $this->state;
- }
- function setState($state) {
- $this->state = $state;
- if ($state == 'done') {
- ModelMeta::flushModelCache();
- $this->createUpgradedTicket();
- }
- }
- function createUpgradedTicket() {
- global $cfg;
- $i18n = new Internationalization();
- $vars = $i18n->getTemplate('templates/ticket/upgraded.yaml')->getData();
- $vars['deptId'] = $cfg->getDefaultDeptId();
- //Create a ticket to make the system warm and happy.
- $errors = array();
- Ticket::create($vars, $errors, 'api', false, false);
- }
- function getMode() {
- return $this->mode;
- }
- function setMode($mode) {
- $this->mode = $mode;
- }
- function upgrade() {
- if (!$this->current)
- return true;
- return $this->getCurrentStream()->upgrade();
- }
- function __call($what, $args) {
- if ($this->getCurrentStream()) {
- $callable = array($this->getCurrentStream(), $what);
- if (!is_callable($callable))
- throw new Exception('InternalError: Upgrader method not callable: '
- . $what);
- return call_user_func_array($callable, $args);
- }
- }
- }
- /**
- * 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;
- var $phash;
- /**
- * Parameters:
- * schema_signature - (string<hash-hex>) Current database-reflected (via
- * config table) version of the stream
- * 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 __construct($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()]))
- $_SESSION['ost_upgrader']['task'] = array();
- //Tasks to perform - saved on the session.
- $this->phash = &$_SESSION['ost_upgrader']['phash'];
- //Database migrater
- $this->migrater = null;
- }
- function check_prereq() {
- return (parent::check_prereq() && $this->check_mysql_version());
- }
- function onError($error) {
- global $ost, $thisstaff;
- $subject = '['.$this->name.']: '._S('Upgrader Error');
- $ost->logError($subject, $error);
- $this->setError($error);
- $this->upgrader->setState('aborted');
- //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,
- '"'._S('osTicket Alerts')."\" <{$thisstaff->getEmail()}>");
- }
- }
- function isUpgradable() {
- return $this->getNextPatch();
- }
- 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;
- }
- function getPatches() {
- $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) {
- $info = $matches = $matches2 = array();
- if (preg_match(':/\*\*(.*)\*/:s', file_get_contents($patch), $matches)) {
- if (preg_match_all('/@([\w\d_-]+)\s+(.*)$/m', $matches[0],
- $matches2, PREG_SET_ORDER))
- foreach ($matches2 as $match)
- $info[$match[1]] = $match[2];
- }
- if (!isset($info['version']))
- $info['version'] = substr(basename($patch), 9, 8);
- return $info;
- }
- function getUpgradeSummary() {
- $summary = '';
- foreach ($this->getPatches() as $p) {
- $info = $this->readPatchInfo($p);
- $summary .= '<div class="patch">' . $info['version'];
- if (isset($info['title']))
- $summary .= ': <span class="patch-title">'.$info['title']
- .'</span>';
- $summary .= '</div>';
- }
- return $summary;
- }
- function getNextAction() {
- $action=sprintf(__('Upgrade osTicket to %s'), $this->getVersion());
- if($task=$this->getTask()) {
- $action = $task->getDescription() .' ('.$task->getStatus().')';
- } elseif($this->isUpgradable() && ($nextversion = $this->getNextVersion())) {
- $action = sprintf(__("Upgrade to %s"),$nextversion);
- }
- return '['.$this->name.'] '.$action;
- }
- function getPendingTask() {
- $pending=array();
- if (($task=$this->getTask()) && ($task instanceof MigrationTask))
- return ($task->isFinished()) ? 1 : 0;
- return false;
- }
- function getTask() {
- global $ost;
- $task_file = $this->getSQLDir() . "{$this->phash}.task.php";
- if (!file_exists($task_file))
- return null;
- if (!isset($this->task)) {
- $class = (include $task_file);
- if (!is_string($class) || !class_exists($class))
- return $ost->logError("Bogus migration task",
- "{$this->phash}:{$class}"); //FIXME: This can cause crash
- $this->task = new $class();
- if (isset($_SESSION['ost_upgrader']['task'][$this->phash]))
- $this->task->wakeup($_SESSION['ost_upgrader']['task'][$this->phash]);
- }
- return $this->task;
- }
- function doTask() {
- if(!($task = $this->getTask()))
- return false; //Nothing to do.
- $this->log(
- sprintf(_S('Upgrader - %s (task pending).'), $this->getShash()),
- sprintf(_S('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.
- // Drop any model meta cache to ensure model changes do not cause
- // crashes
- ModelMeta::flushModelCache();
- $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]);
- $this->phash = null;
- unset($this->task);
- return false;
- }
- function upgrade() {
- global $ost;
- if($this->getPendingTask() || !($patches=$this->getPatches()))
- return false;
- $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()]);
- $phash = substr(basename($patch), 0, 17);
- $shash = substr($phash, 9, 8);
- //Log the patch info
- $logMsg = sprintf(_S("Patch %s applied successfully"), $phash);
- if(($info = $this->readPatchInfo($patch)) && $info['version'])
- $logMsg.= ' ('.$info['version'].') ';
- $this->log(sprintf(_S("Upgrader - %s applied"), $shash), $logMsg);
- $this->signature = $shash; //Update signature to the *new* HEAD
- $this->phash = $phash;
- //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))
- break;
- else
- // Apply the next patch
- continue;
- }
- //We have work to do... set the tasks and break.
- $_SESSION['ost_upgrader'][$shash]['state'] = 'upgrade';
- break;
- }
- //Reset the migrater
- $this->migrater = null;
- return true;
- }
- function log($title, $message, $level=LOG_DEBUG) {
- global $ost;
- // Never alert the admin, and force the write to the database
- $ost->log($level, $title, $message, false, true);
- }
- /************* TASKS **********************/
- function cleanup() {
- $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)) {
- $this->log(sprintf(_S("Upgrader - %s cleanup"), $this->phash),
- sprintf(_S("Applied cleanup script %s"), $file));
- return 0;
- }
- $this->log(_S('Upgrader'), sprintf(_S("%s: Unable to process cleanup file"),
- $this->phash));
- return 0;
- }
- }
- ?>