Skip to content
Snippets Groups Projects
class.upgrader.php 11.4 KiB
Newer Older
<?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($thisstaff->getEmail(), $subject, $error, null, array('bulk' => true));
        } else {//no luck - try the system mail.
Peter Rotich's avatar
Peter Rotich committed
            Mailer::sendmail($thisstaff->getEmail(), $subject, $error, sprintf('"osTicket Alerts"<%s>', $thisstaff->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;



    }