Helpdesk da PluGzOne, baseado no osTicket
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

437 lines
14 KiB

<?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.
// Drop any model meta cache to ensure model changes do not cause
// crashes
ModelMeta::flushModelCache();
// 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;
}
}
?>