Skip to content
Snippets Groups Projects
class.upgrader.php 11.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • <?php
    /*********************************************************************
        class.upgrader.php
    
        osTicket Upgrader
    
        Peter Rotich <peter@osticket.com>
        Copyright (c)  2006-2012 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:
    **********************************************************************/
    
    
    Peter Rotich's avatar
    Peter Rotich committed
    require_once INCLUDE_DIR.'class.setup.php';
    require_once INCLUDE_DIR.'class.migrater.php';
    
    
    class Upgrader extends SetupWizard {
    
        var $prefix;
        var $sqldir;
        var $signature;
    
        function Upgrader($signature, $prefix, $sqldir) {
    
            $this->signature = $signature;
            $this->shash = substr($signature, 0, 8);
            $this->prefix = $prefix;
            $this->sqldir = $sqldir;
            $this->errors = array();
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            //Disable time limit if - safe mode is set.
            if(!ini_get('safe_mode'))
                set_time_limit(0);
    
    
            //Init persistent state of upgrade.
    
    Peter Rotich's avatar
    Peter Rotich committed
            $this->state = &$_SESSION['ost_upgrader']['state'];
    
    
            //Init the task Manager.
            if(!isset($_SESSION['ost_upgrader'][$this->getShash()]))
                $_SESSION['ost_upgrader'][$this->getShash()]['tasks']=array();
    
            //Tasks to perform - saved on the session.
            $this->tasks = &$_SESSION['ost_upgrader'][$this->getShash()]['tasks'];
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            //Database migrater 
            $this->migrater = new DatabaseMigrater($this->signature, SCHEMA_SIGNATURE, $this->sqldir);
    
        }
    
        function onError($error) {
    
            global $ost, $thisstaff;
    
            $ost->logError('Upgrader Error', $error);
    
            $this->setError($error);
            $this->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.
    
            $subject = 'Upgrader Error';
            if($email) {
                $email->send($thistaff->getEmail(), $subject, $error);
            } else {//no luck - try the system mail.
    
                Mailer::sendmail($thistaff->getEmail(), $subject, $error, sprintf('"osTicket Alerts"<%s>', $thistaff->getEmail()));
    
        }
    
        function isUpgradable() {
            return (!$this->isAborted() && $this->getNextPatch());
        }
    
        function isAborted() {
            return !strcasecmp($this->getState(), 'aborted');
        }
    
        function getSchemaSignature() {
            return $this->signature;
        }
    
        function getShash() {
            return $this->shash;
        }
    
        function getTablePrefix() {
            return $this->prefix;
        }
    
        function getSQLDir() {
            return $this->sqldir;
        }
    
        function getState() {
            return $this->state;
        }
    
        function setState($state) {
            $this->state = $state;
        }
    
        function getPatches() {
    
    Peter Rotich's avatar
    Peter Rotich committed
            return $this->migrater->getPatches();
    
        }
    
        function getNextPatch() {
            $p = $this->getPatches();
            return (count($p)) ? $p[0] : false;
        }
    
        function getNextVersion() {
            if(!$patch=$this->getNextPatch())
                return '(Latest)';
    
            $info = $this->readPatchInfo($patch);
            return $info['version'];
        }
    
        function readPatchInfo($patch) {
            $info = array();
            if (preg_match('/\*(.*)\*/', file_get_contents($patch), $matches)) {
                if (preg_match('/@([\w\d_-]+)\s+(.*)$/', $matches[0], $matches2))
                    foreach ($matches2 as $match)
                        $info[$match[0]] = $match[1];
            }
            if (!isset($info['version']))
                $info['version'] = substr(basename($patch), 9, 8);
            return $info;
        }
    
        function getNextAction() {
    
            $action='Upgrade osTicket to '.$this->getVersion();
            if($this->getNumPendingTasks() && ($task=$this->getNextTask())) {
                $action = $task['desc'];
                if($task['status']) //Progress report... 
                    $action.=' ('.$task['status'].')';
            } elseif($this->isUpgradable() && ($nextversion = $this->getNextVersion())) {
                $action = "Upgrade to $nextversion";
            }
    
            return $action;
        }
    
        function getNumPendingTasks() {
    
            return count($this->getPendingTasks());
        }
    
        function getPendingTasks() {
    
            $pending=array();
            if(($tasks=$this->getTasks())) {
                foreach($tasks as $k => $task) {
                    if(!$task['done'])
                        $pending[$k] = $task;
                }  
            }
            
            return $pending;
        }
    
        function getTasks() {
           return $this->tasks;
        }
    
        function getNextTask() {
    
            if(!($tasks=$this->getPendingTasks()))
                return null;
    
            return current($tasks);
        }
    
        function removeTask($tId) {
    
            if(isset($this->tasks[$tId]))
                unset($this->tasks[$tId]);
    
            return (!$this->tasks[$tId]);
        }
    
        function setTaskStatus($tId, $status) {
            if(isset($this->tasks[$tId]))
                $this->tasks[$tId]['status'] = $status;
        }
    
        function doTasks() {
    
    
            if(!($tasks=$this->getPendingTasks()))
                return true; //Nothing to do.
    
    
            $ost->logDebug('Upgrader', sprintf('There are %d pending upgrade tasks', count($tasks)));
    
    Peter Rotich's avatar
    Peter Rotich committed
            $start_time = Misc::micro_time();
    
            foreach($tasks as $k => $task) {
    
    Peter Rotich's avatar
    Peter Rotich committed
                //TODO: check time used vs. max execution - break if need be
    
                if(call_user_func(array($this, $task['func']), $k)===0) {
                    $this->tasks[$k]['done'] = true;
                } else { //Task has pending items to process.
                    break;
                }
            }
    
    
            return $this->getPendingTasks();
    
        }
        
        function upgrade() {
    
    
            if($this->getPendingTasks() || !($patches=$this->getPatches()))
                return false;
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            $start_time = Misc::micro_time();
            if(!($max_time = ini_get('max_execution_time')))
                $max_time = 300; //Apache/IIS defaults.
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            foreach ($patches as $patch) {
    
    Peter Rotich's avatar
    Peter Rotich committed
                //TODO: check time used vs. max execution - break if need be
    
                if (!$this->load_sql_file($patch, $this->getTablePrefix()))
                    return false;
    
    
    Peter Rotich's avatar
    Peter Rotich committed
                //clear previous patch info - 
    
                unset($_SESSION['ost_upgrader'][$this->getShash()]);
    
    Peter Rotich's avatar
    Peter Rotich committed
    
                $phash = substr(basename($patch), 0, 17);
    
    
                //Log the patch info
                $logMsg = "Patch $phash applied ";
                if(($info = $this->readPatchInfo($patch)) && $info['version'])
                    $logMsg.= ' ('.$info['version'].') ';
    
    
                $ost->logDebug('Upgrader - Patch applied', $logMsg);
    
                
                //Check if the said patch has scripted tasks
    
    Peter Rotich's avatar
    Peter Rotich committed
                if(!($tasks=$this->getTasksForPatch($phash))) {
                    //Break IF elapsed time is greater than 80% max time allowed.
                    if(($elapsedtime=(Misc::micro_time()-$start_time)) && $max_time && $elapsedtime>($max_time*0.80))
                        break;
    
    
    Peter Rotich's avatar
    Peter Rotich committed
                    continue;
    
    
                //We have work to do... set the tasks and break.
    
    Peter Rotich's avatar
    Peter Rotich committed
                $shash = substr($phash, 9, 8);
                $_SESSION['ost_upgrader'][$shash]['tasks'] = $tasks;
                $_SESSION['ost_upgrader'][$shash]['state'] = 'upgrade';
    
                
                $ost->logDebug('Upgrader', sprintf('Found %d tasks to be executed for %s',
                                count($tasks), $shash));
    
    Peter Rotich's avatar
    Peter Rotich committed
                break;
    
    Peter Rotich's avatar
    Peter Rotich committed
            }
    
            return true;
    
        }
    
        function getTasksForPatch($phash) {
    
    
            $tasks=array();
            switch($phash) { //Add  patch specific scripted tasks.
    
    Peter Rotich's avatar
    Peter Rotich committed
                case 'c00511c7-7be60a84': //V1.6 ST- 1.7 * {{MD5('1.6 ST') -> c00511c7c1db65c0cfad04b4842afc57}}
    
                    $tasks[] = array('func' => 'migrateSessionFile2DB',
                                     'desc' => 'Transitioning to db-backed sessions');
    
                case '98ae1ed2-e342f869': //v1.6 RC1-4 -> v1.6 RC5
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $tasks[] = array('func' => 'migrateAPIKeys',
                                     'desc' => 'Migrating API keys to a new table');
                    break;
    
                case '435c62c3-2e7531a2':
    
    Peter Rotich's avatar
    Peter Rotich committed
                    $tasks[] = array('func' => 'migrateGroupDeptAccess',
                                     'desc' => 'Migrating group\'s department access to a new table');
    
                case '15b30765-dd0022fb':
                    $tasks[] = array('func' => 'migrateAttachments2DB',
                                     'desc' => 'Migrating attachments to database, it might take a while depending on the number of files.');
                    break;
    
    Peter Rotich's avatar
    Peter Rotich committed
            //Check IF SQL cleanup exists. 
    
    Peter Rotich's avatar
    Peter Rotich committed
            $file=$this->getSQLDir().$phash.'.cleanup.sql';
            if(file_exists($file)) 
    
    Peter Rotich's avatar
    Peter Rotich committed
                $tasks[] = array('func' => 'cleanup',
                                 'desc' => 'Post-upgrade cleanup!',
                                 'phash' => $phash);
    
    Peter Rotich's avatar
    Peter Rotich committed
            return $tasks;
    
    Peter Rotich's avatar
    Peter Rotich committed
        /************* TASKS **********************/
    
    Peter Rotich's avatar
    Peter Rotich committed
        function cleanup($taskId) {
    
            global $ost;
    
            $phash = $this->tasks[$taskId]['phash'];
            $file=$this->getSQLDir().$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",
                            $phash));
            return 0;
    
    Peter Rotich's avatar
    Peter Rotich committed
        function migrateAttachments2DB($taskId) {
    
    Peter Rotich's avatar
    Peter Rotich committed
            
    
    Peter Rotich's avatar
    Peter Rotich committed
            if(!($max_time = ini_get('max_execution_time')))
                $max_time = 30; //Default to 30 sec batches.
    
    
            $att_migrater = new AttachmentMigrater();
    
    Peter Rotich's avatar
    Peter Rotich committed
            if($att_migrater->do_batch(($max_time*0.9), 100)===0)
                return 0;
    
            return $att_migrater->getQueueLength();
    
    
        function migrateSessionFile2DB($taskId) {
            # How about 'dis for a hack?
            osTicketSession::write(session_id(), session_encode()); 
            return 0;
        }
    
    
        function migrateAPIKeys($taskId) {
    
            $res = db_query('SELECT api_whitelist, api_key FROM '.CONFIG_TABLE.' WHERE id=1');
            if(!$res || !db_num_rows($res))
                return 0;  //Reporting success.
    
            list($whitelist, $key) = db_fetch_row($res);
    
    
    Peter Rotich's avatar
    Peter Rotich committed
            $ips=array_filter(array_map('trim', explode(',', $whitelist)));
    
            foreach($ips as $ip) {
                $sql='INSERT INTO '.API_KEY_TABLE.' SET created=NOW(), updated=NOW(), isactive=1 '
                    .',ipaddr='.db_input($ip)
                    .',apikey='.db_input(strtoupper(md5($ip.md5($key))));
                db_query($sql);
            }
    
            return 0;
        }
    
    Peter Rotich's avatar
    Peter Rotich committed
    
        function migrateGroupDeptAccess($taskId) {
    
            $res = db_query('SELECT group_id, dept_access FROM '.GROUP_TABLE);
            if(!$res || !db_num_rows($res))
                return 0;  //No groups??
    
            while(list($groupId, $access) = db_fetch_row($res)) {
                $depts=array_filter(array_map('trim', explode(',', $access)));
                foreach($depts as $deptId) {
                    $sql='INSERT INTO '.GROUP_DEPT_TABLE
                        .' SET dept_id='.db_input($deptId).', group_id='.db_input($groupId);
                    db_query($sql);
                }
            }
    
            return 0;
    
    
    
        }