diff --git a/client.inc.php b/client.inc.php
index b714e93b44bd587f4d10a2f292b2d4890bc63a3f..3b450e9e2491653ec44c65f346ef79758cc1d1a8 100644
--- a/client.inc.php
+++ b/client.inc.php
@@ -28,21 +28,12 @@ define('OSTCLIENTINC',TRUE);
 
 define('ASSETS_PATH',ROOT_PATH.'assets/default/');
 
-
 //Check the status of the HelpDesk.
-if(!is_object($cfg) || !$cfg->getId() || $cfg->isHelpDeskOffline()) {
+if(!is_object($cfg) || !$cfg->getId() || $cfg->isHelpDeskOffline() || $cfg->isUpgradePending()) {
     include('./offline.php');
     exit;
 }
 
-//Forced upgrade? Version mismatch.
-if(defined('THIS_VERSION') && strcasecmp($cfg->getVersion(),THIS_VERSION)) {
-    die('System is offline for an upgrade.');
-    exit;
-}
-
-
-
 /* include what is needed on client stuff */
 require_once(INCLUDE_DIR.'class.client.php');
 require_once(INCLUDE_DIR.'class.ticket.php');
diff --git a/include/ajax.config.php b/include/ajax.config.php
index 7dcfd9972717e9c8cd5cb14a217b0e3b361dd9ee..fc9fb2c3fc8622108054052828b7e6bb9a42dcd6 100644
--- a/include/ajax.config.php
+++ b/include/ajax.config.php
@@ -19,7 +19,7 @@ if(!defined('INCLUDE_DIR')) die('!');
 class ConfigAjaxAPI extends AjaxController {
 
     //config info UI might need.
-    function ui() {
+    function scp_ui() {
         global $thisstaff, $cfg;
 
         $config=array('ticket_lock_time'=>($cfg->getLockTime()*3600),
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index 458e430ea83192703bea52c026413af63c4edb63..f67581005194848131dbaf1c709b8c238c8e404a 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -20,11 +20,11 @@ include_once(INCLUDE_DIR.'class.ticket.php');
 
 class TicketsAjaxAPI extends AjaxController {
    
-    function search() {
+    function lookup() {
         global $thisstaff;
 
         if(!is_numeric($_REQUEST['q']))
-            return self::searchByEmail();
+            return self::lookupByEmail();
 
 
         $limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25;
@@ -53,7 +53,7 @@ class TicketsAjaxAPI extends AjaxController {
         return $this->json_encode($tickets);
     }
 
-    function searchByEmail() {
+    function lookupByEmail() {
         global $thisstaff;
 
 
@@ -84,6 +84,100 @@ class TicketsAjaxAPI extends AjaxController {
         return $this->json_encode($tickets);
     }
 
+    function search() {
+        global $thisstaff;
+          
+        $result=array();
+        $select = 'SELECT count(ticket.ticket_id) as tickets ';
+        $from = ' FROM '.TICKET_TABLE.' ticket ';
+        $where = ' WHERE 1 ';
+
+        //Access control.
+        $where.=' AND ( ticket.staff_id='.db_input($thisstaff->getId());
+
+        if(($teams=$thisstaff->getTeams()) && count(array_filter($teams)))
+            $where.=' OR ticket.team_id IN('.implode(',', array_filter($teams)).')';
+
+        if(!$thisstaff->showAssignedOnly() && ($depts=$thisstaff->getDepts()))
+            $where.=' OR ticket.dept_id IN ('.implode(',', $depts).')';
+
+        $where.=' ) ';
+
+        //Department
+        if($_REQUEST['deptId'])
+            $where.=' AND ticket.dept_id='.db_input($_REQUEST['deptId']);
+
+        //Status
+        switch(strtolower($_REQUEST['status'])) {
+            case 'open';
+                $where.=' AND ticket.status="open" ';
+                break;
+            case 'overdue':
+                $where.=' AND ticket.status="open" AND ticket.isoverdue=1 ';
+                break;
+            case 'closed':
+                $where.=' AND ticket.status="closed" ';
+                break;
+        }
+
+        //Assignee 
+        if($_REQUEST['assignee'] && strcasecmp($_REQUEST['status'], 'closed'))  {
+            $id=preg_replace("/[^0-9]/", "", $_REQUEST['assignee']);
+            $assignee = $_REQUEST['assignee'];
+            $where.= ' AND ( ';
+            if($assignee[0]=='t')
+                $where.='  (ticket.team_id='.db_input($id). ' AND ticket.status="open") ';
+            elseif($assignee[0]=='s')
+                $where.='  (ticket.staff_id='.db_input($id). ' AND ticket.status="open") ';
+            else 
+                $where.='  (ticket.staff_id='.db_input($id). ' AND ticket.status="open") ';
+
+            if($_REQUEST['staffId'] && !$_REQUEST['status']) //Assigned TO + Closed By
+                $where.= ' OR (ticket.staff_id='.db_input($_REQUEST['staffId']). ' AND ticket.status="closed") ';    
+
+            $where.= ' ) ';
+        } elseif($_REQUEST['staffId']) { 
+            $where.=' AND (ticket.staff_id='.db_input($_REQUEST['staffId']).' AND ticket.status="closed") ';
+        }
+            
+        //dates
+        $startTime  =($_REQUEST['startDate'] && (strlen($_REQUEST['startDate'])>=8))?strtotime($_REQUEST['startDate']):0;
+        $endTime    =($_REQUEST['endDate'] && (strlen($_REQUEST['endDate'])>=8))?strtotime($_REQUEST['endDate']):0;
+        if( ($startTime && $startTime>time()) or ($startTime>$endTime && $endTime>0))
+            $startTime=$endTime=0;
+        
+        if($startTime)
+            $where.=' AND ticket.created>=FROM_UNIXTIME('.$startTime.')';
+
+        if($endTime)
+            $where.=' AND ticket.created<=FROM_UNIXTIME('.$endTime.')';
+
+        //Query
+        if($_REQUEST['query']) {
+            $queryterm=db_real_escape($_REQUEST['query'], false);
+               
+            $from.=' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON (ticket.ticket_id=thread.ticket_id )';
+            $where.=" AND (  ticket.email LIKE '%$queryterm%'"
+                       ." OR ticket.name LIKE '%$queryterm%'"
+                       ." OR ticket.subject LIKE '%$queryterm%'"
+                       ." OR thread.title LIKE '%$queryterm%'"
+                       ." OR thread.body LIKE '%$queryterm%'"               
+                       .' )';
+            $groupby = 'GROUP BY ticket.ticket_id ';
+        }
+        
+        $sql="$select $from $where $groupby";
+        if(($tickets=db_result(db_query($sql)))) {
+            $result['success'] =sprintf("Search criteria matched %s - <a href='tickets.php?%s'>view</a>",
+                                        ($tickets>1?"$tickets tickets":"$tickets ticket"),
+                                        str_replace(array('&amp;', '&'), array('&', '&amp;'), $_SERVER['QUERY_STRING']));
+        } else {
+            $result['fail']='No tickets found matching your search criteria.';
+        }
+            
+        return $this->json_encode($result);
+    }
+
     function acquireLock($tid) {
         global $cfg,$thisstaff;
         
diff --git a/include/class.config.php b/include/class.config.php
index c51814bebc73aef1ae3a9052db364d2702798c04..95a5204e389b55c161cf192c1af9511793732e70 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -67,22 +67,35 @@ class Config {
         return !$this->isSystemOnline();
     }
 
+    function isHelpDeskOnline() {
+        return $this->isSystemOnline();
+    }
+
     function isSystemOnline() {
-        return ($this->config['isonline']);
+        return ($this->config['isonline'] && !$this->isUpgradePending());
     }
 
-    function isKnowledgebaseEnabled() {
+    function isUpgradePending() {
+        return (defined('SCHEMA_SIGNATURE') && strcasecmp($this->getSchemaSignature(), SCHEMA_SIGNATURE));
+    }
 
+    function isKnowledgebaseEnabled() {
         require_once(INCLUDE_DIR.'class.faq.php');
         return ($this->config['enable_kb'] && FAQ::countPublishedFAQs());
     }
 
     function getVersion() {
-        return '1.7-DPR2';
+        return THIS_VERSION;
     }
 
     function getSchemaSignature() {
-        return $this->config['schema_signature'];
+
+        if($this->config['schema_signature'])
+            return $this->config['schema_signature'];
+        elseif($this->config['ostversion']) //old version 1.6 st.
+            return md5(strtoupper($this->config['ostversion']));
+
+        return null;
     }
 
     function setMysqlTZ($tz) {
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 8eca918fbb101b71f02276b930e62b1bde35a3b1..b9fb218d5d343693307b34020928fbfc5164494a 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -75,18 +75,25 @@ class Ticket{
        
         $sql='SELECT  ticket.*, topic.topic as helptopic, lock_id, dept_name, priority_desc '
             .' ,count(attach.attach_id) as attachments '
-            .' ,count(DISTINCT message.msg_id) as messages '
-            .' ,count(DISTINCT response.response_id) as responses '
-            .' ,count(DISTINCT note.note_id) as notes '
+            .' ,count(DISTINCT message.id) as messages '
+            .' ,count(DISTINCT response.id) as responses '
+            .' ,count(DISTINCT note.id) as notes '
             .' FROM '.TICKET_TABLE.' ticket '
             .' LEFT JOIN '.DEPT_TABLE.' dept ON (ticket.dept_id=dept.dept_id) '
-            .' LEFT JOIN '.TICKET_PRIORITY_TABLE.' pri ON (ticket.priority_id=pri.priority_id) '
-            .' LEFT JOIN '.TOPIC_TABLE.' topic ON (ticket.topic_id=topic.topic_id) '
-            .' LEFT JOIN '.TICKET_LOCK_TABLE.' tlock ON (ticket.ticket_id=tlock.ticket_id AND tlock.expire>NOW()) '
-            .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach ON (ticket.ticket_id=attach.ticket_id) '
-            .' LEFT JOIN '.TICKET_MESSAGE_TABLE.' message ON (ticket.ticket_id=message.ticket_id) '
-            .' LEFT JOIN '.TICKET_RESPONSE_TABLE.' response ON (ticket.ticket_id=response.ticket_id) '
-            .' LEFT JOIN '.TICKET_NOTE_TABLE.' note ON (ticket.ticket_id=note.ticket_id ) '
+            .' LEFT JOIN '.TICKET_PRIORITY_TABLE.' pri ON ('
+                .'ticket.priority_id=pri.priority_id) '
+            .' LEFT JOIN '.TOPIC_TABLE.' topic ON ('
+                .'ticket.topic_id=topic.topic_id) '
+            .' LEFT JOIN '.TICKET_LOCK_TABLE.' tlock ON ('
+                .'ticket.ticket_id=tlock.ticket_id AND tlock.expire>NOW()) '
+            .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach ON ('
+                .'ticket.ticket_id=attach.ticket_id) '
+            .' LEFT JOIN '.TICKET_THREAD_TABLE.' message ON ('
+                ."ticket.ticket_id=message.ticket_id AND message.thread_type = 'M') "
+            .' LEFT JOIN '.TICKET_THREAD_TABLE.' response ON ('
+                ."ticket.ticket_id=response.ticket_id AND response.thread_type = 'R') "
+            .' LEFT JOIN '.TICKET_THREAD_TABLE.' note ON ( '
+                ."ticket.ticket_id=note.ticket_id AND note.thread_type = 'N') "
             .' WHERE ticket.ticket_id='.db_input($id)
             .' GROUP BY ticket.ticket_id';
 
@@ -437,9 +444,10 @@ class Ticket{
     function getLastRespondent() {
 
         $sql ='SELECT  resp.staff_id '
-             .' FROM '.TICKET_RESPONSE_TABLE.' resp '
+             .' FROM '.TICKET_THREAD_TABLE.' resp '
              .' LEFT JOIN '.STAFF_TABLE. ' USING(staff_id) '
              .' WHERE  resp.ticket_id='.db_input($this->getId()).' AND resp.staff_id>0 '
+             .'   AND  resp.thread_type="R"'
              .' ORDER BY resp.created DESC LIMIT 1';
 
         if(!($res=db_query($sql)) || !db_num_rows($res))
@@ -457,8 +465,9 @@ class Ticket{
             return $this->lastmsgdate;
 
         //for old versions...XXX: still needed????
-        $sql='SELECT created FROM '.TICKET_MESSAGE_TABLE
+        $sql='SELECT created FROM '.TICKET_THREAD_TABLE
             .' WHERE ticket_id='.db_input($this->getId())
+            ."   AND thread_type = 'M'"
             .' ORDER BY created DESC LIMIT 1';
         if(($res=db_query($sql)) && db_num_rows($res))
             list($this->lastmsgdate)=db_fetch_row($res);
@@ -475,8 +484,9 @@ class Ticket{
         if($this->lastrespdate)
             return $this->lastrespdate;
 
-        $sql='SELECT created FROM '.TICKET_RESPONSE_TABLE
+        $sql='SELECT created FROM '.TICKET_THREAD_TABLE
             .' WHERE ticket_id='.db_input($this->getId())
+            .'   AND thread_type="R"'
             .' ORDER BY created DESC LIMIT 1';
         if(($res=db_query($sql)) && db_num_rows($res))
             list($this->lastrespdate)=db_fetch_row($res);
@@ -523,11 +533,12 @@ class Ticket{
             $order='DESC';
 
         $sql ='SELECT note.*, count(DISTINCT attach.attach_id) as attachments '
-            .' FROM '.TICKET_NOTE_TABLE.' note '
+            .' FROM '.TICKET_THREAD_TABLE.' note '
             .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach
-                ON (note.ticket_id=attach.ticket_id AND note.note_id=attach.ref_id AND ref_type="N") '
+                ON (note.ticket_id=attach.ticket_id AND note.id=attach.ref_id AND ref_type="N") '
             .' WHERE note.ticket_id='.db_input($this->getId())
-            .' GROUP BY note.note_id '
+            .'   AND note.thread_type="N"'
+            .' GROUP BY note.id '
             .' ORDER BY note.created '.$order;
 
         $notes=array();
@@ -540,14 +551,17 @@ class Ticket{
 
     function getMessages() {
 
-        $sql='SELECT msg.msg_id, msg.created, msg.message '
-            .' ,count(DISTINCT attach.attach_id) as attachments, count( DISTINCT resp.response_id) as responses '
-            .' FROM '.TICKET_MESSAGE_TABLE.' msg '
-            .' LEFT JOIN '.TICKET_RESPONSE_TABLE. ' resp ON(resp.msg_id=msg.msg_id) '
-            .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach 
-                ON (msg.ticket_id=attach.ticket_id AND msg.msg_id=attach.ref_id AND ref_type="M") '
+        $sql='SELECT msg.id, msg.created, msg.body '
+            .' ,count(DISTINCT attach.attach_id) as attachments '
+            .' ,count( DISTINCT resp.id) as responses '
+            .' FROM '.TICKET_THREAD_TABLE.' msg '
+            .' LEFT JOIN '.TICKET_THREAD_TABLE.' resp ON ('
+                .'resp.pid=msg.id AND resp.thread_type = "R") '
+            .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach '
+                .'ON (msg.ticket_id=attach.ticket_id AND msg.id=attach.ref_id AND ref_type="M") '
             .' WHERE  msg.ticket_id='.db_input($this->getId())
-            .' GROUP BY msg.msg_id '
+               .' AND msg.thread_type="M"'
+            .' GROUP BY msg.id '
             .' ORDER BY msg.created ASC ';
 
         $messages=array();
@@ -561,11 +575,12 @@ class Ticket{
     function getResponses($msgId) {
 
         $sql='SELECT resp.*, count(DISTINCT attach.attach_id) as attachments '
-            .' FROM '.TICKET_RESPONSE_TABLE. ' resp '
+            .' FROM '.TICKET_THREAD_TABLE. ' resp '
             .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach 
-                ON (resp.ticket_id=attach.ticket_id AND resp.response_id=attach.ref_id AND ref_type="R") '
+                ON (resp.ticket_id=attach.ticket_id AND resp.id=attach.ref_id AND ref_type="R") '
             .' WHERE  resp.ticket_id='.db_input($this->getId())
-            .' GROUP BY resp.response_id '
+            .'   AND  resp.thread_type="R"'
+            .' GROUP BY resp.id '
             .' ORDER BY resp.created';
 
         $responses=array();
@@ -654,10 +669,14 @@ class Ticket{
     //Set staff ID...assign/unassign/release (id can be 0)
     function setStaffId($staffId){
        
-      $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW(), staff_id='.db_input($staffId)
-          .' WHERE ticket_id='.db_input($this->getId());
+        $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW(), staff_id='.db_input($staffId)
+            .' WHERE ticket_id='.db_input($this->getId());
 
-      return (db_query($sql)  && db_affected_rows());
+        if (db_query($sql)  && db_affected_rows()) {
+            $this->staff_id = $staffId;
+            return true;
+        }
+        return false;
     }
 
     function setSLAId($slaId) {
@@ -769,7 +788,7 @@ class Ticket{
 
         $sql.=' WHERE ticket_id='.db_input($this->getId());
 
-        $this->track('closed');
+        $this->logEvent('closed');
         return (db_query($sql) && db_affected_rows());
     }
 
@@ -783,7 +802,7 @@ class Ticket{
 
         //TODO: log reopen event here 
 
-        $this->track('reopened');
+        $this->logEvent('reopened');
         return (db_query($sql) && db_affected_rows());
     }
 
@@ -1128,8 +1147,8 @@ class Ticket{
         if(!db_query($sql) || !db_affected_rows())
             return false;
 
+        $this->logEvent('overdue');
         $this->onOverdue($whine);
-        $this->track('overdue');
 
         return true;
     }
@@ -1150,6 +1169,7 @@ class Ticket{
              $this->reopen();
         
          $this->reload(); //reload - new dept!!
+         $this->logEvent('transferred');
 
          //Send out alerts if enabled AND requested
          if(!$alert || !$cfg->alertONTransfer() || !($dept=$this->getDept())) return true; //no alerts!!
@@ -1196,7 +1216,6 @@ class Ticket{
             }
          }
 
-         $this->track('transferred');
          return true;
     }
 
@@ -1209,8 +1228,8 @@ class Ticket{
             return false;
 
         $this->onAssign($note, $alert);
+        $this->logEvent('assigned');
 
-        $this->track('assigned');
         return true;
     }
 
@@ -1228,8 +1247,8 @@ class Ticket{
             $this->setStaffId(0);
 
         $this->onAssign($note, $alert);
+        $this->logEvent('assigned');
 
-        $this->track('assigned');
         return true;
     }
 
@@ -1270,18 +1289,18 @@ class Ticket{
     }
 
     //Insert message from client
-    function postMessage($msg,$source='',$msgid=NULL,$headers='',$newticket=false){
+    function postMessage($msg,$source='',$emsgid=null,$headers='',$newticket=false){
         global $cfg;
        
         if(!$this->getId()) return 0;
         
         # XXX: Refuse auto-response messages? (via email) XXX: No - but kill our auto-responder.
 
-        $sql='INSERT INTO '.TICKET_MESSAGE_TABLE.' SET created=NOW() '
+        $sql='INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW()'
+            .' ,thread_type="M" '
             .' ,ticket_id='.db_input($this->getId())
-            .' ,messageId='.db_input($msgid)
-            .' ,message='.db_input(Format::striptags($msg)) //Tags/code stripped...meaning client can not send in code..etc
-            .' ,headers='.db_input($headers) //Raw header.
+            # XXX: Put Subject header into the 'title' field
+            .' ,body='.db_input(Format::striptags($msg)) //Tags/code stripped...meaning client can not send in code..etc
             .' ,source='.db_input($source?$source:$_SERVER['REMOTE_ADDR'])
             .' ,ip_address='.db_input($_SERVER['REMOTE_ADDR']);
     
@@ -1289,6 +1308,15 @@ class Ticket{
 
         $this->setLastMsgId($msgid);
 
+        if ($emsgid !== null) {
+            $sql='INSERT INTO '.TICKET_EMAIL_INFO_TABLE
+                .' SET msg_id='.db_input($msgid)
+                .', email_mid='.db_input($emsgid)
+                .', headers='.db_input($headers);
+
+            if (!db_query($sql)) return 0;
+        }
+
         if($newticket) return $msgid; //Our work is done...
 
         $autorespond = true;
@@ -1353,10 +1381,11 @@ class Ticket{
 
         if($errors) return 0;
 
-        $sql='INSERT INTO '.TICKET_RESPONSE_TABLE.' SET created=NOW() '
+        $sql='INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW() '
+            .' ,thread_type="R"'
             .' ,ticket_id='.db_input($this->getId())
-            .' ,msg_id='.db_input($vars['msgId'])
-            .' ,response='.db_input(Format::striptags($vars['response']))
+            .' ,pid='.db_input($vars['msgId'])
+            .' ,body='.db_input(Format::striptags($vars['response']))
             .' ,staff_id='.db_input($thisstaff->getId())
             .' ,staff_name='.db_input($thisstaff->getName())
             .' ,ip_address='.db_input($thisstaff->getIP());
@@ -1430,11 +1459,11 @@ class Ticket{
         if(!$cfg || !$cfg->logTicketActivity())
             return 0;
 
-        return $this->postNote($title,$note,false,'system');
+        return $this->postNote($title,$note,false,'System');
     }
 
     // History log -- used for statistics generation (pretty reports)
-    function track($state, $staff=null) {
+    function logEvent($state, $staff=null) {
         global $thisstaff;
 
         if ($staff === null) {
@@ -1442,8 +1471,12 @@ class Ticket{
             else $staff='SYSTEM';               # XXX: Security Violation ?
         }
 
-        return db_query('INSERT INTO '.TICKET_HISTORY_TABLE
+        return db_query('INSERT INTO '.TICKET_EVENT_TABLE
             .' SET ticket_id='.db_input($this->getId())
+            .', staff_id='.db_input($this->getStaffId())
+            .', team_id='.db_input($this->getTeamId())
+            .', dept_id='.db_input($this->getDeptId())
+            .', topic_id='.db_input($this->getTopicId())
             .', timestamp=NOW(), state='.db_input($state)
             .', staff='.db_input($staff))
             && db_affected_rows() == 1;
@@ -1453,12 +1486,13 @@ class Ticket{
     function postNote($title,$note,$alert=true,$poster='') {        
         global $thisstaff,$cfg;
 
-        $sql= 'INSERT INTO '.TICKET_NOTE_TABLE.' SET created=NOW() '.
+        $sql= 'INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW() '.
+                ',thread_type="N"'.
                 ',ticket_id='.db_input($this->getId()).
                 ',title='.db_input(Format::striptags($title)).
-                ',note='.db_input(Format::striptags($note)).
+                ',body='.db_input(Format::striptags($note)).
                 ',staff_id='.db_input($thisstaff?$thisstaff->getId():0).
-                ',source='.db_input(($poster || !$thisstaff)?$poster:$thisstaff->getName());
+                ',poster='.db_input(($poster || !$thisstaff)?$poster:$thisstaff->getName());
         //echo $sql;
         if(!db_query($sql) || !($id=db_insert_id()))
             return false;
@@ -1560,9 +1594,7 @@ class Ticket{
         if(!db_query($sql) || !db_affected_rows())
             return false;
 
-        db_query('DELETE FROM '.TICKET_MESSAGE_TABLE.' WHERE ticket_id='.db_input($this->getId()));
-        db_query('DELETE FROM '.TICKET_RESPONSE_TABLE.' WHERE ticket_id='.db_input($this->getId()));
-        db_query('DELETE FROM '.TICKET_NOTE_TABLE.' WHERE ticket_id='.db_input($this->getId()));
+        db_query('DELETE FROM '.TICKET_THREAD_TABLE.' WHERE ticket_id='.db_input($this->getId()));
         $this->deleteAttachments();
         
         return true;
@@ -1678,8 +1710,9 @@ class Ticket{
             return 0;
 
         $sql='SELECT ticket.ticket_id FROM '.TICKET_TABLE. ' ticket '.
-             ' LEFT JOIN '.TICKET_MESSAGE_TABLE.' msg USING(ticket_id) '.
-             ' WHERE messageId='.db_input($mid).' AND email='.db_input($email);
+             ' LEFT JOIN '.TICKE_THREAD_TABLE.' msg USING(ticket_id) '.
+             ' INNER JOIN '.TICKET_EMAIL_INFO_TABLE.' emsg ON (msg.id = emsg.message_id) '.
+             ' WHERE email_mid='.db_input($mid).' AND email='.db_input($email);
         $id=0;
         if(($res=db_query($sql)) && db_num_rows($res))
             list($id)=db_fetch_row($res);
@@ -1962,6 +1995,9 @@ class Ticket{
                     && ($client->getNumOpenTickets()==$cfg->getMaxOpenTickets())) {
             $ticket->onOpenLimit(($autorespond && strcasecmp($origin, 'staff')));
         }
+        
+        /* Start tracking ticket lifecycle events */
+        $ticket->logEvent('created');
 
         /* Phew! ... time for tea (KETEPA) */
 
diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php
index a360217167bde83dedcb4decb339677e3c5370d6..7719ec74a7ef2a58babcae7112a0d2cb05caa92e 100644
--- a/include/client/tickets.inc.php
+++ b/include/client/tickets.inc.php
@@ -59,13 +59,12 @@ if($search) {
         $queryterm=db_real_escape($_REQUEST['q'],false); //escape the term ONLY...no quotes.
         $qwhere.=' AND ( '
                 ." ticket.subject LIKE '%$queryterm%'"
-                ." OR message.message LIKE '%$queryterm%'"
-                ." OR response.response LIKE '%$queryterm%'"
+                ." OR thread.body LIKE '%$queryterm%'"
                 .' ) ';
         $deep_search=true;
         //Joins needed for search
-        $qfrom.=' LEFT JOIN '.TICKET_MESSAGE_TABLE.' message ON (ticket.ticket_id=message.ticket_id )'
-               .' LEFT JOIN '.TICKET_RESPONSE_TABLE.' response ON (ticket.ticket_id=response.ticket_id )';
+        $qfrom.=' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON ('
+               .'ticket.ticket_id=thread.ticket_id AND thread.thread_type IN ("M","R"))';
     }
 }
 
diff --git a/include/client/view.inc.php b/include/client/view.inc.php
index e720ed96bffa58c5d7321e7fe2544c7829a86678..f67e6d94bd3a4456e30b17797254dff49204b960 100644
--- a/include/client/view.inc.php
+++ b/include/client/view.inc.php
@@ -67,11 +67,11 @@ if($ticket->getThreadCount() && ($messages = $ticket->getMessages())) {
         
             <tr><th><?php echo Format::db_datetime($message['created']); ?></th></tr>
             
-            <tr><td><?php echo Format::display($message['message']); ?></td></tr>
+            <tr><td><?php echo Format::display($message['body']); ?></td></tr>
             
             <?php
             
-            if($message['attachments'] && ($links=$ticket->getAttachmentsLinks($message['msg_id'],'M'))) { ?>
+            if($message['attachments'] && ($links=$ticket->getAttachmentsLinks($message['id'],'M'))) { ?>
             
                 <tr><td class="info"><?php echo $links; ?></td></tr>
                 
@@ -81,7 +81,7 @@ if($ticket->getThreadCount() && ($messages = $ticket->getMessages())) {
             
         </table>
         <?php
-        if($message['responses'] && ($responses=$ticket->getResponses($message['msg_id']))) {
+        if($message['responses'] && ($responses=$ticket->getResponses($message['id']))) {
            foreach($responses as $resp) {
                $staff=$cfg->hideStaffName()?'staff':Format::htmlchars($resp['staff_name']);
                ?>
@@ -89,9 +89,9 @@ if($ticket->getThreadCount() && ($messages = $ticket->getMessages())) {
                 <tr>
                     <th><?php echo Format::db_datetime($resp['created']);?>&nbsp;-&nbsp;<?php echo $staff; ?></th>
                 </tr>
-                <tr><td><?php echo Format::display($resp['response']); ?></td></tr>
+                <tr><td><?php echo Format::display($resp['body']); ?></td></tr>
                 <?php
-                if($resp['attachments'] && ($links=$ticket->getAttachmentsLinks($resp['response_id'],'R'))) {?>
+                if($resp['attachments'] && ($links=$ticket->getAttachmentsLinks($resp['id'],'R'))) {?>
                  <tr><td class="info"><?php echo $links; ?></td></tr>
                 <?php
                  }?>
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index 33f46eadd443ccf0cda94867d0e54a924b73e30e..faac0f86ef791a8726c266a8121a980860ddc267 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -196,11 +196,11 @@ if($ticket->isOverdue())
             </tr>
             <tr>
                 <td colspan="2">
-                    <?php echo Format::htmlchars($note['note']); ?>
+                    <?php echo Format::htmlchars($note['body']); ?>
                 </td>
             </tr>
             <?php
-            if($note['attachments'] && ($links=$ticket->getAttachmentsLinks($note['note_id'],'N'))) {?>
+            if($note['attachments'] && ($links=$ticket->getAttachmentsLinks($note['id'],'N'))) {?>
             <tr>
                 <td class="info" colspan="2"><?php echo $links; ?></td>
             </tr>
@@ -220,9 +220,9 @@ if($ticket->isOverdue())
        foreach($messages as $message) {?>
         <table class="message" cellspacing="0" cellpadding="1" width="940" border="0">
             <tr><th><?php echo Format::db_datetime($message['created']); ?></th></tr>
-            <tr><td><?php echo Format::display($message['message']); ?></td></tr>
+            <tr><td><?php echo Format::display($message['body']); ?></td></tr>
             <?php
-            if($message['attachments'] && ($links=$ticket->getAttachmentsLinks($message['msg_id'],'M'))) {?>
+            if($message['attachments'] && ($links=$ticket->getAttachmentsLinks($message['id'],'M'))) {?>
             <tr>
                 <td class="info"><?php echo $links; ?></td>
             </tr>
@@ -231,17 +231,17 @@ if($ticket->isOverdue())
         </table>
         <?php
         /* --------- Responses ------------ */
-        if($message['responses'] && ($responses=$ticket->getResponses($message['msg_id']))) {
+        if($message['responses'] && ($responses=$ticket->getResponses($message['id']))) {
            foreach($responses as $resp) {?>
             <table class="response" cellspacing="0" cellpadding="1" width="100%" border="0">
                 <tr>
                     <th><?php echo Format::db_datetime($resp['created']); ?>&nbsp;-&nbsp;<?php echo Format::htmlchars($resp['staff_name']); ?></th>
                 </tr>
                 <tr>
-                    <td><?php echo Format::display($resp['response']); ?></td>
+                    <td><?php echo Format::display($resp['body']); ?></td>
                 </tr>
                 <?php
-                if($resp['attachments'] && ($links=$ticket->getAttachmentsLinks($resp['response_id'],'R'))) {?>
+                if($resp['attachments'] && ($links=$ticket->getAttachmentsLinks($resp['id'],'R'))) {?>
                 <tr>
                     <td class="info"><?php echo $links; ?></td>
                 </tr>
@@ -251,7 +251,7 @@ if($ticket->isOverdue())
             <?php
            }
         }
-        $msgId=$message['msg_id'];
+        $msgId=$message['id'];
        }
     } else {
         echo '<p>Error fetching ticket thread - get technical help.</p>';
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index 67d270c3f033e0db98e901d66b09fdcabf5247c6..9d6d5c7bcf0005e315f4e6e032943c97a237c01d 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -42,6 +42,7 @@ switch(strtolower($_REQUEST['status'])){ //Status is overloaded
     case 'assigned':
         $status='open';
         $staffId=$thisstaff->getId();
+        $results_type='My Tickets';
         break;
     case 'answered':
         $status='open';
@@ -97,9 +98,6 @@ $deep_search=false;
 if($search):
     $qstr.='&a='.urlencode($_REQUEST['a']);
     $qstr.='&t='.urlencode($_REQUEST['t']);
-    if(isset($_REQUEST['advance_search'])){ //advance search box!
-        $qstr.='&advance_search=Search';
-    }
 
     //query
     if($searchTerm){
@@ -108,6 +106,8 @@ if($search):
         if(is_numeric($searchTerm)){
             $qwhere.=" AND ticket.ticketID LIKE '$queryterm%'";
         }elseif(strpos($searchTerm,'@') && Validator::is_email($searchTerm)){ //pulling all tricks!
+            # XXX: What about searching for email addresses in the body of
+            #      the thread message
             $qwhere.=" AND ticket.email='$queryterm'";
         }else{//Deep search!
             //This sucks..mass scan! search anything that moves! 
@@ -117,33 +117,50 @@ if($search):
                 $qwhere.=" AND ( ticket.email LIKE '%$queryterm%'".
                             " OR ticket.name LIKE '%$queryterm%'".
                             " OR ticket.subject LIKE '%$queryterm%'".
-                            " OR note.title LIKE '%$queryterm%'".
-                            " OR MATCH(message.message)   AGAINST('$queryterm')".
-                            " OR MATCH(response.response) AGAINST('$queryterm')".
-                            " OR MATCH(note.note) AGAINST('$queryterm')".
+                            " OR thread.title LIKE '%$queryterm%'".
+                            " OR MATCH(thread.body)   AGAINST('$queryterm')".
                             ' ) ';
             }else{
                 $qwhere.=" AND ( ticket.email LIKE '%$queryterm%'".
                             " OR ticket.name LIKE '%$queryterm%'".
                             " OR ticket.subject LIKE '%$queryterm%'".
-                            " OR message.message LIKE '%$queryterm%'".
-                            " OR response.response LIKE '%$queryterm%'".
-                            " OR note.note LIKE '%$queryterm%'".
-                            " OR note.title LIKE '%$queryterm%'".
+                            " OR thread.body LIKE '%$queryterm%'".
+                            " OR thread.title LIKE '%$queryterm%'".
                             ' ) ';
             }
         }
     }
     //department
-    if($_REQUEST['dept'] && in_array($_REQUEST['dept'],$thisstaff->getDepts())) {
+    if($_REQUEST['deptId'] && in_array($_REQUEST['deptId'],$thisstaff->getDepts())) {
         //This is dept based search..perm taken care above..put the sucker in.
-        $qwhere.=' AND ticket.dept_id='.db_input($_REQUEST['dept']);
-        $qstr.='&dept='.urlencode($_REQUEST['dept']);
+        $qwhere.=' AND ticket.dept_id='.db_input($_REQUEST['deptId']);
+        $qstr.='&deptId='.urlencode($_REQUEST['deptId']);
     }
-    //Teams
-    if($_REQUEST['team'] && ($thisuser->isadmin() || in_array($_REQUEST['team'],$thisuser->getTeams()))) {
-        $qwhere.=' AND ticket.team_id='.db_input($_REQUEST['team']);
-        $qstr.='&team='.urlencode($_REQUEST['team']);
+        
+    //Assignee 
+    if($_REQUEST['assignee'] && strcasecmp($_REQUEST['status'], 'closed'))  {
+        $id=preg_replace("/[^0-9]/", "", $_REQUEST['assignee']);
+        $assignee = $_REQUEST['assignee'];
+        $qstr.='&assignee='.urlencode($_REQUEST['assignee']);
+        $qwhere.= ' AND ( ';
+                  
+        if($assignee[0]=='t')
+            $qwhere.='  (ticket.team_id='.db_input($id). ' AND ticket.status="open") ';
+        elseif($assignee[0]=='s')
+            $qwhere.='  (ticket.staff_id='.db_input($id). ' AND ticket.status="open") ';
+        else
+            $qwhere.='  (ticket.staff_id='.db_input($id). ' AND ticket.status="open") ';
+        
+                   
+        if($_REQUEST['staffId'] && !$_REQUEST['status']) { //Assigned TO + Closed By
+            $qwhere.= ' OR (ticket.staff_id='.db_input($_REQUEST['staffId']). ' AND ticket.status="closed") ';
+            $qstr.='&staffId='.urlencode($_REQUEST['staffId']);
+        }
+            
+        $qwhere.= ' ) ';
+    } elseif($_REQUEST['staffId']) {
+        $qwhere.=' AND (ticket.staff_id='.db_input($_REQUEST['staffId']).' AND ticket.status="closed") ';
+        $qstr.='&staffId='.urlencode($_REQUEST['staffId']);
     }
 
     //dates
@@ -163,7 +180,7 @@ if($search):
             $qwhere.=' AND ticket.created<=FROM_UNIXTIME('.$endTime.')';
             $qstr.='&endDate='.urlencode($_REQUEST['endDate']);
         }
-}
+   }
 
 endif;
 
@@ -206,9 +223,7 @@ $qfrom=' FROM '.TICKET_TABLE.' ticket '.
 
 $sjoin='';
 if($search && $deep_search) {
-    $sjoin=' LEFT JOIN '.TICKET_MESSAGE_TABLE.' message ON (ticket.ticket_id=message.ticket_id )'
-          .' LEFT JOIN '.TICKET_RESPONSE_TABLE.' response ON (ticket.ticket_id=response.ticket_id )'
-          .' LEFT JOIN '.TICKET_NOTE_TABLE.' note ON (ticket.ticket_id=note.ticket_id ) ';
+    $sjoin=' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON (ticket.ticket_id=thread.ticket_id )';
 }
 
 $qgroup=' GROUP BY ticket.ticket_id';
@@ -222,9 +237,7 @@ $pageNav->setURL('tickets.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&ord
 
 //ADD attachment,priorities, lock and other crap
 $qselect.=' ,count(attach.attach_id) as attachments '
-         .' ,count(DISTINCT message.msg_id) as messages '
-         .' ,count(DISTINCT response.response_id) as responses '
-         .' ,count(DISTINCT note.note_id) as notes '
+         .' ,count(DISTINCT thread.id) as thread_count '
          .' ,IF(ticket.reopened is NULL,IF(ticket.lastmessage is NULL,ticket.created,ticket.lastmessage),ticket.reopened) as effective_date '
          .' ,CONCAT_WS(" ", staff.firstname, staff.lastname) as staff, team.name as team '
          .' ,IF(staff.staff_id IS NULL,team.name,CONCAT_WS(" ", staff.lastname, staff.firstname)) as assigned ';
@@ -233,9 +246,7 @@ $qfrom.=' LEFT JOIN '.TICKET_PRIORITY_TABLE.' pri ON (ticket.priority_id=pri.pri
        .' LEFT JOIN '.TICKET_LOCK_TABLE.' tlock ON (ticket.ticket_id=tlock.ticket_id AND tlock.expire>NOW() 
                AND tlock.staff_id!='.db_input($thisstaff->getId()).') '
        .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach ON (ticket.ticket_id=attach.ticket_id) '
-       .' LEFT JOIN '.TICKET_MESSAGE_TABLE.' message ON (ticket.ticket_id=message.ticket_id) '
-       .' LEFT JOIN '.TICKET_RESPONSE_TABLE.' response ON (ticket.ticket_id=response.ticket_id) '
-       .' LEFT JOIN '.TICKET_NOTE_TABLE.' note ON (ticket.ticket_id=note.ticket_id ) '
+       .' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON ( ticket.ticket_id=thread.ticket_id) '
        .' LEFT JOIN '.STAFF_TABLE.' staff ON (ticket.staff_id=staff.staff_id) '
        .' LEFT JOIN '.TEAM_TABLE.' team ON (ticket.team_id=team.team_id) ';
 
@@ -245,17 +256,18 @@ $hash = md5($query);
 $_SESSION['search_'.$hash] = $query;
 $res = db_query($query);
 $showing=db_num_rows($res)?$pageNav->showing():"";
-if(!$results_type) {
-    $results_type=($search)?'Search Results':ucfirst($status).' Tickets';
-}
-$negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting..
+if(!$results_type)
+    $results_type = ucfirst($status).' Tickets';
 
-$basic_display=!isset($_REQUEST['advance_search'])?true:false;
+if($search)
+    $results_type.= ' (Search Results)';
+
+$negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting..
 
 //YOU BREAK IT YOU FIX IT.
 ?>
 <!-- SEARCH FORM START -->
-<div id='basic' style="display:<?php echo $basic_display?'block':'none'; ?>">
+<div id='basic_search'>
     <form action="tickets.php" method="get">
     <input type="hidden" name="a" value="search">
     <table>
@@ -263,6 +275,7 @@ $basic_display=!isset($_REQUEST['advance_search'])?true:false;
             <td><input type="text" id="basic-ticket-search" name="query" size=30 value="<?php echo Format::htmlchars($_REQUEST['query']); ?>"
                 autocomplete="off" autocorrect="off" autocapitalize="off"></td>
             <td><input type="submit" name="basic_search" class="button" value="Search"></td>
+            <td>&nbsp;&nbsp;<a href="" id="go-advanced">[advanced]</a></td>
         </tr>
     </table>
     </form>
@@ -300,7 +313,7 @@ $basic_display=!isset($_REQUEST['advance_search'])?true:false;
                         title="Sort By Status <?php echo $negorder; ?>">Status</a></th>
             <?php
             } else { ?>
-                <th width="60" <?=$pri_sort?>>
+                <th width="60" <?php echo $pri_sort;?>>
                     <a <?php echo $pri_sort; ?> href="tickets.php?sort=pri&order=<?php echo $negorder; ?><?php echo $qstr; ?>" 
                         title="Sort By Priority <?php echo $negorder; ?>">Priority</a></th>
             <?php
@@ -310,15 +323,18 @@ $basic_display=!isset($_REQUEST['advance_search'])?true:false;
             <th width="150">
                 <a <?php echo $assignee_sort; ?> href="tickets.php?sort=assignee&order=<?php echo $negorder; ?><?php echo $qstr; ?>" 
                     title="Sort By Assignee <?php echo $negorder;?>">Assigned To</a></th>
-            <?}elseif(!strcasecmp($status,'closed')){?>
+            <?php 
+            } elseif(!strcasecmp($status,'closed')) { ?>
             <th width="150">
                 <a <?php echo $staff_sort; ?> href="tickets.php?sort=staff&order=<?php echo $negorder; ?><?php echo $qstr; ?>" 
                     title="Sort By Closing Staff Name <?php echo $negorder; ?>">Closed By</a></th>
-            <?}else{?>
+            <?php 
+            } else { ?>
             <th width="150">
-                <a <?=$dept_sort?> href="tickets.php?sort=dept&order=<?=$negorder?><?=$qstr?>" 
-                    title="Sort By Department <?=$negorder?>">Department</a></th>
-            <?}?>
+                <a <?php echo $dept_sort; ?> href="tickets.php?sort=dept&order=<?php echo $negorder;?><?php echo $qstr; ?>" 
+                    title="Sort By Department <?php echo $negorder; ?>">Department</a></th>
+            <?php
+            } ?>
         </tr>
      </thead>
      <tbody>
@@ -347,7 +363,7 @@ $basic_display=!isset($_REQUEST['advance_search'])?true:false;
                 }
                 $tid=$row['ticketID'];
                 $subject = Format::truncate($row['subject'],40);
-                $threadcount=$row['messages']+$row['responses'];
+                $threadcount=$row['thread_count'];
                 if(!strcasecmp($row['status'],'open') && !$row['isanswered'] && !$row['lock_id']) {
                     $tid=sprintf('<b>%s</b>',$tid);
                 }
@@ -449,3 +465,89 @@ $basic_display=!isset($_REQUEST['advance_search'])?true:false;
     } ?>
     </form>
 </div>
+<div id="overlay"></div>
+<div style="display:none;" id="advanced-search">
+    <h3>Advanced Ticket Search</h3>
+    <a class="close" href="">&times;</a>
+    <form action="tickets.php" method="post" id="search" name="search">
+        <input type="hidden" name="a" value="search">
+        <fieldset class="query">
+            <label for="query">Keyword:</label>
+            <input type="input" id="query" name="query" size="20"> <em>Optional</em>
+        </fieldset>
+        <fieldset>
+            <label for="status">Status:</label>
+            <select id="status" name="status">
+                <option value="">&mdash; Any Status &mdash;</option>
+                <option value="open">Open</option>
+                <option value="overdue">Overdue</option>
+                <option value="closed">Closed</option>
+            </select>
+            <label for="deptId">Dept:</label>
+            <select id="deptId" name="deptId">
+                <option value="">&mdash; All Departments &mdash;</option>
+                <?php
+                if(($mydepts = $thisstaff->getDepts()) && ($depts=Dept::getDepartments())) {
+                    foreach($depts as $id =>$name) {
+                        if(!in_array($id, $mydepts)) continue; 
+                        echo sprintf('<option value="%d">%s</option>', $id, $name);
+                    }
+                }
+                ?>
+            </select>
+        </fieldset>
+        <fieldset class="owner">
+            <label for="assignee">Assigned To:</label>
+            <select id="assignee" name="assignee">
+                <option value="0">&mdash; Anyone &mdash;</option>
+                <?php
+                if(($users=Staff::getStaffMembers())) {
+                    echo '<OPTGROUP label="Staff Members ('.count($users).')">';
+                    foreach($users as $id => $name) {
+                        $k="s$id";
+                        echo sprintf('<option value="%s">%s</option>', $k, $name);
+                    }
+                    echo '</OPTGROUP>';
+                }
+                
+                if(($teams=Team::getTeams())) {
+                    echo '<OPTGROUP label="Teams ('.count($teams).')">';
+                    foreach($teams as $id => $name) {
+                        $k="t$id";
+                        echo sprintf('<option value="%s">%s</option>', $k, $name);
+                    }
+                    echo '</OPTGROUP>';
+                }
+                ?>
+            </select>
+            <label for="staffId">Closed By:</label>
+            <select id="staffId" name="staffId">
+                <option value="0">&mdash; Anyone &mdash;</option>
+                <?php
+                if(($users=Staff::getStaffMembers())) {
+                    foreach($users as $id => $name)
+                        echo sprintf('<option value="%d">%s</option>', $id, $name);
+                }
+                ?>
+            </select>
+        </fieldset>
+        <fieldset class="date_range">
+            <label>Date Range:</label>
+            <input class="dp" type="input" size="20" name="startDate"><i></i>
+            <span>TO</span>
+            <input class="dp" type="input" size="20" name="endDate"><i></i>
+        </fieldset>
+        <p>
+            <span class="buttons">
+                <input type="submit" value="Search">
+                <input type="reset" value="Reset">
+                <input type="button" value="Cancel" class="close">
+            </span>
+            <span class="spinner">
+                <img src="./images/ajax-loader.gif" width="16" height="16">
+            </span>
+        </p>
+    </form>
+    <div id="result-count">
+    </div>
+</div>
diff --git a/main.inc.php b/main.inc.php
index bb997ebd6cb5d1d98d30136a66c55bd8a79008a7..fdd1a6863cc1e4b2210235415af5dbe49ee7634f 100644
--- a/main.inc.php
+++ b/main.inc.php
@@ -55,7 +55,7 @@
 
     #Current version && schema signature (Changes from version to version)
     define('THIS_VERSION','1.7-DPR2'); //Shown on admin panel
-    define('SCHEMA_SIGNATURE','ssddsdsd'); //MD5 signature of the db schema. (used to trigger upgrades)
+    define('SCHEMA_SIGNATURE','abe9c0cb845be52c10fcd7b3e626a589'); //MD5 signature of the db schema. (used to trigger upgrades)
 
     #load config info
     $configfile='';
@@ -135,14 +135,12 @@
     define('CANNED_ATTACHMENT_TABLE',TABLE_PREFIX.'canned_attachment');
 
     define('TICKET_TABLE',TABLE_PREFIX.'ticket');
-    define('TICKET_NOTE_TABLE',TABLE_PREFIX.'ticket_note');
-    define('TICKET_MESSAGE_TABLE',TABLE_PREFIX.'ticket_message');
-    define('TICKET_RESPONSE_TABLE',TABLE_PREFIX.'ticket_response');
+    define('TICKET_THREAD_TABLE',TABLE_PREFIX.'ticket_thread');
     define('TICKET_ATTACHMENT_TABLE',TABLE_PREFIX.'ticket_attachment');
     define('TICKET_PRIORITY_TABLE',TABLE_PREFIX.'ticket_priority');
     define('PRIORITY_TABLE',TICKET_PRIORITY_TABLE);
     define('TICKET_LOCK_TABLE',TABLE_PREFIX.'ticket_lock');
-    define('TICKET_HISTORY_TABLE',TABLE_PREFIX.'ticket_history');
+    define('TICKET_EVENT_TABLE',TABLE_PREFIX.'ticket_event');
   
     define('EMAIL_TABLE',TABLE_PREFIX.'email');
     define('EMAIL_TEMPLATE_TABLE',TABLE_PREFIX.'email_template');
diff --git a/scp/admin.inc.php b/scp/admin.inc.php
index a580fa6986b87661b039419bcf178e5ae46ad12e..8b3da09e6e6fbd55eeb81f9a93f9216fb6fe183e 100644
--- a/scp/admin.inc.php
+++ b/scp/admin.inc.php
@@ -15,34 +15,29 @@
 **********************************************************************/
 require('staff.inc.php');
 //Make sure config is loaded and the staff is set and of admin type
-if(!$cfg or !$thisstaff or !$thisstaff->isadmin()){
+if(!$cfg or !$thisstaff or !$thisstaff->isadmin()) {
     header('Location: index.php');
     require('index.php'); // just in case!
     exit;
 }
 
 //Some security related warnings - bitch until fixed!!! :)
-if(defined('THIS_VERSION') && strcasecmp($cfg->getVersion(),THIS_VERSION)) {
-    $sysnotice=sprintf('The script is version %s while the database is version %s.',THIS_VERSION,$cfg->getVersion());
-    if(file_exists('../setup/'))
-        $sysnotice.=' Possibly caused by incomplete <a href="../setup/upgrade.php">upgrade</a>.';
-    $errors['err']=$sysnotice; 
-}elseif(!$cfg->isHelpDeskOffline()) {
-    if(file_exists('../setup/')){
-        $sysnotice='Please take a minute to delete <strong>setup/install</strong> directory for security reasons.';
-    }else{
-
-        if(CONFIG_FILE && file_exists(CONFIG_FILE) && is_writable(CONFIG_FILE)) {
+if($cfg->isUpgradePending()) {
+    $errors['err']=$sysnotice='System upgrade is pending <a href="../setup/upgrade.php">Upgrade Now</a>';
+} elseif(!$cfg->isHelpDeskOffline()) {
+    
+    if(file_exists('../setup/')) {
+        $sysnotice='Please take a minute to delete <strong>setup/install</strong> directory (../setup/) for security reasons.';
+    } elseif(CONFIG_FILE && file_exists(CONFIG_FILE) && is_writable(CONFIG_FILE)) {
             //Confirm for real that the file is writable by group or world.
             clearstatcache(); //clear the cache!
             $perms = @fileperms(CONFIG_FILE);
             if(($perms & 0x0002) || ($perms & 0x0010)) { 
                 $sysnotice=sprintf('Please change permission of config file (%s) to remove write access. e.g <i>chmod 644 %s</i>',
-                                basename(CONFIG_FILE),basename(CONFIG_FILE));
+                                basename(CONFIG_FILE), basename(CONFIG_FILE));
             }
-        }
-
     }
+
     if(!$sysnotice && ini_get('register_globals'))
         $sysnotice='Please consider turning off register globals if possible';
 }
diff --git a/scp/ajax.php b/scp/ajax.php
index 471e9c710d577d662abae098ef2053dbfd702694..2b08168adda9664f710f8b4abaa47cb4c0b78801 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -43,10 +43,13 @@ $dispatcher = patterns('',
         url_get('^ticket_variables', 'ticket_variables')
     )),
     url('^/config/', patterns('ajax.config.php:ConfigAjaxAPI',
-        url_get('^ui', 'ui')
+        url_get('^ui', 'scp_ui')
     )),
     url_get('^/users$', array('ajax.users.php:UsersAjaxAPI', 'search')),
-    url_get('^/tickets$', array('ajax.tickets.php:TicketsAjaxAPI', 'search')),
+    url('^/tickets', patterns('ajax.tickets.php:TicketsAjaxAPI',
+        url_get('^/lookup', 'lookup'),
+        url_get('^$', 'search')
+    )),
     url('^/ticket/', patterns('ajax.tickets.php:TicketsAjaxAPI',
         url_get('^(?P<tid>\d+)/preview', 'previewTicket'),
         url_get('^(?P<tid>\d+)/lock', 'acquireLock'),
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 90fd975fd8c8272be307a8a986cad77bdd02a2d5..80f99b8ba7eb61958adcb454307e74fc9d1aae6f 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -1070,3 +1070,156 @@ h2 .reload {
   padding-left: 24px;
   background: url('../images/icons/page.png') 0 50% no-repeat;
 }
+
+
+/* Advanced Ticket Search */
+
+#overlay {
+    background:#000;
+    position:absolute;
+    display:none;
+    z-index:1000;
+}
+
+#advanced-search, #advanced-search * {
+    box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+}
+
+#advanced-search {
+    position:absolute;
+    padding:1em;
+    width:640px;
+    height:360px;
+    background:#fff;
+    border:1px solid #2a67ac;
+    display:none;
+    z-index:1200;
+}
+
+#advanced-search h3 {
+    color:#2a67ac;
+    font-size:20px;
+    margin:0;
+    padding:0;
+    display:inline-block;
+}
+
+#advanced-search a.close {
+    display:inline-block;
+    float:right;
+    font-size:16px;
+    color:#777;
+}
+
+#advanced-search form {
+    clear:both;
+    padding:2em 0 1em 0;
+    width:100%;
+}
+
+#advanced-search div.closed_by, #advanced-search span.spinner {
+    display:none;
+}
+
+#advanced-search fieldset {
+    margin:0;
+    padding:0.25em 0;
+    border:none;
+    overflow:hidden;
+}
+
+#advanced-search label {
+    width:100px;
+    display:inline-block;
+    text-align:right;
+    padding:10px;
+}
+
+#advanced-search fieldset input {
+    border:1px solid #ccc;
+    background:#fff;
+}
+
+#advanced-search fieldset select {
+    width:170px;
+    display:inline-block;
+}
+
+#advanced-search fieldset span {
+    width:50px;
+    display:inline-block;
+    text-align:center;
+    color:#777;
+    font-size:0.75em;
+}
+
+#advanced-search .query input {
+    width:350px;
+}
+
+#advanced-search .date_range input {
+    width:175px;
+}
+
+#advanced-search .date_range i {
+    display:inline-block;
+    margin-left:3px;
+    position:relative;
+    top:5px;
+    width:16px;
+    height:16px;
+    background:url(../images/cal.png) bottom left no-repeat;
+}
+
+#advanced-search fieldset.sorting select {
+    width:130px;
+}
+
+#advanced-search p {
+    text-align:center;
+}
+
+#advanced-search input[type="submit"],
+#advanced-search input[type="reset"],
+#advanced-search input[type="button"]
+{
+    display:inline-block;
+    margin:0;
+    height:24px;
+    line-height:24px;
+    font-weight:bold;
+    border:1px solid #666666;
+    padding:0 10px;
+    background: url('../images/grey_btn_bg.png?1312910883') top left repeat-x;
+    color: #333;
+}
+
+#advanced-search input[type="reset"], #advanced-search input[type="button"] {
+    opacity:0.7;
+}
+
+#advanced-search input[type=submit]:hover, #advanced-search input[type=submit]:active,
+#advanced-search input[type=reset]:hover, #advanced-search input[type=reset]:active {
+    background-position:bottom left;
+}
+
+#result-count div {
+    padding:5px 10px;
+    text-align:left;
+    font-weight:bold;
+    width:100%;
+    margin:0 auto;
+}
+
+#result-count .success {
+    background:#e3ffd8;
+    border:1px solid #0a0;
+}
+
+#result-count .fail {
+    background:#ffd8d8;
+    border:1px solid #a00;
+}
+
diff --git a/scp/images/ajax-loader.gif b/scp/images/ajax-loader.gif
new file mode 100644
index 0000000000000000000000000000000000000000..d42f72c723644bbf8cf8d6e1b7ff0bea7ddd305a
Binary files /dev/null and b/scp/images/ajax-loader.gif differ
diff --git a/scp/js/scp.js b/scp/js/scp.js
index 7a9e0e218d1da97b3ec4b2860f307dd1c1769d1f..56f776ed6ff4ef0a6a20a54ef3837d957a487949 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -198,11 +198,11 @@ $(document).ready(function(){
         }
     }
 
-    /* Typeahead init */
+    /* Typeahead tickets lookup */
     $('#basic-ticket-search').typeahead({
         source: function (typeahead, query) {
             $.ajax({
-                url: "ajax.php/tickets?q="+query,
+                url: "ajax.php/tickets/lookup?q="+query,
                 dataType: 'json',
                 success: function (data) {
                     typeahead.process(data);
@@ -215,6 +215,7 @@ $(document).ready(function(){
         property: "value"
     });
 
+    /* Typeahead user lookup */
     $('#email.typeahead').typeahead({
         source: function (typeahead, query) {
             if(query.length > 2) {
@@ -235,5 +236,84 @@ $(document).ready(function(){
         property: "email"
     });
 
+    /* advanced search */
+    $("#overlay").css({
+        opacity : 0.3,
+        top     : 0,
+        left    : 0,
+        width   : $(window).width(),
+        height  : $(window).height()
+    });
+
+    $("#advanced-search").css({
+        top  : ($(window).height() / 6),
+        left : ($(window).width() / 2 - 300)
+    });
+
+    $('#go-advanced').click(function(e) {
+        e.preventDefault();
+        $('#result-count').html('');
+        $('#overlay').show();
+        $('#advanced-search').show();
+    });
+
+    $('#advanced-search').delegate('a.close, input.close', 'click', function(e) {
+        e.preventDefault();
+        $('#advanced-search').hide()
+        $('#overlay').hide();
+    }).delegate('#status', 'change', function() {
+        switch($(this).val()) {
+            case 'closed':
+                $('select#assignee').find('option:first').attr('selected', 'selected').parent('select');
+                $('select#assignee').attr('disabled','disabled');
+                $('select#staffId').removeAttr('disabled');
+                break;
+            case 'open':
+            case 'overdue':
+                $('select#staffId').find('option:first').attr('selected', 'selected').parent('select');
+                $('select#staffId').attr('disabled','disabled');
+                $('select#assignee').removeAttr('disabled');
+                break;
+            default:
+                $('select#staffId').removeAttr('disabled');
+                $('select#assignee').removeAttr('disabled');
+        }
+    });
+
+    $('#advanced-search form#search').submit(function(e) { 
+        e.preventDefault();
+        var fObj = $(this);
+        var elem = $('#advanced-search');
+        $('#result-count').html('');
+        $.ajax({
+                url: "ajax.php/tickets",
+                data: fObj.serialize(),
+                dataType: 'json',
+                beforeSend: function ( xhr ) {
+                   $('.buttons', elem).hide();
+                   $('.spinner', elem).show();
+                   return true;
+                },
+                success: function (resp) {
+                        
+                    if(resp.success) {
+                        $('#result-count').html('<div class="success">' + resp.success +'</div>');
+                    } else if (resp.fail) {
+                        $('#result-count').html('<div class="fail">' + resp.fail +'</div>');
+                    } else {
+                        $('#result-count').html('<div class="fail">Unknown error</div>');
+                    }
+                }
+            })
+            .done( function () {
+             })
+            .fail( function () {
+                $('#result-count').html('<div class="fail">Advanced search failed - try again!</div>');
+            })
+            .always( function () {
+                $('.spinner', elem).hide();
+                $('.buttons', elem).show();
+             });
+    });
 
 });
diff --git a/scp/staff.inc.php b/scp/staff.inc.php
index b3ee30a4ec3a555c2fd8c3c86ed6e72b2c6517ca..bddf874cf976f4a170b3782330b7fdc3b8a87732 100644
--- a/scp/staff.inc.php
+++ b/scp/staff.inc.php
@@ -63,19 +63,19 @@ if(!$thisstaff || !is_object($thisstaff) || !$thisstaff->getId() || !$thisstaff-
     exit;
 }
 //2) if not super admin..check system status and group status
-if(!$thisstaff->isadmin()){
-    //Staff are not allowed to login in offline mode!!
-    if($cfg->isHelpDeskOffline()){
-        staffLoginPage('System Offline');
-        exit;
-    }
+if(!$thisstaff->isadmin()) {
     //Check for disabled staff or group!
     if(!$thisstaff->isactive() || !$thisstaff->isGroupActive()) {
         staffLoginPage('Access Denied. Contact Admin');
         exit;
     }
-}
 
+    //Staff are not allowed to login in offline mode!!
+    if($cfg->isHelpDeskOffline() || $cfg->isUpgradePending()) {
+        staffLoginPage('System Offline');
+        exit;
+    }
+}
 
 //Keep the session activity alive
 $thisstaff->refreshSession();
@@ -93,10 +93,9 @@ $errors=array();
 $msg=$warn=$sysnotice='';
 $tabs=array();
 $submenu=array();
-
-if(defined('THIS_VERSION') && strcasecmp($cfg->getVersion(),THIS_VERSION)) {
-    $errors['err']=$sysnotice=sprintf('The script is version %s while the database is version %s',THIS_VERSION,$cfg->getVersion());
-}elseif($cfg->isHelpDeskOffline()){
+if($cfg->isUpgradePending()) {
+    $errors['err']=$sysnotice='System upgrade is pending <a href="../setup/upgrade.php">Upgrade Now</a>';
+} elseif($cfg->isHelpDeskOffline()) {
     $sysnotice='<strong>System is set to offline mode</strong> - Client interface is disabled and ONLY admins can access staff control panel.';
     $sysnotice.=' <a href="settings.php">Enable</a>.';
 }
diff --git a/setup/cleanup.php b/setup/cleanup.php
deleted file mode 100644
index 1309228eb6c64eaaee6f16926ec98b06d91d0221..0000000000000000000000000000000000000000
--- a/setup/cleanup.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-/*********************************************************************
-    cleanup.php
-
-    Cleanup script called via ajax to migrate attachments.
-
-    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:
-**********************************************************************/
-session_start();
-if($_GET['c']>10) { //When Done send 304 - nothing else to do.
-    $_SESSION['s']='done';
-    session_write_close();
-    header("HTTP/1.1 304 Not Modified"); 
-    exit;
-}
-echo "Cleaning up...".time(); 
-?>
diff --git a/setup/css/wizard.css b/setup/css/wizard.css
index 6fd18e17854f65a30a2947460a955ca4c403541c..c92b05447a6acb9992ea21be3ce13581840784a9 100644
--- a/setup/css/wizard.css
+++ b/setup/css/wizard.css
@@ -70,7 +70,7 @@ form .row span { width: 600px; color: #666666; }
 
 #overlay { display: none; position: fixed; background: #000; z-index: 2000; }
 
-#loading { padding: 10px 10px 10px 60px; width: 300px; height: 100px; background: url('../images/ajax-loader.gif?1312925608') 10px 50% no-repeat white; position: fixed; display: none; z-index: 3000; }
+#loading { padding: 10px 10px 10px 60px; width: 400px; height: 150px; background: url('../images/ajax-loader.gif?1312925608') 10px 50% no-repeat white; position: fixed; display: none; z-index: 3000; }
 #loading h4 { margin: 3px 0 0 0; padding: 0; color: #d80; }
 
 .tip { display: inline-block; width: 16px; height: 16px; outline: none; text-decoration: none; color: #d80; }
diff --git a/setup/inc/class.installer.php b/setup/inc/class.installer.php
new file mode 100644
index 0000000000000000000000000000000000000000..75ebdc7cdef148a89fd9016e1d1eb62e278f8e39
--- /dev/null
+++ b/setup/inc/class.installer.php
@@ -0,0 +1,202 @@
+<?php
+/*********************************************************************
+    class.installer.php
+
+    osTicket Intaller - installs the latest version.
+
+    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:
+**********************************************************************/
+require_once INC_DIR.'class.setup.php';
+
+class Installer extends SetupWizard {
+
+    var $config;
+
+    function Installer($configfile) {
+        $this->config =$configfile;
+        $this->errors=array();
+    }
+
+    function getConfigFile() {
+        return $this->config;
+    }
+
+    function config_exists() {
+        return ($this->getConfigFile() && file_exists($this->getConfigFile()));
+    }
+
+    function config_writable() {
+        return ($this->getConfigFile() && is_writable($this->getConfigFile()));
+    }
+
+    function check_config() {
+        return ($this->config_exists() && $this->config_writable());
+    }
+
+    //XXX: Latest version insall logic...no carry over.
+    function install($vars) {
+
+        $this->errors=$f=array();
+        
+        $f['name']          = array('type'=>'string',   'required'=>1, 'error'=>'Name required');
+        $f['email']         = array('type'=>'email',    'required'=>1, 'error'=>'Valid email required');
+        $f['fname']         = array('type'=>'string',   'required'=>1, 'error'=>'First name required');
+        $f['lname']         = array('type'=>'string',   'required'=>1, 'error'=>'Last name required');
+        $f['admin_email']   = array('type'=>'email',    'required'=>1, 'error'=>'Valid email required');
+        $f['username']      = array('type'=>'username', 'required'=>1, 'error'=>'Username required');
+        $f['passwd']        = array('type'=>'password', 'required'=>1, 'error'=>'Password required');
+        $f['passwd2']       = array('type'=>'string',   'required'=>1, 'error'=>'Confirm password');
+        $f['prefix']        = array('type'=>'string',   'required'=>1, 'error'=>'Table prefix required');
+        $f['dbhost']        = array('type'=>'string',   'required'=>1, 'error'=>'Hostname required');
+        $f['dbname']        = array('type'=>'string',   'required'=>1, 'error'=>'Database name required');
+        $f['dbuser']        = array('type'=>'string',   'required'=>1, 'error'=>'Username required');
+        $f['dbpass']        = array('type'=>'string',   'required'=>1, 'error'=>'password required');
+        
+
+        if(!Validator::process($f,$vars,$this->errors) && !$this->errors['err'])
+            $this->errors['err']='Missing or invalid data - correct the errors and try again.';
+
+
+        //Staff's email can't be same as system emails.
+        if($vars['admin_email'] && $vars['email'] && !strcasecmp($vars['admin_email'],$vars['email']))
+            $this->errors['admin_email']='Conflicts with system email above';
+        //Admin's pass confirmation. 
+        if(!$this->errors && strcasecmp($vars['passwd'],$vars['passwd2']))
+            $this->errors['passwd2']='passwords to not match!';
+        //Check table prefix underscore required at the end!
+        if($vars['prefix'] && substr($vars['prefix'], -1)!='_')
+            $this->errors['prefix']='Bad prefix. Must have underscore (_) at the end. e.g \'ost_\'';
+
+        //Make sure admin username is not very predictable. XXX: feels dirty but necessary 
+        if(!$this->errors['username'] && in_array(strtolower($vars['username']),array('admin','admins','username','osticket')))
+            $this->errors['username']='Bad username';
+
+        //MYSQL: Connect to the DB and check the version & database (create database if it doesn't exist!)
+        if(!$this->errors) {
+            if(!db_connect($vars['dbhost'],$vars['dbuser'],$vars['dbpass']))
+                $this->errors['db']='Unable to connect to MySQL server. Possibly invalid login info.';
+            elseif(db_version()< $this->getMySQLVersion())
+                $this->errors['db']=sprintf('osTicket requires MySQL %s or better!',$this->getMySQLVersion());
+            elseif(!db_select_database($vars['dbname']) && !db_create_database($vars['dbname'])) {
+                $this->errors['dbname']='Database doesn\'t exist';
+                $this->errors['db']='Unable to create the database.';
+            } elseif(!db_select_database($vars['dbname'])) {
+                $this->errors['dbname']='Unable to select the database';
+            }
+        }
+
+        //bailout on errors.
+        if($this->errors) return false;
+
+        /*************** We're ready to install ************************/
+        define('ADMIN_EMAIL',$vars['admin_email']); //Needed to report SQL errors during install.
+        define('PREFIX',$vars['prefix']); //Table prefix
+
+        $schemaFile =INC_DIR.'sql/osticket-v1.7-mysql.sql'; //DB dump.
+        $debug = true; //XXX:Change it to true to show SQL errors.
+
+        //Last minute checks.
+        if(!file_exists($schemaFile))
+            $this->errors['err']='Internal Error - please make sure your download is the latest (#1)';
+        elseif(!file_exists($this->getConfigFile()) || !($configFile=file_get_contents($this->getConfigFile())))
+            $this->errors['err']='Unable to read config file. Permission denied! (#2)';
+        elseif(!($fp = @fopen($this->getConfigFile(),'r+')))
+            $this->errors['err']='Unable to open config file for writing. Permission denied! (#3)';
+        elseif(!$this->load_sql_file($schemaFile,$vars['prefix'], true, $debug))
+            $this->errors['err']='Error parsing SQL schema! Get help from developers (#4)';
+              
+        if(!$this->errors) {
+            //Create admin user.
+            $sql='INSERT INTO '.PREFIX.'staff SET created=NOW() '
+                .', isactive=1, isadmin=1, group_id=1, dept_id=1, timezone_id=8, max_page_size=25 '
+                .', email='.db_input($_POST['admin_email'])
+                .', firstname='.db_input($vars['fname'])
+                .', lastname='.db_input($vars['lname'])
+                .', username='.db_input($vars['username'])
+                .', passwd='.db_input(Passwd::hash($vars['passwd']));
+            if(!mysql_query($sql) || !($uid=mysql_insert_id()))
+                $this->errors['err']='Unable to create admin user (#6)';
+        }
+
+        if(!$this->errors) {
+            //Create config settings---default settings!
+            //XXX: rename ostversion  helpdesk_* ??
+            $sql='INSERT INTO '.PREFIX.'config SET updated=NOW(), isonline=0 '
+                .', default_email_id=1, alert_email_id=2, default_dept_id=1 '
+                .', default_sla_id=1, default_timezone_id=8, default_template_id=1 '
+                .', admin_email='.db_input($vars['admin_email'])
+                .', schema_signature='.db_input(md5_file($schemaFile))
+                .', helpdesk_url='.db_input(URL)
+                .', helpdesk_title='.db_input($vars['name']);
+            if(!mysql_query($sql) || !($cid=mysql_insert_id()))
+                $this->errors['err']='Unable to create config settings (#7)';
+        }
+
+        if($this->errors) return false; //Abort on internal errors.
+
+
+        //Rewrite the config file - MUST be done last to allow for installer recovery.
+        $configFile= str_replace("define('OSTINSTALLED',FALSE);","define('OSTINSTALLED',TRUE);",$configFile);
+        $configFile= str_replace('%ADMIN-EMAIL',$vars['admin_email'],$configFile);
+        $configFile= str_replace('%CONFIG-DBHOST',$vars['dbhost'],$configFile);
+        $configFile= str_replace('%CONFIG-DBNAME',$vars['dbname'],$configFile);
+        $configFile= str_replace('%CONFIG-DBUSER',$vars['dbuser'],$configFile);
+        $configFile= str_replace('%CONFIG-DBPASS',$vars['dbpass'],$configFile);
+        $configFile= str_replace('%CONFIG-PREFIX',$vars['prefix'],$configFile);
+        $configFile= str_replace('%CONFIG-SIRI',Misc::randcode(32),$configFile);
+        if(!$fp || !ftruncate($fp,0) || !fwrite($fp,$configFile)) {
+            $this->errors['err']='Unable to write to config file. Permission denied! (#5)';
+            return false;
+        }
+        @fclose($fp);
+
+        /************* Make the system happy ***********************/
+        //Create default emails!
+        $email = $vars['email'];
+        list(,$domain)=explode('@',$vars['email']);
+        $sql='INSERT INTO '.PREFIX.'email (`email_id`, `dept_id`, `name`,`email`,`created`,`updated`) VALUES '
+                ." (1,1,'Support','$email',NOW(),NOW())"
+                .",(2,1,'osTicket Alerts','alerts@$domain',NOW(),NOW())"
+                .",(3,1,'','noreply@$domain',NOW(),NOW())";
+        @mysql_query($sql);
+                   
+        //Create a ticket to make the system warm and happy.
+        $sql='INSERT INTO '.PREFIX.'ticket SET created=NOW(), status="open", source="Web" '
+            .' ,priority_id=2, dept_id=1, topic_id=1 '
+            .' ,ticketID='.db_input(Misc::randNumber(6))
+            .' ,email="support@osticket.com" '
+            .' ,name="osTicket Support" '
+            .' ,subject="osTicket Installed!"';
+        if(mysql_query($sql) && ($tid=mysql_insert_id())) {
+            if(!($msg=file_get_contents(INC_DIR.'msg/installed.txt')))
+                $msg='Congratulations and Thank you for choosing osTicket!';
+                        
+            $sql='INSERT INTO '.PREFIX.'ticket_thread SET created=NOW()'
+                .', source="Web" '
+                .', thread_type="M" '
+                .', ticket_id='.db_input($tid)
+                .', title='.db_input('osTicket Installed')
+                .', body='.db_input($msg);
+            @mysql_query($sql);
+        }
+        //TODO: create another personalized ticket and assign to admin??
+                    
+        //Log a message.
+        $msg="Congratulations osTicket basic installation completed!\n\nThank you for choosing osTicket!";
+        $sql='INSERT INTO '.PREFIX.'syslog SET created=NOW(), updated=NOW(), log_type="Debug" '
+            .', title="osTicket installed!"'
+            .', log='.db_input($msg)
+            .', ip_address='.db_input($_SERVER['REMOTE_ADDR']);
+        @mysql_query($sql);
+
+        return true;
+    }
+}
+?>
diff --git a/setup/inc/class.migrater.php b/setup/inc/class.migrater.php
new file mode 100644
index 0000000000000000000000000000000000000000..de8a3d9590e97b9d78ecb905528bcbc1bc2c03a5
--- /dev/null
+++ b/setup/inc/class.migrater.php
@@ -0,0 +1,67 @@
+<?php
+/*********************************************************************
+    class.migrater.php
+
+    SQL database migrater. This provides the engine capable of rolling the
+    database for an osTicket installation forward (and perhaps even
+    backward) in time using a set of included migration scripts. Each script
+    will roll the database between two database checkpoints. Where possible,
+    the migrater will roll several checkpoint scripts into one to be applied
+    together.
+
+    Jared Hancock <jared@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:
+**********************************************************************/
+
+class DatabaseMigrater {
+
+    var $start;
+    var $end;
+    var $sqldir;
+
+    function DatabaseMigrater($start, $end, $sqldir) {
+       
+        $this->start = $start;
+        $this->end = $end;
+        $this->sqldir = $sqldir;
+       
+    }
+
+    function getPatches($stop=null) {
+
+        $start= $this->start;
+        $stop = $stop?$stop:$this->end;
+
+        $patches = array();
+        while (true) {
+            $next = glob($this->sqldir . substr($start, 0, 8)
+                         . '-*.patch.sql');
+            if (count($next) == 1) {
+                $patches[] = $next[0];
+                $start = substr(basename($next[0]), 9, 8);
+            } elseif (count($next) == 0) {
+                # There are no patches leaving the current signature. We
+                # have to assume that we've applied all the available
+                # patches.
+                break;
+            } else {
+                # Problem -- more than one patch exists from this snapshot.
+                # We probably need a graph approach to solve this.
+                break;
+            }
+
+            # Break if we've reached our target stop.
+            if(!$start || !strncasecmp($start, $stop, 8))
+                break;
+        }
+
+        return $patches;
+    }
+}
+?>
diff --git a/setup/inc/class.setup.php b/setup/inc/class.setup.php
index c6bf7b6751c9e80236d3cbfe143a37c6d52cf6c5..34bc57486942bca10d9540459dfd8daf37a4f232 100644
--- a/setup/inc/class.setup.php
+++ b/setup/inc/class.setup.php
@@ -21,8 +21,8 @@ Class SetupWizard {
                         'mysql' => '4.4');
 
     //Version info - same as the latest version.
-    var $version ='1.7-rc1';
-    var $version_verbose='1.7 RC 1';
+    var $version ='1.7-dpr2';
+    var $version_verbose='1.7 DPR 2';
 
     //Errors
     var $errors=array();
@@ -31,18 +31,18 @@ Class SetupWizard {
         $this->errors=array();
     }
 
-    function load_sql_file($file, $prefix, $debug=false) {
+    function load_sql_file($file, $prefix, $abort=true, $debug=false) {
         
         if(!file_exists($file) || !($schema=file_get_contents($file)))
-            return $this->abort('Error accessing SQL file');
+            return $this->abort('Error accessing SQL file '.basename($file));
 
-        return $this->load_sql($schema, $prefix, $debug);
+        return $this->load_sql($schema, $prefix, $abort, $debug);
     }
 
     /*
         load SQL schema - assumes MySQL && existing connection
         */
-    function load_sql($schema, $prefix, $debug=false) {
+    function load_sql($schema, $prefix, $abort=true, $debug=false) {
 
         # Strip comments and remarks
         $schema=preg_replace('%^\s*(#|--).*$%m','',$schema);
@@ -57,7 +57,8 @@ Class SetupWizard {
         foreach($statements as $k=>$sql) {
             if(!mysql_query($sql)) {
                 if($debug) echo "[$sql]=>".mysql_error();
-                return $this->abort("[$sql] - ".mysql_error());
+                if($abort)
+                    return $this->abort("[$sql] - ".mysql_error());
             }
         }
 
@@ -96,15 +97,18 @@ Class SetupWizard {
         @error is a mixed var.
     */
     function abort($error) {
-        
+       
+        $this->onError($error);
 
+        return false; // Always false... It's an abort.
+    }
+
+    function setError($error) {
+    
         if($error && is_array($error))
             $this->errors = array_merge($this->errors,$error);
         elseif($error)
             $this->errors[] = $error;
-
-        //Always returns FALSE.
-        return false;
     }
 
     function getErrors(){
@@ -112,276 +116,8 @@ Class SetupWizard {
         return $this->errors;
     }
 
-    /* Access and user validation*/
-
-    function getThisUser() {
-
-
-    }
-}
-
-class Upgrader extends SetupWizard {
-
-    var $prefix;
-
-    function Upgrader($prefix) {
-        $this->prefix = $prefix;
-        $this->errors = array();
-    }
-
-    function getTablePrefix() {
-        return $this->prefix;
-    }
-
-    /* upgrade magic related to the given version */
-    function upgradeTo($version) {
-
-        $errors = array();
-        switch($version) {
-            case '1.7-RC1':
-                //TODO: latest upgrade logic.
-                break;
-            case '1.6 ST':
-                //TODO: refactor code from 1.6 ST.
-                break;
-            case '1.6 RC5':
-                //TODO: refactor code from 1.6 ST.
-                break;
-            default:
-                //XXX: escape version 
-                return $this->abort('Trying to upgrade unknown version '.$version);
-        }
-
-        if($errors)
-            return $this->abort($errors);
-
-        return true;
-    }
-
-    /*
-       Do base upgrade
-       Does fall-through upgrade until we reach the current version.
-       We're assumming the user - is upgrading upgradable version of osTicket!
-        @version - version number to upgrade from!
-       */
-    function upgradeFrom($version) {
-
-        if(!$version || $this->getErrors())
-            return false;
-        
-        if(!strcasecmp($version,$this->getVersion()))
-            return true;
-
-        //XXX: Note FALLTHROUGH (we only break on error) and uppercase cases.
-        switch(strtoupper($version)) {
-            case 'OLD': //Upgrade old versions to 1.6 ST.
-                if(!$this->upgradeTo('1.6 RC5')) break;
-                /* FALLTHROUGH */
-            case '1.6 RC5': //Upgrade 1.6 RC5 to 1.6 ST
-                if(!$this->upgradeTo('1.6 ST')) break;
-                /* FALLTHROUGH */
-            case '1.6 ST': //Upgrade 1.6 ST to to 1.7 RC1
-                if(!$this->upgradeTo('1.7-RC1')) break;
-                /* LAST CASE IS NOT FALLTHROUGH */
-                break;
-            default: //Catch all - Upgrading older versions 1.3+ 
-                return $this->upgradeFrom('OLD');
-        }
-        //XXX: Set errors???
-       
-        return (!$this->getErrors());
-    }
-
-    function cleanup() {
-        //FIXME: cleanup logic here.
-        sleep(2);
-
-        return true;
-    }
-}
-
-/*
-   Installer class - latest version.
-   */
-class Installer extends SetupWizard {
-
-    var $config;
-
-    function Installer($configfile) {
-        $this->config =$configfile;
-        $this->errors=array();
-    }
-
-    function getConfigFile() {
-        return $this->config;
-    }
-
-    function config_exists() {
-        return ($this->getConfigFile() && file_exists($this->getConfigFile()));
-    }
-
-    function config_writable() {
-        return ($this->getConfigFile() && is_writable($this->getConfigFile()));
-    }
-
-    function check_config() {
-        return ($this->config_exists() && $this->config_writable());
-    }
-
-    //XXX: Latest version insall logic...no carry over.
-    function install($vars) {
-
-        $this->errors=$f=array();
-        
-        $f['name']          = array('type'=>'string',   'required'=>1, 'error'=>'Name required');
-        $f['email']         = array('type'=>'email',    'required'=>1, 'error'=>'Valid email required');
-        $f['fname']         = array('type'=>'string',   'required'=>1, 'error'=>'First name required');
-        $f['lname']         = array('type'=>'string',   'required'=>1, 'error'=>'Last name required');
-        $f['admin_email']   = array('type'=>'email',    'required'=>1, 'error'=>'Valid email required');
-        $f['username']      = array('type'=>'username', 'required'=>1, 'error'=>'Username required');
-        $f['passwd']        = array('type'=>'password', 'required'=>1, 'error'=>'Password required');
-        $f['passwd2']       = array('type'=>'string',   'required'=>1, 'error'=>'Confirm password');
-        $f['prefix']        = array('type'=>'string',   'required'=>1, 'error'=>'Table prefix required');
-        $f['dbhost']        = array('type'=>'string',   'required'=>1, 'error'=>'Hostname required');
-        $f['dbname']        = array('type'=>'string',   'required'=>1, 'error'=>'Database name required');
-        $f['dbuser']        = array('type'=>'string',   'required'=>1, 'error'=>'Username required');
-        $f['dbpass']        = array('type'=>'string',   'required'=>1, 'error'=>'password required');
-        
-
-        if(!Validator::process($f,$vars,$this->errors) && !$this->errors['err'])
-            $this->errors['err']='Missing or invalid data - correct the errors and try again.';
-
-
-        //Staff's email can't be same as system emails.
-        if($vars['admin_email'] && $vars['email'] && !strcasecmp($vars['admin_email'],$vars['email']))
-            $this->errors['admin_email']='Conflicts with system email above';
-        //Admin's pass confirmation. 
-        if(!$this->errors && strcasecmp($vars['passwd'],$vars['passwd2']))
-            $this->errors['passwd2']='passwords to not match!';
-        //Check table prefix underscore required at the end!
-        if($vars['prefix'] && substr($vars['prefix'], -1)!='_')
-            $this->errors['prefix']='Bad prefix. Must have underscore (_) at the end. e.g \'ost_\'';
-
-        //Make sure admin username is not very predictable. XXX: feels dirty but necessary 
-        if(!$this->errors['username'] && in_array(strtolower($vars['username']),array('admin','admins','username','osticket')))
-            $this->errors['username']='Bad username';
-
-        //MYSQL: Connect to the DB and check the version & database (create database if it doesn't exist!)
-        if(!$this->errors) {
-            if(!db_connect($vars['dbhost'],$vars['dbuser'],$vars['dbpass']))
-                $this->errors['db']='Unable to connect to MySQL server. Possibly invalid login info.';
-            elseif(db_version()< $this->getMySQLVersion())
-                $this->errors['db']=sprintf('osTicket requires MySQL %s or better!',$this->getMySQLVersion());
-            elseif(!db_select_database($vars['dbname']) && !db_create_database($vars['dbname'])) {
-                $this->errors['dbname']='Database doesn\'t exist';
-                $this->errors['db']='Unable to create the database.';
-            } elseif(!db_select_database($vars['dbname'])) {
-                $this->errors['dbname']='Unable to select the database';
-            }
-        }
-
-        //bailout on errors.
-        if($this->errors) return false;
-
-        /*************** We're ready to install ************************/
-        define('ADMIN_EMAIL',$vars['admin_email']); //Needed to report SQL errors during install.
-        define('PREFIX',$vars['prefix']); //Table prefix
-
-        $schemaFile =INC_DIR.'sql/osticket-v1.7-mysql.sql'; //DB dump.
-        $debug = true; //XXX:Change it to true to show SQL errors.
-
-        //Last minute checks.
-        if(!file_exists($schemaFile))
-            $this->errors['err']='Internal Error - please make sure your download is the latest (#1)';
-        elseif(!file_exists($this->getConfigFile()) || !($configFile=file_get_contents($this->getConfigFile())))
-            $this->errors['err']='Unable to read config file. Permission denied! (#2)';
-        elseif(!($fp = @fopen($this->getConfigFile(),'r+')))
-            $this->errors['err']='Unable to open config file for writing. Permission denied! (#3)';
-        elseif(!$this->load_sql_file($schemaFile,$vars['prefix'],$debug))
-            $this->errors['err']='Error parsing SQL schema! Get help from developers (#4)';
-              
-        if(!$this->errors) {
-            //Create admin user.
-            $sql='INSERT INTO '.PREFIX.'staff SET created=NOW() '
-                .', isactive=1, isadmin=1, group_id=1, dept_id=1, timezone_id=8, max_page_size=25 '
-                .', email='.db_input($_POST['admin_email'])
-                .', firstname='.db_input($vars['fname'])
-                .', lastname='.db_input($vars['lname'])
-                .', username='.db_input($vars['username'])
-                .', passwd='.db_input(Passwd::hash($vars['passwd']));
-            if(!mysql_query($sql) || !($uid=mysql_insert_id()))
-                $this->errors['err']='Unable to create admin user (#6)';
-        }
-
-        if(!$this->errors) {
-            //Create config settings---default settings!
-            //XXX: rename ostversion  helpdesk_* ??
-            $sql='INSERT INTO '.PREFIX.'config SET updated=NOW(), isonline=0 '
-                .', default_email_id=1, alert_email_id=2, default_dept_id=1 '
-                .', default_sla_id=1, default_timezone_id=8, default_template_id=1 '
-                .', admin_email='.db_input($vars['admin_email'])
-                .', schema_signature='.db_input(md5_file($schemaFile))
-                .', helpdesk_url='.db_input(URL)
-                .', helpdesk_title='.db_input($vars['name']);
-            if(!mysql_query($sql) || !($cid=mysql_insert_id()))
-                $this->errors['err']='Unable to create config settings (#7)';
-        }
-
-        if($this->errors) return false; //Abort on internal errors.
-
-
-        //Rewrite the config file - MUST be done last to allow for installer recovery.
-        $configFile= str_replace("define('OSTINSTALLED',FALSE);","define('OSTINSTALLED',TRUE);",$configFile);
-        $configFile= str_replace('%ADMIN-EMAIL',$vars['admin_email'],$configFile);
-        $configFile= str_replace('%CONFIG-DBHOST',$vars['dbhost'],$configFile);
-        $configFile= str_replace('%CONFIG-DBNAME',$vars['dbname'],$configFile);
-        $configFile= str_replace('%CONFIG-DBUSER',$vars['dbuser'],$configFile);
-        $configFile= str_replace('%CONFIG-DBPASS',$vars['dbpass'],$configFile);
-        $configFile= str_replace('%CONFIG-PREFIX',$vars['prefix'],$configFile);
-        $configFile= str_replace('%CONFIG-SIRI',Misc::randcode(32),$configFile);
-        if(!$fp || !ftruncate($fp,0) || !fwrite($fp,$configFile)) {
-            $this->errors['err']='Unable to write to config file. Permission denied! (#5)';
-            return false;
-        }
-        @fclose($fp);
-
-        /************* Make the system happy ***********************/
-        //Create default emails!
-        $email = $vars['email'];
-        list(,$domain)=explode('@',$vars['email']);
-        $sql='INSERT INTO '.PREFIX.'email (`email_id`, `dept_id`, `name`,`email`,`created`,`updated`) VALUES '
-                ." (1,1,'Support','$email',NOW(),NOW())"
-                .",(2,1,'osTicket Alerts','alerts@$domain',NOW(),NOW())"
-                .",(3,1,'','noreply@$domain',NOW(),NOW())";
-        @mysql_query($sql);
-                   
-        //Create a ticket to make the system warm and happy.
-        $sql='INSERT INTO '.PREFIX.'ticket SET created=NOW(), status="open", source="Web" '
-            .' ,priority_id=2, dept_id=1, topic_id=1 '
-            .' ,ticketID='.db_input(Misc::randNumber(6))
-            .' ,email="support@osticket.com" '
-            .' ,name="osTicket Support" '
-            .' ,subject="osTicket Installed!"';
-        if(mysql_query($sql) && ($tid=mysql_insert_id())) {
-            if(!($msg=file_get_contents(INC_DIR.'msg/installed.txt')))
-                $msg='Congratulations and Thank you for choosing osTicket!';
-                        
-            $sql='INSERT INTO '.PREFIX.'ticket_message SET created=NOW(),source="Web" '
-                .', ticket_id='.db_input($tid)
-                .', message='.db_input($msg);
-            @mysql_query($sql);
-        }
-        //TODO: create another personalized ticket and assign to admin??
-                    
-        //Log a message.
-        $msg="Congratulations osTicket basic installation completed!\n\nThank you for choosing osTicket!";
-        $sql='INSERT INTO '.PREFIX.'syslog SET created=NOW(),updated=NOW(),log_type="Debug" '
-            .', title="osTicket installed!"'
-            .', log='.db_input($msg)
-            .', ip_address='.db_input($_SERVER['REMOTE_ADDR']);
-        @mysql_query($sql);
-
-        return true;
+    function onError($error) {
+       return $this->setError($error);
     }
 }
 ?>
diff --git a/setup/inc/class.upgrader.php b/setup/inc/class.upgrader.php
new file mode 100644
index 0000000000000000000000000000000000000000..c82ae721f7d4c28cb451ada9a5ba6f753e983149
--- /dev/null
+++ b/setup/inc/class.upgrader.php
@@ -0,0 +1,266 @@
+<?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:
+**********************************************************************/
+
+require_once INC_DIR.'class.setup.php';
+require_once INC_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();
+
+        //Init persistent state of upgrade.
+        $this->state = &$_SESSION['ost_upgrader'][$this->getShash()]['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'];
+
+        //Database migrater 
+        $this->migrater = new DatabaseMigrater($this->signature, SCHEMA_SIGNATURE, $this->sqldir);
+    }
+
+    function getStops() {
+        return array('7be60a84' => 'migrateAttachments2DB');
+    }
+
+    function onError($error) {
+        $this->setError($error);
+        $this->setState('aborted');
+    }
+
+    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() {
+        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.
+
+        foreach($tasks as $k => $task) {
+            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;
+
+        foreach ($patches as $patch) {
+            if (!$this->load_sql_file($patch, $this->getTablePrefix()))
+                return false;
+
+            //TODO: Log the upgrade
+        
+            
+            //clear previous patch info - 
+            unset($_SESSION['ost_upgrader'][$this->getSHash()]);
+
+            //Load up post-upgrade tasks.... if any.
+            $phash = substr(basename($patch), 0, 17);
+            if(!($tasks=$this->getTasksForPatch($phash)))
+                continue;
+
+            //We have tasks to perform - set the tasks and break.
+            $shash = substr($phash, 9, 8);
+            $_SESSION['ost_upgrader'][$shash]['tasks'] = $tasks;
+            $_SESSION['ost_upgrader'][$shash]['state'] = 'upgrade';
+            break;
+        }
+
+        return true;
+
+    }
+
+    function getTasksForPatch($phash) {
+
+        $tasks=array();
+        switch($phash) { //Add  patch specific scripted tasks.
+            case 'd4fe13b1-7be60a84': //V1.6 ST- 1.7 *
+                $tasks[] = array('func' => 'migrateAttachments2DB',
+                                 'desc' => 'Migrating attachments to database, it might take a while depending on the number of files.');
+                break;
+        }
+
+        //Check if cleanup p 
+        $file=$this->getSQLDir().$phash.'.cleanup.sql';
+        if(file_exists($file)) 
+            $tasks[] = array('func' => 'cleanup', 'desc' => 'Post-upgrade cleanup!');
+
+
+        return $tasks;
+    }
+
+    /************* TASKS **********************/
+    function cleanup($tId=0) {
+
+        $file=$this->getSQLDir().$this->getSHash().'-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;
+
+        //XXX: ???
+        return false;
+    }
+    
+
+    function migrateAttachments2DB($tId=0) {
+        echo "Process attachments here - $tId";
+        $att_migrater = new AttachmentMigrater();
+        $att_migrater->start_migration();
+        # XXX: Loop here (with help of task manager)
+        $att_migrater->do_batch();
+        return 0;
+    }
+}
+?>
diff --git a/setup/inc/sql/02decaa2-60fcbee1.patch.sql b/setup/inc/sql/02decaa2-60fcbee1.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..40dc644b3df64b0e103a3a7c16ed3c835a4bfae3
--- /dev/null
+++ b/setup/inc/sql/02decaa2-60fcbee1.patch.sql
@@ -0,0 +1,6 @@
+-- Update all temlates with the new wording.
+UPDATE `%TABLE_PREFIX%email_template`
+    SET `ticket_overlimit_body` = '%name\r\n\r\nYou have reached the maximum number of open tickets allowed.\r\n\r\nTo be able to open another ticket, one of your pending tickets must be closed. To update or add comments to an open ticket simply login using the link below.\r\n\r\n%url/view.php?e=%email\r\n\r\nThank you.\r\n\r\nSupport Ticket System';
+
+UPDATE `%TABLE_PREFIX%config`
+    SET `schema_signature`='60fcbee1da3180d1b690187aa5006c88';
diff --git a/setup/inc/sql/522e5b78-02decaa2.patch.sql b/setup/inc/sql/522e5b78-02decaa2.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..78c951d69cc10f255ffb3e4ba85fcf93dae6db58
--- /dev/null
+++ b/setup/inc/sql/522e5b78-02decaa2.patch.sql
@@ -0,0 +1,10 @@
+/**
+ * @version v1.7-DPR2-P2 
+ */
+UPDATE `%TABLE_PREFIX%sla`
+    SET `created` = NOW(),
+        `updated` = NOW()
+    WHERE `created` IS NULL;
+
+UPDATE `%TABLE_PREFIX%config`
+    SET `schema_signature`='02decaa20c10c9615558762018e25507';
diff --git a/setup/inc/sql/60fcbee1-f8856d56.patch.sql b/setup/inc/sql/60fcbee1-f8856d56.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..2b7e48599816e88702f709f50557189b160a727e
--- /dev/null
+++ b/setup/inc/sql/60fcbee1-f8856d56.patch.sql
@@ -0,0 +1,19 @@
+DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_event`;
+CREATE TABLE `%TABLE_PREFIX%ticket_event` (
+  `ticket_id` int(11) unsigned NOT NULL default '0',
+  `staff_id` int(11) unsigned NOT NULL,
+  `team_id` int(11) unsigned NOT NULL,
+  `dept_id` int(11) unsigned NOT NULL,
+  `topic_id` int(11) unsigned NOT NULL,
+  `state` enum('created','closed','reopened','assigned','transferred','overdue') NOT NULL,
+  `staff` varchar(255) NOT NULL default 'SYSTEM',
+  `timestamp` datetime NOT NULL,
+  KEY `ticket_state` (`ticket_id`, `state`, `timestamp`),
+  KEY `ticket_stats` (`timestamp`, `state`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_history`;
+DROP TABLE IF EXISTS `%TABLE_PREFIX%history`;
+
+UPDATE `%TABLE_PREFIX%config`
+    SET `schema_signature`='f8856d56e51c5cc3416389de78b54515';
diff --git a/setup/inc/sql/7be60a84-522e5b78.patch.sql b/setup/inc/sql/7be60a84-522e5b78.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..acdaf7d0d3c1f8baaedd40bd68e33b01b97e72c9
--- /dev/null
+++ b/setup/inc/sql/7be60a84-522e5b78.patch.sql
@@ -0,0 +1,9 @@
+/**
+ * @version v1.7-DPR1 (P1)
+ */ 
+UPDATE `%TABLE_PREFIX%email_template`
+    SET `ticket_overlimit_subj` = 'Open Tickets Limit Reached'
+    WHERE `tpl_id` = 1 AND `cfg_id` = 1;
+
+UPDATE `%TABLE_PREFIX%config`
+    SET `schema_signature`='522e5b783c2824c67222260ee22baa93';
diff --git a/setup/inc/sql/f8856d56-abe9c0cb.patch.sql b/setup/inc/sql/f8856d56-abe9c0cb.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..a5f54aa8516aaaaf268e363b8ae58674725b61af
--- /dev/null
+++ b/setup/inc/sql/f8856d56-abe9c0cb.patch.sql
@@ -0,0 +1,109 @@
+/**
+ * Merge ticket thread tables into one
+ *  
+ * Replace the ticket_{message,response,note} tables with a single
+ * ticket_thread table that will contain data for all three current message
+ * types. This simplifies much of the ticket thread code and paves the way 
+ * for other types of messages in the future.
+ * 
+ * This patch automagically moves the data from the three federated tables
+ * into the one combined table.
+ */
+DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_thread`;
+CREATE TABLE `%TABLE_PREFIX%ticket_thread` (
+  `id` int(11) unsigned NOT NULL auto_increment,
+  `pid` int(11) unsigned NOT NULL default '0',
+  `ticket_id` int(11) unsigned NOT NULL default '0',
+  `staff_id` int(11) unsigned NOT NULL default '0',
+  `thread_type` enum('M','R','N') NOT NULL,
+  `poster` varchar(128) NOT NULL default '',
+  `source` varchar(32) NOT NULL default '',
+  `title` varchar(255),
+  `body` text NOT NULL,
+  `ip_address` varchar(64) NOT NULL default '',
+  `created` datetime NOT NULL,
+  `updated` datetime NOT NULL,
+  -- Temporary columns for conversion
+  `old_pk` int(11) unsigned NOT NULL,
+  `old_pid` int(11) unsigned,
+  PRIMARY KEY  (`id`),
+  KEY `ticket_id` (`ticket_id`),
+  KEY `staff_id` (`staff_id`),
+  FULLTEXT KEY `body` (`body`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_email_info`;
+CREATE TABLE `%TABLE_PREFIX%ticket_email_info` (
+  `message_id` int(11) unsigned NOT NULL,
+  `email_mid` varchar(255) NOT NULL,
+  `headers` text,
+  KEY `message_id` (`email_mid`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
+-- Transfer messages
+INSERT INTO `%TABLE_PREFIX%ticket_thread`
+  (`ticket_id`, `thread_type`, `body`, `ip_address`,
+    `created`, `updated`, `old_pk`)
+  SELECT `ticket_id`, 'M', `message`, `ip_address`,
+    `created`, COALESCE(`updated`, NOW()), `msg_id`
+    FROM `%TABLE_PREFIX%ticket_message`;
+
+-- Transfer responses
+INSERT INTO `%TABLE_PREFIX%ticket_thread`
+  (`ticket_id`, `staff_id`, `thread_type`, `poster`, `body`, `ip_address`,
+    `created`, `updated`, `old_pk`, `old_pid`)
+  SELECT `ticket_id`, `staff_id`, 'R', `staff_name`, `response`, `ip_address`,
+    `created`, COALESCE(`updated`, NOW()), `response_id`, `msg_id` 
+    FROM `%TABLE_PREFIX%ticket_response`;
+
+-- Connect responses to (new) messages
+CREATE TABLE `%TABLE_PREFIX%T_resp_links`
+    SELECT `id`, `old_pk`, `old_pid` FROM `%TABLE_PREFIX%ticket_thread`;
+
+UPDATE `%TABLE_PREFIX%ticket_thread`
+    SET `pid` = ( SELECT T2.`id` FROM `%TABLE_PREFIX%T_resp_links` T2
+                  WHERE `old_pid` = T2.`old_pk` )
+    WHERE `thread_type` = 'R'
+      AND `old_pid` IS NOT NULL;
+
+DROP TABLE `%TABLE_PREFIX%T_resp_links`;
+
+-- Transfer notes
+INSERT INTO `%TABLE_PREFIX%ticket_thread`
+  (`ticket_id`, `staff_id`, `thread_type`, `body`, `title`,
+    `source`, `poster`, `created`, `updated`, `old_pk`)
+  SELECT `ticket_id`, `staff_id`, 'N', `note`, `title`,
+    `source`, ( SELECT CONCAT_WS(' ', T2.`firstname`, T2.`lastname`)
+                FROM `%TABLE_PREFIX%staff` T2
+                WHERE T2.`staff_id` = `staff_id` ),
+    `created`, NOW(), `note_id`
+    FROM `%TABLE_PREFIX%ticket_note`;
+
+-- Transfer email information from messages
+INSERT INTO `%TABLE_PREFIX%ticket_email_info`
+    (`message_id`, `email_mid`, `headers`)
+    SELECT ( SELECT T2.`id` FROM `%TABLE_PREFIX%ticket_thread` T2
+             WHERE `msg_id` = T2.`old_pk`
+               AND `thread_type` = 'M' ),
+         `messageId`, `headers`
+    FROM `%TABLE_PREFIX%ticket_message`
+    WHERE `messageId` IS NOT NULL;
+
+-- Update attachment table
+UPDATE `%TABLE_PREFIX%ticket_attachment`
+    SET `ref_id` = ( SELECT T2.`id` FROM `%TABLE_PREFIX%ticket_thread` T2
+                     WHERE `ref_id` = T2.`old_pk`
+                       AND `ref_type` = T2.`thread_type` );
+
+-- Drop temporary columns
+ALTER TABLE `%TABLE_PREFIX%ticket_thread` DROP COLUMN `old_pk`;
+ALTER TABLE `%TABLE_PREFIX%ticket_thread` DROP COLUMN `old_pid`;
+
+-- Drop old tables
+DROP TABLE `%TABLE_PREFIX%ticket_message`;
+DROP TABLE `%TABLE_PREFIX%ticket_response`;
+DROP TABLE `%TABLE_PREFIX%ticket_note`;
+
+-- Finished with patch
+UPDATE `%TABLE_PREFIX%config`
+    SET `schema_signature`='abe9c0cb845be52c10fcd7b3e626a589';
diff --git a/setup/inc/sql/osticket-v1.7-mysql.sql b/setup/inc/sql/osticket-v1.7-mysql.sql
index df11daf8612702b46a2c9fda0be56a2fe16f2445..fbd2059bab92027cac455a893689ddc9e80445ca 100644
--- a/setup/inc/sql/osticket-v1.7-mysql.sql
+++ b/setup/inc/sql/osticket-v1.7-mysql.sql
@@ -598,48 +598,28 @@ CREATE TABLE `%TABLE_PREFIX%ticket_lock` (
   KEY `staff_id` (`staff_id`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
 
-DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_history`;
-CREATE TABLE `%TABLE_PREFIX%ticket_history` (
+DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_email_info`;
+CREATE TABLE `%TABLE_PREFIX%ticket_email_info` (
+  `message_id` int(11) unsigned NOT NULL,
+  `email_mid` varchar(255) NOT NULL,
+  `headers` text,
+  KEY `message_id` (`email_mid`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_event`;
+CREATE TABLE `%TABLE_PREFIX%ticket_event` (
   `ticket_id` int(11) unsigned NOT NULL default '0',
-  `state` enum('opened','closed','assigned','transferred','overdue') NOT NULL,
+  `staff_id` int(11) unsigned NOT NULL,
+  `team_id` int(11) unsigned NOT NULL,
+  `dept_id` int(11) unsigned NOT NULL,
+  `topic_id` int(11) unsigned NOT NULL,
+  `state` enum('created','closed','reopened','assigned','transferred','overdue') NOT NULL,
   `staff` varchar(255) NOT NULL default 'SYSTEM',
   `timestamp` datetime NOT NULL,
   KEY `ticket_state` (`ticket_id`, `state`, `timestamp`),
   KEY `ticket_stats` (`timestamp`, `state`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
 
-DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_message`;
-CREATE TABLE `%TABLE_PREFIX%ticket_message` (
-  `msg_id` int(11) unsigned NOT NULL auto_increment,
-  `ticket_id` int(11) unsigned NOT NULL default '0',
-  `messageId` varchar(255) default NULL,
-  `message` text NOT NULL,
-  `headers` text,
-  `source` varchar(16) default NULL,
-  `ip_address` varchar(16) default NULL,
-  `created` datetime NOT NULL,
-  `updated` datetime default NULL,
-  PRIMARY KEY  (`msg_id`),
-  KEY `ticket_id` (`ticket_id`),
-  KEY `msgId` (`messageId`),
-  FULLTEXT KEY `message` (`message`)
-) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
-
-DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_note`;
-CREATE TABLE `%TABLE_PREFIX%ticket_note` (
-  `note_id` int(11) unsigned NOT NULL auto_increment,
-  `ticket_id` int(11) unsigned NOT NULL default '0',
-  `staff_id` int(10) unsigned NOT NULL default '0',
-  `source` varchar(32) NOT NULL default '',
-  `title` varchar(255) NOT NULL default 'Generic Intermal Notes',
-  `note` text NOT NULL,
-  `created` datetime NOT NULL,
-  PRIMARY KEY  (`note_id`),
-  KEY `ticket_id` (`ticket_id`),
-  KEY `staff_id` (`staff_id`),
-  FULLTEXT KEY `note` (`note`)
-) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
-
 DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_priority`;
 CREATE TABLE `%TABLE_PREFIX%ticket_priority` (
   `priority_id` tinyint(4) NOT NULL auto_increment,
@@ -660,22 +640,24 @@ INSERT INTO `%TABLE_PREFIX%ticket_priority` (`priority_id`, `priority`, `priorit
     (3, 'high', 'High', '#FEE7E7', 2, 1),
     (4, 'emergency', 'Emergency', '#FEE7E7', 1, 0);
 
-DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_response`;
-CREATE TABLE `%TABLE_PREFIX%ticket_response` (
-  `response_id` int(11) unsigned NOT NULL auto_increment,
-  `msg_id` int(11) unsigned NOT NULL default '0',
+DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_thread`;
+CREATE TABLE `%TABLE_PREFIX%ticket_thread` (
+  `id` int(11) unsigned NOT NULL auto_increment,
+  `pid` int(11) unsigned NOT NULL default '0',
   `ticket_id` int(11) unsigned NOT NULL default '0',
   `staff_id` int(11) unsigned NOT NULL default '0',
-  `staff_name` varchar(32) NOT NULL default '',
-  `response` text NOT NULL,
-  `ip_address` varchar(16) NOT NULL default '',
+  `thread_type` enum('M','R','N') NOT NULL,
+  `poster` varchar(128) NOT NULL default '',
+  `source` varchar(32) NOT NULL default '',
+  `title` varchar(255),
+  `body` text NOT NULL,
+  `ip_address` varchar(64) NOT NULL default '',
   `created` datetime NOT NULL,
   `updated` datetime NOT NULL,
-  PRIMARY KEY  (`response_id`),
+  PRIMARY KEY  (`id`),
   KEY `ticket_id` (`ticket_id`),
-  KEY `msg_id` (`msg_id`),
   KEY `staff_id` (`staff_id`),
-  FULLTEXT KEY `response` (`response`)
+  FULLTEXT KEY `body` (`body`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
 
 DROP TABLE IF EXISTS `%TABLE_PREFIX%timezone`;
diff --git a/setup/inc/sql/osticket-v1.7-mysql.sql.md5 b/setup/inc/sql/osticket-v1.7-mysql.sql.md5
new file mode 100644
index 0000000000000000000000000000000000000000..23011d006ad5e19d75630760bff1cfa872db12ec
--- /dev/null
+++ b/setup/inc/sql/osticket-v1.7-mysql.sql.md5
@@ -0,0 +1 @@
+abe9c0cb845be52c10fcd7b3e626a589
diff --git a/setup/inc/sql/v1.6st-1.7-upgrade-mysql.sql b/setup/inc/sql/v1.6st-1.7-upgrade-mysql.sql
index efeb8de6f26eb4e4c4a5e10140147c5b1ebdd3c6..3a2acf2295170c9b6263b33f05d649c378ed5dbb 100644
--- a/setup/inc/sql/v1.6st-1.7-upgrade-mysql.sql
+++ b/setup/inc/sql/v1.6st-1.7-upgrade-mysql.sql
@@ -26,10 +26,14 @@ ALTER TABLE `%TABLE_PREFIX%ticket`
         'Web', 'Email', 'Phone', 'API', 'Other') NOT NULL DEFAULT 'Other';
 
 -- Add table for ticket history (statistics) tracking
-DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_history`;
-CREATE TABLE `%TABLE_PREFIX%ticket_history` (
+DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_event`;
+CREATE TABLE `%TABLE_PREFIX%ticket_event` (
   `ticket_id` int(11) unsigned NOT NULL default '0',
-  `state` enum('opened','closed','assigned','transferred','overdue') NOT NULL,
+  `staff_id` int(11) unsigned NOT NULL,
+  `team_id` int(11) unsigned NOT NULL,
+  `dept_id` int(11) unsigned NOT NULL,
+  `topic_id` int(11) unsigned NOT NULL,
+  `state` enum('created','closed','reopened','assigned','transferred','overdue') NOT NULL,
   `staff` varchar(255) NOT NULL default 'SYSTEM',
   `timestamp` datetime NOT NULL,
   KEY `ticket_state` (`ticket_id`, `state`, `timestamp`),
diff --git a/setup/inc/upgrade-aborted.inc.php b/setup/inc/upgrade-aborted.inc.php
index 8d8f64aa831841dd9431f017c7289760ba18448e..8624590d35710a42906952afab82b2688f028315 100644
--- a/setup/inc/upgrade-aborted.inc.php
+++ b/setup/inc/upgrade-aborted.inc.php
@@ -6,17 +6,18 @@ if(!defined('SETUPINC')) die('Kwaheri!');
     <div id="intro">
         <p>Upgrade aborted due to errors. Any errors at this stage are fatal. Please note the error(s), if any, when contacting us.<p>
         <?php
-        if($_SESSION['upgrader']['errors']) {
-            $errors=$_SESSION['upgrader']['errors'];
+        if($upgrader && ($errors=$upgrader->getErrors())) {
             if($errors['err'])
                 echo sprintf('<b><font color="red">%s</font></b>',$errors['err']);
-                
             echo '<ul class="error">';
             unset($errors['err']);
-            foreach($errors as $k=>$error)
+            foreach($errors as $k => $error)
                 echo sprintf('<li>%s</li>',$error);
             echo '</ul>';
-        } ?>
+        } else {
+            echo '<b><font color="red">Internal error occurred - get technical help.</font></b>';
+        }
+        ?>
         <p>Please, refer to the <a target="_blank" href="http://osticket.com/wiki/Upgrade_and_Migration">Upgrade Guide</a> on the wiki for more information.</p>
     </div>
     <p><strong>Need Help?</strong> We provide <a target="_blank" href="http://osticket.com/support/professional_services.php"><u>professional upgrade services</u></a> and commercial support. <a target="_blank" href="http://osticket.com/support/">Contact us</a> today for expedited help.</p>
diff --git a/setup/inc/upgrade-attachments.inc.php b/setup/inc/upgrade-attachments.inc.php
deleted file mode 100644
index c8155d32c672e358cb068d79720a076327a88706..0000000000000000000000000000000000000000
--- a/setup/inc/upgrade-attachments.inc.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-if(!defined('SETUPINC')) die('Kwaheri!');
-$msg = $_SESSION['ost_upgrader']['msg'];
-?>    
-<div id="main">
-    <h1>Attachments Migration</h1>
-    <p>We're almost done! We're now migrating attachments to the database, it might take a while dending on the number of files in your database.<p>
-    <p style="color:#FF7700;font-weight:bold;">We have to migrate files in batches for technical reasons.</p>
-    <p>Please don't cancel or close the browser.</p>
-            
-    <div id="bar">
-        <form method="post" action="upgrade.php" id="attachments">
-            <input type="hidden" name="s" value="cleanup">
-            <input class="btn"  type="submit" name="submit" value="Next Batch">
-        </form>
-        
-    </div>
-</div>
-<div id="sidebar">
-    <h3>Upgrade Tips</h3>
-    <p>1. Be patient the process will take a few minutes.</p>
-    <p>2. If you experience any problems, you can always restore your files/dabase backup.</p>
-    <p>3. We can help, feel free to <a href="http://osticket.com/support/" target="_blank">contact us </a> for professional help.</p>    
-</div>    
-<div id="overlay"></div>
-    <div id="loading">
-        <h4>Moving attachments</h4>
-        <br>
-        Please wait... while we migrate attachments!
-        <br><br>
-        <div id="msg" style="font-weight: bold;"><?php echo Format::htmlchars($msg); ?></div>
-    </div>
diff --git a/setup/inc/upgrade-cleanup.inc.php b/setup/inc/upgrade-cleanup.inc.php
deleted file mode 100644
index f8406ae533a9efbce73e864239d749b586e8f66e..0000000000000000000000000000000000000000
--- a/setup/inc/upgrade-cleanup.inc.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-if(!defined('SETUPINC')) die('Kwaheri!');
-
-?>
-    <div id="main">
-            <h1>osTicket Upgrade</h1>
-            <div id="intro">
-             <p>We're almost done! Please don't cancel or close the browser, any errors at this stage will be fatal.</p>
-            </div>
-            <h2>Cleanup: Step 2 of 2</h2>
-            <p>The upgrade wizard will now attempt to do post upgrade cleanup. It might take a while dending on the size of your database. </p>
-            <ul>
-                <li>Setting Changes</li>
-                <li>Attachment Migration</li>
-                <li>Database Optimization</li>
-            </ul>
-            <div id="bar">
-                <form method="post" action="upgrade.php" id="cleanup">
-                    <input type="hidden" name="s" value="cleanup">
-                    <input class="btn"  type="submit" name="submit" value="Do It Now!">
-                </form>
-            </div>
-    </div>
-    <div id="sidebar">
-            <h3>Upgrade Tips</h3>
-            <p>1. Be patient the process will take a couple of seconds.</p>
-            <p>2. If you experience any problems, you can always restore your files/dabase backup.</p>
-            <p>3. We can help, feel free to <a href="http://osticket.com/support/" target="_blank">contact us </a> for professional help.</p>
-    </div>
-
-    <div id="overlay"></div>
-    <div id="loading">
-        <h4>Doing serious stuff!</h4>
-        <br>
-        Please wait... while we do post-upgrade cleanup!
-        <br><br>
-        <div id="msg" style="font-weight: bold;"></div>
-    </div>
diff --git a/setup/inc/upgrade-core.inc.php b/setup/inc/upgrade-core.inc.php
deleted file mode 100644
index ec42e50437537ea01931484a18e86c902ba909b1..0000000000000000000000000000000000000000
--- a/setup/inc/upgrade-core.inc.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-if(!defined('SETUPINC')) die('Kwaheri!');
-
-?>
-    <div id="main">
-            <h1>osTicket Upgrade</h1>
-            <div id="intro">
-             <p>Thank you for taking the time to upgrade your osTicket intallation!</p>
-             <p>Please don't cancel or close the browser, any errors at this
-              stage will be fatal.</p>
-            </div>
-            <h2>Base upgrade: Step 1 of 2</h2>
-            <p>The upgrade wizard will now attempt to upgrade your database and core settings!</p>
-            <ul>
-                <li>Database enhancements</li>
-                <li>New and updated features</li>
-                <li>Enhance settings and security</li>
-            </ul>
-            <div id="bar">
-                <form method="post" action="upgrade.php" id="upgrade">
-                    <input type="hidden" name="s" value="upgrade">
-                    <input class="btn"  type="submit" name="submit" value="Do It Now!">
-                </form>
-            </div>
-    </div>
-    <div id="sidebar">
-            <h3>Upgrade Tips</h3>
-            <p>1. Be patient the process will take a couple of seconds.</p>
-            <p>2. If you experience any problems, you can always restore your files/dabase backup.</p>
-            <p>3. We can help, feel free to <a href="http://osticket.com/support/" target="_blank">contact us </a> for professional help.</p>
-    </div>
-
-    <div id="overlay"></div>
-    <div id="loading">
-        <h4>Doing serious stuff!</h4>
-        Please wait... while we upgrade your osTicket installation!
-        <div id="udb"><br><b>Smile!</b></div>
-    </div>
diff --git a/setup/inc/upgrade-prereq.inc.php b/setup/inc/upgrade-prereq.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..5f60cece25500679de83b01981ea05ac634949e9
--- /dev/null
+++ b/setup/inc/upgrade-prereq.inc.php
@@ -0,0 +1,50 @@
+<?php
+if(!defined('SETUPINC')) die('Kwaheri!');
+
+?>
+    <div id="main">
+            <h1>osTicket Upgrade!</h1>
+            <font color="red"><b><?php echo $errors['err']; ?></b></font>
+            <div id="intro">
+             <p>Thank you for being a loyal osTicket user!</p>
+             <p>The upgrade wizard will guide you every step of the way in the upgrade process. While we try to ensure that the upgrade process is straightforward and painless, we can't guarantee it will be the case for every user.</p>
+            </div>
+            <h2>Getting ready!</h2>
+            <p>Before we begin, we'll check your server configuration to make sure you meet the minimum requirements to run the latest version of osTicket.</p>
+            <h3>Prerequisites: <font color="red"><?php echo $errors['prereq']; ?></font></h3>
+            These items are necessary in order to use osTicket the latest version of osTicket.
+            <ul class="progress">
+                <li class="<? echo $upgrader->check_php()?'yes':'no'; ?>">
+                PHP v4.3 or greater - (<small><b><?php echo PHP_VERSION; ?></b></small>)</li>
+                <li class="<? echo $upgrader->check_mysql()?'yes':'no'; ?>">
+                MySQL v4.4 or greater - (<small><b><?php echo extension_loaded('mysql')?'module loaded':'missing!'; ?></b></small>)</li>
+            </ul>
+            <h3>Higly Recommended:</h3>
+            We hightly recommend that you follow the steps below.
+            <ul>
+                <li>Take osTicket offline momentarily during upgrade.</li>
+                <li>Backup the current database, if you haven't done so already.</li>
+                <li>Be patient the upgrade process will take a couple of seconds.</li>
+            </ul>
+            <div id="bar">
+                <form method="post" action="upgrade.php" id="prereq">
+                    <input type="hidden" name="s" value="prereq">
+                    <input class="btn"  type="submit" name="submit" value="Start Upgrade Now &raquo;">
+                </form>
+            </div>
+    </div>
+    <div id="sidebar">
+            <h3>Upgrade Tips</h3>
+            <p>1. Remember to backup your osTicket database</p>
+            <p>2. Take osTicket offline momentarily</p>
+            <p>3. If you experience any problems, you can always restore your files/dabase backup.</p>
+            <p>4. We can help, feel free to <a href="http://osticket.com/support/" target="_blank">contact us </a> for professional help.</p>
+
+    </div>
+
+    <div id="overlay"></div>
+    <div id="loading">
+        <h4>Doing stuff!</h4>
+        Please wait... while we upgrade your osTicket installation!
+        <div id="udb"></div>
+    </div>
diff --git a/setup/inc/upgrade.inc.php b/setup/inc/upgrade.inc.php
index e5b8ba77bef7da18c547820031d1ca294b2b4dea..6f05cd169c66d49163908d8a6c6bef9d5b9a1a18 100644
--- a/setup/inc/upgrade.inc.php
+++ b/setup/inc/upgrade.inc.php
@@ -1,50 +1,40 @@
 <?php
-if(!defined('SETUPINC')) die('Kwaheri!');
+if(!defined('SETUPINC') || !$upgrader)  die('Kwaheri!');
+$action=$upgrader->getNextAction();
 
 ?>
     <div id="main">
-            <h1>osTicket Upgrade!</h1>
-            <font color="red"><b><?php echo $errors['err']; ?></b></font>
+            <h1>osTicket Upgrade</h1>
             <div id="intro">
-             <p>Thank you for being a loyal osTicket user!</p>
-             <p>The upgrade wizard will guide you every step of the way in the upgrade process. While we try to ensure that the upgrade process is straightforward and painless, we can't guarantee it will be the case for every user.</p>
+             <p>Thank you for taking the time to upgrade your osTicket intallation!</p>
+             <p>Please don't cancel or close the browser, any errors at this
+              stage will be fatal.</p>
             </div>
-            <h2>Getting ready!</h2>
-            <p>Before we begin, we'll check your server configuration to make sure you meet the minimum requirements to run the latest version of osTicket.</p>
-            <h3>Prerequisites: <font color="red"><?php echo $errors['prereq']; ?></font></h3>
-            These items are necessary in order to use osTicket the latest version of osTicket.
-            <ul class="progress">
-                <li class="<? echo $upgrader->check_php()?'yes':'no'; ?>">
-                PHP v4.3 or greater - (<small><b><?php echo PHP_VERSION; ?></b></small>)</li>
-                <li class="<? echo $upgrader->check_mysql()?'yes':'no'; ?>">
-                MySQL v4.4 or greater - (<small><b><?php echo extension_loaded('mysql')?'module loaded':'missing!'; ?></b></small>)</li>
-            </ul>
-            <h3>Higly Recommended:</h3>
-            We hightly recommend that you follow the steps below.
+            <h2><?php echo $action ?></h2>
+            <p>The upgrade wizard will now attempt to upgrade your database and core settings!</p>
             <ul>
-                <li>Take osTicket offline momentarily during upgrade.</li>
-                <li>Backup the current database, if you haven't done so already.</li>
-                <li>Be patient the upgrade process will take a couple of seconds.</li>
+                <li>Database enhancements</li>
+                <li>New and updated features</li>
+                <li>Enhance settings and security</li>
             </ul>
             <div id="bar">
                 <form method="post" action="upgrade.php" id="upgrade">
-                    <input type="hidden" name="s" value="prereq">
-                    <input class="btn"  type="submit" name="submit" value="Start Upgrade Now &raquo;">
+                    <input type="hidden" name="s" value="upgrade">
+                    <input type="hidden" name="sh" value="<?php echo $upgrader->getSchemaSignature(); ?>">
+                    <input class="btn"  type="submit" name="submit" value="Do It Now!">
                 </form>
             </div>
     </div>
     <div id="sidebar">
             <h3>Upgrade Tips</h3>
-            <p>1. Remember to backup your osTicket database</p>
-            <p>2. Take osTicket offline momentarily</p>
-            <p>3. If you experience any problems, you can always restore your files/dabase backup.</p>
-            <p>4. We can help, feel free to <a href="http://osticket.com/support/" target="_blank">contact us </a> for professional help.</p>
-
+            <p>1. Be patient the process will take a couple of seconds.</p>
+            <p>2. If you experience any problems, you can always restore your files/dabase backup.</p>
+            <p>3. We can help, feel free to <a href="http://osticket.com/support/" target="_blank">contact us </a> for professional help.</p>
     </div>
 
     <div id="overlay"></div>
     <div id="loading">
-        <h4>Doing stuff!</h4>
+        <h4><?php echo $action; ?></h4>
         Please wait... while we upgrade your osTicket installation!
-        <div id="udb"></div>
+        <div id="msg" style="font-weight: bold;padding-top:10px;">Smile!</div>
     </div>
diff --git a/setup/install.php b/setup/install.php
index 27b1388933d03ef9c0db5690dd147213848d750f..d019598e55de7099c9dbb517b4a9aa1663886ad0 100644
--- a/setup/install.php
+++ b/setup/install.php
@@ -15,6 +15,9 @@
 **********************************************************************/
 require('setup.inc.php');
 
+require_once INC_DIR.'class.installer.php';
+
+
 //define('OSTICKET_CONFIGFILE','../include/ost-config.php'); //osTicket config file full path.
 define('OSTICKET_CONFIGFILE','../include/ost-config.php'); //XXX: Make sure the path is corrent b4 releasing.
 
diff --git a/setup/js/setup.js b/setup/js/setup.js
index 5d274ea3198bbec310c20abef16ac68df51ee270..b1f8e08c3aa48397206c43ad854fc3bf4815473c 100644
--- a/setup/js/setup.js
+++ b/setup/js/setup.js
@@ -13,30 +13,30 @@ jQuery(function($) {
         left : ($(window).width() / 2 - 160)
         });
         
-    $('form#install, form#upgrade, form#attachments').submit(function(e) {
+    $('form#install').submit(function(e) {
         $('input[type=submit]', this).attr('disabled', 'disabled');
         $('#overlay, #loading').show();
         return true;
         });
 
-    $('form#cleanup').submit(function(e) {
+    $('form#upgrade').submit(function(e) {
         e.preventDefault();
         var form = $(this);
         $('input[type=submit]', this).attr('disabled', 'disabled');
         $('#overlay, #loading').show();
-        doCleanup('upgrade',form.attr('action'));
+        doTasks('upgrade.php',form.serialize());
+
         return false;
         });
 
-
-    function doCleanup(type,url) {
+    function doTasks(url, data) {
         function _lp(count) {
             $.ajax({
                 type: 'GET',
-                url: 'cleanup.php',
+                url: 'p.php',
                 async: true,
                 cache: false,
-                data: {c:count,type:type},
+                data: data,
                 dataType: 'text',
                 success: function(res) {
                     if (res) { 
@@ -45,16 +45,17 @@ jQuery(function($) {
                 },
                 statusCode: {
                     200: function() {
-                        setTimeout(function() { _lp(count+1); },2);
+                        setTimeout(function() { _lp(count+1); }, 2);
                     },
 
                     304: function() {
-                        $('#loading #msg').html("We're done... ");
-                        setTimeout(function() { location.href =url;},1000);
+                        $('#loading #msg').html("We're done... cleaning up!");
+                        setTimeout(function() { location.href =url;}, 3000);
                     }
                 },
-                error: function(){
-                    alert("Something went wrong");
+                error: function() {
+                    $('#loading #msg').html("Something went wrong");
+                    setTimeout(function() { location.href =url;}, 1000);
                 }
             });
         };
diff --git a/setup/p.php b/setup/p.php
new file mode 100644
index 0000000000000000000000000000000000000000..ebc4128b3455ac4063425bb9802abc856ff2c0e9
--- /dev/null
+++ b/setup/p.php
@@ -0,0 +1,69 @@
+<?php
+/*********************************************************************
+    upgrader.php
+
+    osTicket Upgrader Helper - called via ajax.
+
+    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:
+**********************************************************************/
+function staffLoginPage($msg) {
+    Http::response(403, $msg?$msg:'Access Denied');
+    exit;
+}
+
+require '../scp/staff.inc.php';
+if(!$thisstaff or !$thisstaff->isadmin()) {
+    staffLoginPage('Admin Access Required!');
+    exit;
+}
+
+define('SETUPINC', true);
+define('INC_DIR', './inc/');
+define('SQL_DIR', INC_DIR.'sql/');
+
+require_once INC_DIR.'class.upgrader.php';
+
+
+$upgrader = new Upgrader($cfg->getSchemaSignature(), TABLE_PREFIX, SQL_DIR);
+
+//Just report the next action on the first call.
+if(!$_SESSION['ost_upgrader'][$upgrader->getSHash()]['progress']) {
+    $_SESSION['ost_upgrader'][$upgrader->getSHash()]['progress'] = $upgrader->getNextAction();
+    Http::response(200, $upgrader->getNextAction());
+    exit;
+}
+
+if($upgrader->getNumPendingTasks()) {
+    if($upgrader->doTasks() && !$upgrader->getNumPendingTasks() && $cfg->isUpgradePending()) {
+        //Just reporting done...with tasks - break in between patches!
+        header("HTTP/1.1 304 Not Modified");
+        exit;
+    }
+} elseif($cfg->isUpgradePending() && $upgrader->isUpgradable()) {
+    $version = $upgrader->getNextVersion();
+    if($upgrader->upgrade()) {
+        //We're simply reporting progress here - call back will report next action'
+        Http::response(200, "Upgraded to $version ... post-upgrade checks!");
+        exit;
+    }
+} elseif(!$cfg->isUpgradePending()) {
+    $upgrader->setState('done');
+    session_write_close();
+    header("HTTP/1.1 304 Not Modified");
+    exit;
+}
+
+if($upgrader->isAborted() || $upgrader->getErrors()) {
+    Http::response(416, "We have a problem ... wait a sec.");
+    exit;
+}
+
+Http::response(200, $upgrader->getNextAction());
+?>
diff --git a/setup/setup.inc.php b/setup/setup.inc.php
index f2af40af61e1b7e94b9ae915eb1e81e103560594..44b2ad4d593f5521b66ad170c9e801a817cf74f1 100644
--- a/setup/setup.inc.php
+++ b/setup/setup.inc.php
@@ -52,10 +52,8 @@ ini_set('include_path', './'.PATH_SEPARATOR.INC_DIR.PATH_SEPARATOR.INCLUDE_DIR.P
 endif;
 
 #required files
-require_once(INC_DIR.'class.setup.php');
 require_once(INCLUDE_DIR.'class.validator.php');
 require_once(INCLUDE_DIR.'class.format.php');
 require_once(INCLUDE_DIR.'class.misc.php');
 require_once(INCLUDE_DIR.'mysql.php');
-
 ?>
diff --git a/setup/upgrade.php b/setup/upgrade.php
index 6fc34cfd307cfcc9df319deb25101ec159f52d75..324c467efd72d887b7592af33f6ba201c6f26e22 100644
--- a/setup/upgrade.php
+++ b/setup/upgrade.php
@@ -13,5 +13,89 @@
 
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
-die('Upgrade NOT supported for v1.7 DPR.');
+function staffLoginPage($msg) {
+        
+    $_SESSION['_staff']['auth']['dest']=THISPAGE;
+    $_SESSION['_staff']['auth']['msg']=$msg;
+    header('Location: ../scp/login.php');
+    exit;
+}
+
+require '../scp/staff.inc.php';
+if(!$thisstaff or !$thisstaff->isadmin()) {
+    staffLoginPage('Admin Access Required!');
+    exit;
+}
+
+define('SETUPINC', true);
+define('INC_DIR', './inc/');
+define('SQL_DIR', INC_DIR.'sql/');
+
+require_once INC_DIR.'class.upgrader.php';
+
+//$_SESSION['ost_upgrader']=null;
+$upgrader = new Upgrader($cfg->getSchemaSignature(), TABLE_PREFIX, SQL_DIR);
+
+
+$wizard=array();
+$wizard['title']='osTicket Upgrade Wizard';
+$wizard['tagline']='Upgrading osTicket to v'.$upgrader->getVersionVerbose();
+$wizard['logo']='logo-upgrade.png';
+$wizard['menu']=array('Upgrade Guide'=>'http://osticket.com/wiki/Upgrade_and_Migration',
+                      'Get Professional Help'=>'http://osticket.com/support');
+$errors=array();
+if($_POST && $_POST['s'] && !$upgrader->isAborted()) {
+    switch(strtolower($_POST['s'])) {
+        case 'prereq':
+            //XXX: check if it's upgradable version??
+            if(!$cfg->isUpgradePending())
+                $errors['err']=' Nothing to do! System already upgraded to the current version';
+            elseif(!$upgrader->isUpgradable())
+                $errors['err']='The upgrader does NOT support upgrading from the current vesion!';
+            elseif($upgrader->check_prereq())
+                $upgrader->setState('upgrade');
+            else
+                $errors['prereq']='Minimum requirements not met!';
+            break;
+        case 'upgrade': //Manual upgrade.... when JS (ajax) is not supported.
+            if($upgrader->getNumPendingTasks()) {
+                $upgrader->doTasks();
+            } elseif($cfg->isUpgradePending() && $upgrader->isUpgradable()) {
+                $upgrader->upgrade();
+            } elseif(!$cfg->isUpgradePending()) {
+                $upgrader->setState('done');
+            }
+
+            if(($errors=$upgrader->getErrors()))  {
+                $upgrader->setState('aborted');
+            }
+            break;
+        default:
+            $errors['err']='Unknown action!';
+    }
+}
+
+switch(strtolower($upgrader->getState())) {
+    case 'aborted':
+        $inc='upgrade-aborted.inc.php';
+        break;
+    case 'upgrade':
+        $inc='upgrade.inc.php';
+        break;
+    case 'done':
+        $inc='upgrade-done.inc.php';
+        break;
+    default:
+        $inc='upgrade-prereq.inc.php';
+        if($upgrader->isAborted())
+            $inc='upgrade-aborted.inc.php';
+        elseif(!$cfg->isUpgradePending())
+            $errors['err']='Nothing to do! System already upgraded to the latest version';
+        elseif(!$upgrader->isUpgradable())
+            $errors['err']='The upgrader does NOT support upgrading from the current vesion!';
+}
+
+require(INC_DIR.'header.inc.php');
+require(INC_DIR.$inc);
+require(INC_DIR.'footer.inc.php');
 ?>