diff --git a/WHATSNEW.md b/WHATSNEW.md
index afe5476bb591121a4c58a81881e0df7e644aab2d..460b4020f95e758483da45e8cf37bd669b4cb15d 100644
--- a/WHATSNEW.md
+++ b/WHATSNEW.md
@@ -1,10 +1,23 @@
+
+New stuff in 1.7-rc6
+====================
+  * Bug fixes and enhancements from rc5
+
+New stuff in 1.7-rc5
+====================
+  * Bug fixes from rc4
+
+New stuff in 1.7-rc4
+====================
+  * Bug fixes from rc3
+
 New stuff in 1.7-rc3
 ====================
   * Bug fixes from rc2
   * Canned auto-reply template
   * Modal dialogs
   * PEAR packages upgrade
-  * Email encoding 
+  * Email encoding
 
 New stuff in 1.7-rc2
 ====================
@@ -33,7 +46,7 @@ New stuff in 1.7-dpr4
 New stuff in 1.7-dpr3
 ======================
   * Advanced search on tickets page
-  * Ticket thread -- revised ticket message storage model for greater 
+  * Ticket thread -- revised ticket message storage model for greater
     flexability
   * New database upgrade system allowing for continuous updates to the
     database model. This will greatly simplify the process of making
diff --git a/ajax.php b/ajax.php
index 5210b628be577f830648e34a7f4d4a9a4c4fb3a1..a629af6392e312f35f02a5dc387b380fddf737eb 100644
--- a/ajax.php
+++ b/ajax.php
@@ -30,5 +30,5 @@ $dispatcher = patterns('',
         url_get('^client', 'client')
     ))
 );
-print $dispatcher->resolve($_SERVER['PATH_INFO']);
+print $dispatcher->resolve($ost->get_path_info());
 ?>
diff --git a/api/cron.php b/api/cron.php
index 43ff2b285a52bff60876ef061209bf868db2a178..0ca641b52062d6a817f67d49297e9e062d63f31b 100644
--- a/api/cron.php
+++ b/api/cron.php
@@ -13,8 +13,11 @@
 
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
-if (substr(php_sapi_name(), 0, 3) != 'cli')
-    die('cron.php only supports local cron jobs - use http -> api/task/cron');
+@chdir(realpath(dirname(__FILE__)).'/'); //Change dir.
+require('api.inc.php');
+
+if (!osTicket::is_cli())
+    die('cron.php only supports local cron calls - use http -> api/tasks/cron');
 
 @chdir(realpath(dirname(__FILE__)).'/'); //Change dir.
 require('api.inc.php');
diff --git a/api/http.php b/api/http.php
index 985d8e49893a280e180f4631bb2188f9116fa525..90926d1e5b531771c057ca82276711520c305b9e 100644
--- a/api/http.php
+++ b/api/http.php
@@ -20,11 +20,11 @@ require_once INCLUDE_DIR."class.dispatcher.php";
 
 $dispatcher = patterns('',
         url_post("^/tickets\.(?P<format>xml|json|email)$", array('api.tickets.php:TicketApiController','create')),
-        url('^/task/', patterns('',
+        url('^/tasks/', patterns('',
                 url_post("^cron$", array('api.cron.php:CronApiController', 'execute'))
          ))
         );
 
 # Call the respective function
-print $dispatcher->resolve($_SERVER['PATH_INFO']);
+print $dispatcher->resolve($ost->get_path_info());
 ?>
diff --git a/api/pipe.php b/api/pipe.php
index aa35f898463ce08b632e622a3555d4a474cf61ab..7cf1ad1b4f7c5edd88ba75cb5c7c66b683a109dc 100644
--- a/api/pipe.php
+++ b/api/pipe.php
@@ -14,14 +14,14 @@
 
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
+ini_set('memory_limit', '256M'); //The concern here is having enough mem for emails with attachments.
+@chdir(realpath(dirname(__FILE__)).'/'); //Change dir.
+require('api.inc.php');
 
 //Only local piping supported via pipe.php
-if (substr(php_sapi_name(), 0, 3) != 'cli')
+if (!osTicket::is_cli())
     die('pipe.php only supports local piping - use http -> api/tickets.email');
 
-ini_set('memory_limit', '256M'); //The concern here is having enough mem for emails with attachments.
-@chdir(realpath(dirname(__FILE__)).'/'); //Change dir.
-require('api.inc.php');
 require_once(INCLUDE_DIR.'api.tickets.php');
 PipeApiController::process();
 ?>
diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css
index 9780f0e048f7598682038fd90f876af17c3f77ed..c8b90631856d3c6810a52dc38468dd5c8fb61702 100644
--- a/assets/default/css/theme.css
+++ b/assets/default/css/theme.css
@@ -702,9 +702,10 @@ body {
 .Icon.phoneTicket {
   background-image: url('../images/icons/ticket_source_phone.gif');
 }
-.Icon.otherTicket {
+.Icon.otherTicket, .Icon.apiTicket  {
   background-image: url('../images/icons/ticket_source_other.gif');
 }
+
 .Icon.attachment {
   background-image: url('../images/icons/attachment.gif');
 }
diff --git a/include/ajax.config.php b/include/ajax.config.php
index 398b6dccdd151c205a32b307a664f764db92d608..2a01e284071ca6122024ffc78c390759f3b48467 100644
--- a/include/ajax.config.php
+++ b/include/ajax.config.php
@@ -24,8 +24,6 @@ class ConfigAjaxAPI extends AjaxController {
 
         $config=array(
                       'lock_time'       => ($cfg->getLockTime()*3600),
-                      'file_types'      => $cfg->getAllowedFileTypes(),
-                      'max_file_size'   => (int) $cfg->getMaxFileSize(),
                       'max_file_uploads'=> (int) $cfg->getStaffMaxFileUploads()
                       );
         return $this->json_encode($config);
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index bb834578064358a26e77c2a1a6ec82f5df6eba9f..1d887f04349241327a324d43b8a2d31545c45cf1 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -85,10 +85,10 @@ class TicketsAjaxAPI extends AjaxController {
     }
 
     function search() {
-        global $thisstaff;
+        global $thisstaff, $cfg;
           
         $result=array();
-        $select = 'SELECT count(ticket.ticket_id) as tickets ';
+        $select = 'SELECT count( DISTINCT ticket.ticket_id) as tickets ';
         $from = ' FROM '.TICKET_TABLE.' ticket ';
         $where = ' WHERE 1 ';
 
@@ -107,11 +107,18 @@ class TicketsAjaxAPI extends AjaxController {
         if($_REQUEST['deptId'])
             $where.=' AND ticket.dept_id='.db_input($_REQUEST['deptId']);
 
+        //Help topic
+        if($_REQUEST['topicId'])
+            $where.=' AND ticket.topic_id='.db_input($_REQUEST['topicId']);
+
         //Status
         switch(strtolower($_REQUEST['status'])) {
-            case 'open';
+            case 'open':
                 $where.=' AND ticket.status="open" ';
                 break;
+            case 'answered':
+                $where.=' AND ticket.status="open" AND ticket.isanswered=1 ';
+                break;
             case 'overdue':
                 $where.=' AND ticket.status="open" AND ticket.isoverdue=1 ';
                 break;
@@ -121,19 +128,23 @@ class TicketsAjaxAPI extends AjaxController {
         }
 
         //Assignee 
-        if($_REQUEST['assignee'] && strcasecmp($_REQUEST['status'], 'closed'))  {
+        if(isset($_REQUEST['assignee']) && strcasecmp($_REQUEST['status'], 'closed'))  {
             $id=preg_replace("/[^0-9]/", "", $_REQUEST['assignee']);
             $assignee = $_REQUEST['assignee'];
-            $where.= ' AND ( ';
+            $where.= ' AND ( ( ticket.status="open" ';
             if($assignee[0]=='t')
-                $where.='  (ticket.team_id='.db_input($id). ' AND ticket.status="open") ';
+                $where.=' AND ticket.team_id='.db_input($id);
             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") ';
+                $where.=' AND ticket.staff_id='.db_input($id);
+            elseif(is_numeric($id))
+                $where.=' AND ticket.staff_id='.db_input($id);
+
+            $where.=')';
 
             if($_REQUEST['staffId'] && !$_REQUEST['status']) //Assigned TO + Closed By
-                $where.= ' OR (ticket.staff_id='.db_input($_REQUEST['staffId']). ' AND ticket.status="closed") ';    
+                $where.= ' OR (ticket.staff_id='.db_input($_REQUEST['staffId']). ' AND ticket.status="closed") ';
+            elseif(isset($_REQUEST['staffId'])) // closed by any
+                $where.= ' OR ticket.status="closed" ';
 
             $where.= ' ) ';
         } elseif($_REQUEST['staffId']) { 
@@ -163,7 +174,6 @@ class TicketsAjaxAPI extends AjaxController {
                        ." OR thread.title LIKE '%$queryterm%'"
                        ." OR thread.body LIKE '%$queryterm%'"               
                        .' )';
-            $groupby = 'GROUP BY ticket.ticket_id ';
         }
         
         $sql="$select $from $where $groupby";
diff --git a/include/api.cron.php b/include/api.cron.php
index 912fff41090df4887598a3b7f6cd0f915910b73b..32d1b0aefb25b2ac546af5188191f5e896410b4f 100644
--- a/include/api.cron.php
+++ b/include/api.cron.php
@@ -6,7 +6,7 @@ class CronApiController extends ApiController {
 
     function execute() {
 
-        if(!($key=$this->requireApiKey()) || !$key->canExecuteCronJob())
+        if(!($key=$this->requireApiKey()) || !$key->canExecuteCron())
             return $this->exerr(401, 'API key not authorized');
 
         $this->run();
diff --git a/include/api.tickets.php b/include/api.tickets.php
index 7346f2b2943bbc5c0451d62fcc7952677034b6d5..1cc93d995fc52a563b6c8ce826137b4f198522c5 100644
--- a/include/api.tickets.php
+++ b/include/api.tickets.php
@@ -12,9 +12,9 @@ class TicketApiController extends ApiController {
         $supported = array(
             "alert", "autorespond", "source", "topicId",
             "name", "email", "subject", "phone", "phone_ext",
-            "attachments" => array("*" => 
+            "attachments" => array("*" =>
                 array("name", "type", "data", "encoding")
-            ), 
+            ),
             "message", "ip", "priorityId"
         );
 
@@ -24,6 +24,37 @@ class TicketApiController extends ApiController {
         return $supported;
     }
 
+    /*
+     Validate data - overwrites parent's validator for additional validations.
+    */
+    function validate(&$data, $format) {
+        global $ost;
+
+        //Call parent to Validate the structure
+        if(!parent::validate($data, $format))
+            $this->exerr(400, 'Unexpected or invalid data received');
+
+        //Nuke attachments IF API files are not allowed.
+        if(!$ost->getConfig()->allowAPIAttachments())
+            $data['attachments'] = array();
+
+        //Validate attachments: Do error checking... soft fail - set the error and pass on the request.
+        if($data['attachments'] && is_array($data['attachments'])) {
+            foreach($data['attachments'] as &$attachment) {
+                if(!$ost->isFileTypeAllowed($attachment))
+                    $data['error'] = 'Invalid file type (ext) for '.Format::htmlchars($attachment['name']);
+                elseif ($attachment['encoding'] && !strcasecmp($attachment['encoding'], 'base64')) {
+                    if(!($attachment['data'] = base64_decode($attachment['data'], true)))
+                        $attachment['error'] = sprintf('%s: Poorly encoded base64 data', Format::htmlchars($attachment['name']));
+                }
+            }
+            unset($attachment);
+        }
+
+        return true;
+    }
+
+
     function create($format) {
 
         if(!($key=$this->requireApiKey()) || !$key->canCreateTickets())
@@ -62,7 +93,7 @@ class TicketApiController extends ApiController {
                 return $this->exerr(403, 'Ticket denied');
             else
                 return $this->exerr(
-                        400, 
+                        400,
                         "Unable to create new ticket: validation errors:\n"
                         .Format::array_implode(": ", "\n", $errors)
                         );
@@ -70,11 +101,6 @@ class TicketApiController extends ApiController {
             return $this->exerr(500, "Unable to create new ticket: unknown error");
         }
 
-        
-        # Save attachment(s)
-        if($data['attachments'])
-            $ticket->importAttachments($data['attachments'], $ticket->getLastMsgId(), 'M');
-
         return $ticket;
     }
 
@@ -97,7 +123,7 @@ class PipeApiController extends TicketApiController {
     //Overwrite grandparent's (ApiController) response method.
     function response($code, $resp) {
 
-        //Use postfix exit codes - instead of HTTP 
+        //Use postfix exit codes - instead of HTTP
         switch($code) {
             case 201: //Success
                 $exitcode = 0;
@@ -119,8 +145,8 @@ class PipeApiController extends TicketApiController {
                 $exitcode = 69;
                 break;
             case 500: //Server error.
-            default: //Temp (unknown) failure - retry 
-                $exitcode = 75; 
+            default: //Temp (unknown) failure - retry
+                $exitcode = 75;
         }
 
         //echo "$code ($exitcode):$resp";
diff --git a/include/class.api.php b/include/class.api.php
index dd0706ceaa374b5ee35c065ce9066590c53e4e05..dd15234676d1c3783f7bbb5af357b77feb945c56 100644
--- a/include/class.api.php
+++ b/include/class.api.php
@@ -71,7 +71,7 @@ class API {
         return ($this->ht['can_create_tickets']);
     }
 
-    function canExecuteCronjob() {
+    function canExecuteCron() {
         return ($this->ht['can_exec_cron']);
     }
 
@@ -192,8 +192,9 @@ class ApiController {
      * work will be done for XML requests
      */
     function getRequest($format) {
-        
-        $input = (substr(php_sapi_name(), 0, 3) == 'cli')?'php://stdin':'php://input';
+        global $ost;
+
+        $input = $ost->is_cli()?'php://stdin':'php://input';
 
         if (!($stream = @fopen($input, 'r')))
             $this->exerr(400, "Unable to read request body");
@@ -219,7 +220,8 @@ class ApiController {
         if (!($data = $parser->parse($stream)))
             $this->exerr(400, $parser->lastError());
        
-        $this->validate($data, $this->getRequestStructure($format));
+        //Validate structure of the request.
+        $this->validate($data, $format);
 
         return $data;
     }
@@ -239,19 +241,33 @@ class ApiController {
      * expected. It is assumed that the functions actually implementing the
      * API will further validate the contents of the request
      */
-    function validate($data, $structure, $prefix="") {
+    function validateRequestStructure($data, $structure, $prefix="") {
+       
         foreach ($data as $key=>$info) {
             if (is_array($structure) and is_array($info)) {
                 $search = (isset($structure[$key]) && !is_numeric($key)) ? $key : "*"; 
                 if (isset($structure[$search])) {
-                    $this->validate($info, $structure[$search], "$prefix$key/");
+                    $this->validateRequestStructure($info, $structure[$search], "$prefix$key/");
                     continue;
                 }
             } elseif (in_array($key, $structure)) {
                 continue;
             }
-            $this->exerr(400, "$prefix$key: Unexpected data received");
+            return $this->exerr(400, "$prefix$key: Unexpected data received");
         }
+
+        return true;
+    }
+
+    /**
+     * Validate request.
+     *
+     */
+    function validate(&$data, $format) {
+        return $this->validateRequestStructure(
+                $data, 
+                $this->getRequestStructure($format)
+                );
     }
 
     /**
diff --git a/include/class.config.php b/include/class.config.php
index a445f952d277c94850d8b4b6de81ff58b165838d..e6bed4657f69b4c432e617a750042ad3271b3611 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -2,7 +2,7 @@
 /*********************************************************************
     class.config.php
 
-    osTicket config info manager. 
+    osTicket config info manager.
 
     Peter Rotich <peter@osticket.com>
     Copyright (c)  2006-2013 osTicket
@@ -17,17 +17,17 @@
 require_once(INCLUDE_DIR.'class.email.php');
 
 class Config {
-    
+
     var $id = 0;
     var $config = array();
 
-    var $defaultDept;   //Default Department    
+    var $defaultDept;   //Default Department
     var $defaultSLA;   //Default SLA
-    var $defaultEmail;  //Default Email 
+    var $defaultEmail;  //Default Email
     var $alertEmail;  //Alert Email
     var $defaultSMTPEmail; //Default  SMTP Email
 
-    function Config($id) { 
+    function Config($id) {
         $this->load($id);
     }
 
@@ -39,11 +39,11 @@ class Config {
         $sql='SELECT *, (TIME_TO_SEC(TIMEDIFF(NOW(), UTC_TIMESTAMP()))/3600) as db_tz_offset '
             .' FROM '.CONFIG_TABLE
             .' WHERE id='.db_input($id);
-        
+
         if(!($res=db_query($sql)) || !db_num_rows($res))
             return false;
 
-            
+
         $this->config = db_fetch_array($res);
         $this->id = $this->config['id'];
 
@@ -100,7 +100,7 @@ class Config {
 
         return null;
     }
-    
+
     function getDBTZoffset() {
         return $this->config['db_tz_offset'];
     }
@@ -135,15 +135,15 @@ class Config {
     function getConfigInfo() {
         return $this->config;
     }
-   
+
     function getTitle() {
         return $this->config['helpdesk_title'];
     }
-    
+
     function getUrl() {
-        return $this->config['helpdesk_url'];        
+        return $this->config['helpdesk_url'];
     }
-    
+
     function getBaseUrl() { //Same as above with no trailing slash.
         return rtrim($this->getUrl(),'/');
     }
@@ -171,11 +171,11 @@ class Config {
     function showNotesInline(){
         return $this->config['show_notes_inline'];
     }
-        
+
     function getClientTimeout() {
         return $this->getClientSessionTimeout();
     }
- 
+
     function getClientSessionTimeout() {
         return $this->config['client_session_timeout']*60;
     }
@@ -191,8 +191,8 @@ class Config {
     function getStaffTimeout() {
         return $this->getStaffSessionTimeout();
     }
-        
-    function getStaffSessionTimeout() {                
+
+    function getStaffSessionTimeout() {
         return $this->config['staff_session_timeout']*60;
     }
 
@@ -218,7 +218,7 @@ class Config {
             $this->defaultDept=Dept::lookup($this->getDefaultDeptId());
 
         return $this->defaultDept;
-    }   
+    }
 
     function getDefaultEmailId() {
         return $this->config['default_email_id'];
@@ -280,7 +280,7 @@ class Config {
     }
 
     function getDefaultTemplate() {
-    
+
         if(!$this->defaultTemplate && $this->getDefaultTemplateId())
             $this->defaultTemplate = Template::lookup($this->getDefaultTemplateId());
 
@@ -319,7 +319,7 @@ class Config {
     function clickableURLS() {
         return ($this->config['clickable_urls']);
     }
-        
+
     function enableStaffIPBinding() {
         return ($this->config['staff_ip_binding']);
     }
@@ -335,12 +335,12 @@ class Config {
     function isEmailPollingEnabled() {
         return ($this->config['enable_mail_polling']);
     }
-        
+
     function allowPriorityChange() {
         return ($this->config['allow_priority_change']);
     }
 
-        
+
     function useEmailPriority() {
         return ($this->config['use_email_priority']);
     }
@@ -352,7 +352,7 @@ class Config {
     function getReplySeparator() {
         return $this->config['reply_separator'];
     }
-  
+
     function stripQuotedReply() {
         return ($this->config['strip_quoted_reply']);
     }
@@ -360,7 +360,7 @@ class Config {
     function saveEmailHeaders() {
         return true; //No longer an option...hint: big plans for headers coming!!
     }
-    
+
     function useRandomIds() {
         return ($this->config['random_ticket_ids']);
     }
@@ -369,7 +369,7 @@ class Config {
     function autoRespONNewTicket() {
         return ($this->config['ticket_autoresponder']);
     }
-    
+
     function autoRespONNewMessage() {
         return ($this->config['message_autoresponder']);
     }
@@ -385,11 +385,11 @@ class Config {
     function alertLastRespondentONNewMessage() {
         return ($this->config['message_alert_laststaff']);
     }
-   
+
     function alertAssignedONNewMessage() {
         return ($this->config['message_alert_assigned']);
     }
-    
+
     function alertDeptManagerONNewMessage() {
         return ($this->config['message_alert_dept_manager']);
     }
@@ -417,7 +417,7 @@ class Config {
     function alertAdminONNewTicket() {
         return ($this->config['ticket_alert_admin']);
     }
-     
+
     function alertDeptManagerONNewTicket() {
         return ($this->config['ticket_alert_dept_manager']);
     }
@@ -433,11 +433,11 @@ class Config {
     function alertAssignedONTransfer() {
         return ($this->config['transfer_alert_assigned']);
     }
-    
+
     function alertDeptManagerONTransfer() {
         return ($this->config['transfer_alert_dept_manager']);
     }
-    
+
     function alertDeptMembersONTransfer() {
         return ($this->config['transfer_alert_dept_members']);
     }
@@ -486,7 +486,7 @@ class Config {
     function showAnsweredTickets() {
         return ($this->config['show_answered_tickets']);
     }
-        
+
     function hideStaffName() {
         return ($this->config['hide_staff_name']);
     }
@@ -494,10 +494,10 @@ class Config {
     function sendOverLimitNotice() {
         return ($this->config['overlimit_notice_active']);
     }
-        
+
     /* Error alerts sent to admin email when enabled */
     function alertONSQLError() {
-        return ($this->config['send_sql_errors']);                    
+        return ($this->config['send_sql_errors']);
     }
     function alertONLoginError() {
         return ($this->config['send_login_errors']);
@@ -507,7 +507,7 @@ class Config {
         return ($this->config['send_mailparse_errors']);
     }
 
-    
+
 
     /* Attachments */
     function getAllowedFileTypes() {
@@ -529,21 +529,27 @@ class Config {
     function allowAttachmentsOnlogin() {
         return ($this->allowOnlineAttachments() && $this->config['allow_online_attachments_onlogin']);
     }
-    
+
     function allowEmailAttachments() {
         return ($this->allowAttachments() && $this->config['allow_email_attachments']);
     }
 
+    //TODO: change db field to allow_api_attachments - which will include  email/json/xml attachments
+    //       terminology changed on the UI
+    function allowAPIAttachments() {
+        return $this->allowEmailAttachments();
+    }
+
     /* Needed by upgrader on 1.6 and older releases upgrade - not not remove */
     function getUploadDir() {
         return $this->config['upload_dir'];
     }
-    
+
     function updateSettings($vars, &$errors) {
 
         if(!$vars || $errors)
             return false;
-        
+
         switch(strtolower($vars['t'])) {
             case 'system':
                 return $this->updateSystemSettings($vars, $errors);
@@ -702,10 +708,10 @@ class Config {
         $f['default_email_id']=array('type'=>'int',   'required'=>1, 'error'=>'Default email required');
         $f['alert_email_id']=array('type'=>'int',   'required'=>1, 'error'=>'Selection required');
         $f['admin_email']=array('type'=>'email',   'required'=>1, 'error'=>'System admin email required');
-       
+
         if($vars['strip_quoted_reply'] && !$vars['reply_separator'])
             $errors['reply_separator']='Reply separator required to strip quoted reply.';
-        
+
         if($vars['admin_email'] && Email::getIdByEmail($vars['admin_email'])) //Make sure admin email is not also a system email.
             $errors['admin_email']='Email already setup as system email';
 
@@ -724,7 +730,7 @@ class Config {
             .' WHERE id='.db_input($this->getId());
 
 
-        
+
         return (db_query($sql));
     }
 
@@ -732,16 +738,16 @@ class Config {
 
 
         if($vars['allow_attachments']) {
-        
+
             if(!ini_get('file_uploads'))
                 $errors['err']='The \'file_uploads\' directive is disabled in php.ini';
-                
+
             if(!is_numeric($vars['max_file_size']))
-                $errors['max_file_size']='Maximum file size required';       
-               
+                $errors['max_file_size']='Maximum file size required';
+
             if(!$vars['allowed_filetypes'])
                 $errors['allowed_filetypes']='Allowed file extentions required';
-        
+
             if(!($maxfileuploads=ini_get('max_file_uploads')))
                 $maxfileuploads=DEFAULT_MAX_FILE_UPLOADS;
 
@@ -842,7 +848,7 @@ class Config {
         }
 
         if($errors) return false;
-        
+
         $sql= 'UPDATE '.CONFIG_TABLE.' SET updated=NOW() '
              .',ticket_alert_active='.db_input($vars['ticket_alert_active'])
              .',ticket_alert_admin='.db_input(isset($vars['ticket_alert_admin'])?1:0)
diff --git a/include/class.export.php b/include/class.export.php
index 3c289550121366e8fd53e59c51e8715f0db27385..372cf62e2d6c55f09e25c59be29ba9691f075df5 100644
--- a/include/class.export.php
+++ b/include/class.export.php
@@ -41,6 +41,7 @@ class Export {
                 'name' =>           'From',
                 'priority_desc' =>  'Priority',
                 'dept_name' =>      'Department',
+                'helptopic' =>      'Help Topic',
                 'source' =>         'Source',
                 'status' =>         'Current Status'
             ),
diff --git a/include/class.faq.php b/include/class.faq.php
index f902dd0f0f4c1412896a319b95fee45730e2ede8..447719fbda62e1eac0fd3fe68cc66cbefc069352 100644
--- a/include/class.faq.php
+++ b/include/class.faq.php
@@ -170,7 +170,7 @@ class FAQ {
         }
 
         //Upload new attachments IF any.
-        if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments'])))
+        if($_FILES['attachments'] && ($files=AttachmentFile::format($_FILES['attachments'])))
             $this->uploadAttachments($files);
 
         $this->reload();
@@ -282,7 +282,7 @@ class FAQ {
         if(($faq=self::lookup($id))) {
             $faq->updateTopics($vars['topics']);
                
-            if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments'])))
+            if($_FILES['attachments'] && ($files=AttachmentFile::format($_FILES['attachments'])))
                 $faq->uploadAttachments($files);
 
             $faq->reload();
diff --git a/include/class.file.php b/include/class.file.php
index 27a8881608ef4bda37a3c2a8be0fb838973626ef..6908dd8eec6efb4e2a24a0618fd1945471859878 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -210,6 +210,53 @@ class AttachmentFile {
         
         return ($id && ($file = new AttachmentFile($id)) && $file->getId()==$id)?$file:null;
     }
+
+    /* 
+      Method formats http based $_FILE uploads - plus basic validation.
+      @restrict - make sure file type & size are allowed.
+     */
+    function format($files, $restrict=false) {
+        global $ost;
+
+        if(!$files || !is_array($files))
+            return null;
+
+        //Reformat $_FILE  for the sane.
+        $attachments = array();
+        foreach($files as $k => $a) {
+            if(is_array($a))
+                foreach($a as $i => $v)
+                    $attachments[$i][$k] = $v;
+        }
+
+        //Basic validation.
+        foreach($attachments as $i => &$file) {
+            //skip no file upload "error" - why PHP calls it an error is beyond me.
+            if($file['error'] && $file['error']==UPLOAD_ERR_NO_FILE) {
+                unset($attachments[$i]); 
+                continue;
+            }
+
+            if($file['error']) //PHP defined error!
+                $file['error'] = 'File upload error #'.$file['error'];
+            elseif(!$file['tmp_name'] || !is_uploaded_file($file['tmp_name']))
+                $file['error'] = 'Invalid or bad upload POST';
+            elseif($restrict) { // make sure file type & size are allowed.
+                if(!$ost->isFileTypeAllowed($file))
+                    $file['error'] = 'Invalid file type for '.Format::htmlchars($file['name']);
+                elseif($ost->getConfig()->getMaxFileSize()
+                        && $file['size']>$ost->getConfig()->getMaxFileSize())
+                    $file['error'] = sprintf('File %s (%s) is too big. Maximum of %s allowed',
+                            Format::htmlchars($file['name']),
+                            Format::file_size($file['size']),
+                            Format::file_size($ost->getConfig()->getMaxFileSize()));
+            }
+        }
+        unset($file);
+
+        return array_filter($attachments);
+    }
+
     /**
      * Removes files and associated meta-data for files which no ticket,
      * canned-response, or faq point to any more.
diff --git a/include/class.format.php b/include/class.format.php
index 220f207426ccaa71089cab46b82467b3dfd7e1b0..7f6cc957b152d2e1b28a033ad481b3166bf7a667 100644
--- a/include/class.format.php
+++ b/include/class.format.php
@@ -34,19 +34,6 @@ class Format {
         return preg_replace('/\s+/', '_', $filename);
     }
 
-    /* re-arrange $_FILES array for the sane */
-    function files($files) {
-
-        foreach($files as $k => $a) {
-            if(is_array($a))
-                foreach($a as $i => $v)
-                    $result[$i][$k] = $v;
-        }
-
-        return $result?array_filter($result):$files;
-    }
-
-
     /* encode text into desired encoding - taking into accout charset when available. */
     function encode($text, $charset=null, $encoding='utf-8') {
 
@@ -68,7 +55,7 @@ class Format {
 
     //Wrapper for utf-8 encoding.
     function utf8encode($text, $charset=null) {
-        return Format::enecode($text, $charset, 'utf-8');
+        return Format::encode($text, $charset, 'utf-8');
     }
 
 	function phone($phone) {
@@ -109,24 +96,50 @@ class Format {
         return Format::html($html,array('safe'=>1,'balance'=>1));
     }
 
+    function sanitize($text, $striptags= true) {
+        
+        //balance and neutralize unsafe tags.
+        $text = Format::safe_html($text);
+
+        //If requested - strip tags with decoding disabled.
+        return $striptags?Format::striptags($text, false):$text;
+    }
+
     function htmlchars($var) {
+        return Format::htmlencode($var);
+    }
+
+    function htmlencode($var) {
         $flags = ENT_COMPAT | ENT_QUOTES;
         if (phpversion() >= '5.4.0')
             $flags |= ENT_HTML401;
+
         return is_array($var)
-            ? array_map(array('Format','htmlchars'),$var)
+            ? array_map(array('Format','htmlencode'), $var)
             : htmlentities($var, $flags, 'UTF-8');
     }
 
+    function htmldecode($var) {
+
+        if(is_array($var))
+            return array_map(array('Format','htmldecode'), $var);
+
+        $flags = ENT_COMPAT;
+        if (phpversion() >= '5.4.0')
+            $flags |= ENT_HTML401;
+            
+        return html_entity_decode($var, $flags, 'UTF-8');
+    }
+
     function input($var) {
-        return Format::htmlchars($var);
+        return Format::htmlencode($var);
     }
 
     //Format text for display..
     function display($text) {
         global $cfg;
 
-        $text=Format::htmlchars($text); //take care of html special chars
+        //make urls clickable.
         if($cfg && $cfg->clickableURLS() && $text)
             $text=Format::clickableurls($text);
 
@@ -140,14 +153,12 @@ class Format {
         return nl2br($text);
     }
 
-    function striptags($var) {
-        $flags = ENT_COMPAT;
-        if (phpversion() >= '5.4.0')
-            $flags |= ENT_HTML401;
-        return is_array($var)
-            ? array_map(array('Format','striptags'),$var)
-              //strip all tags ...no mercy!
-            : strip_tags(html_entity_decode($var, $flags, 'UTF-8'));
+    function striptags($var, $decode=true) {
+
+        if(is_array($var))
+            return array_map(array('Format','striptags'), $var, array_fill(0, count($var), $decode));
+
+        return strip_tags($decode?Format::htmldecode($var):$var);
     }
 
     //make urls clickable. Mainly for display 
diff --git a/include/class.mailer.php b/include/class.mailer.php
index debf2f849a5b09f9a1dd7466602999f42737dc8d..b4ec97c243718f1b86b3a669c8bc901a6c947096 100644
--- a/include/class.mailer.php
+++ b/include/class.mailer.php
@@ -94,7 +94,8 @@ class Mailer {
         //do some cleanup
         $to = preg_replace("/(\r\n|\r|\n)/s",'', trim($to));
         $subject = preg_replace("/(\r\n|\r|\n)/s",'', trim($subject));
-        $body = preg_replace("/(\r\n|\r)/s", "\n", trim($message));
+        //We're decoding html entities here becasuse we only support plain text for now - html support comming.
+        $body = Format::htmldecode(preg_replace("/(\r\n|\r)/s", "\n", trim($message)));
 
         /* Message ID - generated for each outgoing email */
         $messageId = sprintf('<%s%d-%s>', Misc::randCode(6), time(),
diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php
index 5253dfe818eae2e1d8c02757e8db647352cbb598..1bcf6d690cdbc244c233ae65b90ad334ed79185b 100644
--- a/include/class.mailfetch.php
+++ b/include/class.mailfetch.php
@@ -29,20 +29,20 @@ class MailFetcher {
 
     var $charset = 'UTF-8';
     var $encodings =array('UTF-8','WINDOWS-1251', 'ISO-8859-5', 'ISO-8859-1','KOI8-R');
-    
+
     function MailFetcher($email, $charset='UTF-8') {
 
-        
+
         if($email && is_numeric($email)) //email_id
             $email=Email::lookup($email);
 
         if(is_object($email))
             $this->ht = $email->getMailAccountInfo();
         elseif(is_array($email) && $email['host']) //hashtable of mail account info
-            $this->ht = $email; 
+            $this->ht = $email;
         else
             $this->ht = null;
-           
+
         $this->charset = $charset;
 
         if($this->ht) {
@@ -59,12 +59,12 @@ class MailFetcher {
             $this->srvstr=sprintf('{%s:%d/%s', $this->getHost(), $this->getPort(), $this->getProtocol());
             if(!strcasecmp($this->getEncryption(), 'SSL'))
                 $this->srvstr.='/ssl';
-        
+
             $this->srvstr.='/novalidate-cert}';
 
         }
 
-        //Set timeouts 
+        //Set timeouts
         if(function_exists('imap_timeout')) imap_timeout(1,20);
 
     }
@@ -92,7 +92,7 @@ class MailFetcher {
     function getUsername() {
         return $this->ht['username'];
     }
-    
+
     function getPassword() {
         return $this->ht['password'];
     }
@@ -112,7 +112,7 @@ class MailFetcher {
     }
 
     /* Core */
-    
+
     function connect() {
         return ($this->mbox && $this->ping())?$this->mbox:$this->open();
     }
@@ -123,7 +123,7 @@ class MailFetcher {
 
     /* Default folder is inbox - TODO: provide user an option to fetch from diff folder/label */
     function open($box='INBOX') {
-      
+
         if($this->mbox)
            $this->close();
 
@@ -157,7 +157,7 @@ class MailFetcher {
     function createMailbox($folder) {
 
         if(!$folder) return false;
-            
+
         return imap_createmailbox($this->mbox, imap_utf7_encode($this->srvstr.trim($folder)));
     }
 
@@ -187,23 +187,23 @@ class MailFetcher {
             $text=imap_qprint($text);
             break;
         }
-        
+
         return $text;
     }
 
     //Convert text to desired encoding..defaults to utf8
-    function mime_encode($text, $charset=null, $encoding='utf-8') { //Thank in part to afterburner 
+    function mime_encode($text, $charset=null, $encoding='utf-8') { //Thank in part to afterburner
         return Format::encode($text, $charset, $encoding);
     }
-    
+
     //Generic decoder - resulting text is utf8 encoded -> mirrors imap_utf8
     function mime_decode($text, $encoding='utf-8') {
-        
+
         $str = '';
         $parts = imap_mime_header_decode($text);
         foreach ($parts as $part)
             $str.= $this->mime_encode($part->text, $part->charset, $encoding);
-        
+
         return $str?$str:imap_utf8($text);
     }
 
@@ -215,12 +215,12 @@ class MailFetcher {
         $mimeType = array('TEXT', 'MULTIPART', 'MESSAGE', 'APPLICATION', 'AUDIO', 'IMAGE', 'VIDEO', 'OTHER');
         if(!$struct || !$struct->subtype)
             return 'TEXT/PLAIN';
-        
+
         return $mimeType[(int) $struct->type].'/'.$struct->subtype;
     }
 
     function getHeaderInfo($mid) {
-        
+
         if(!($headerinfo=imap_headerinfo($this->mbox, $mid)) || !$headerinfo->from)
             return null;
 
@@ -237,7 +237,7 @@ class MailFetcher {
 
     //search for specific mime type parts....encoding is the desired encoding.
     function getPart($mid, $mimeType, $encoding=false, $struct=null, $partNumber=false) {
-          
+
         if(!$struct && $mid)
             $struct=@imap_fetchstructure($this->mbox, $mid);
 
@@ -264,7 +264,7 @@ class MailFetcher {
         $text='';
         if($struct && $struct->parts) {
             while(list($i, $substruct) = each($struct->parts)) {
-                if($partNumber) 
+                if($partNumber)
                     $prefix = $partNumber . '.';
                 if(($result=$this->getPart($mid, $mimeType, $encoding, $substruct, $prefix.($i+1))))
                     $text.=$result;
@@ -332,29 +332,28 @@ class MailFetcher {
         return imap_fetchheader($this->mbox, $mid,FT_PREFETCHTEXT);
     }
 
-    
+
     function getPriority($mid) {
         return Mail_Parse::parsePriority($this->getHeader($mid));
     }
 
     function getBody($mid) {
-        
+
         $body ='';
         if(!($body = $this->getPart($mid,'TEXT/PLAIN', $this->charset))) {
             if(($body = $this->getPart($mid,'TEXT/HTML', $this->charset))) {
                 //Convert tags of interest before we striptags
                 $body=str_replace("</DIV><DIV>", "\n", $body);
                 $body=str_replace(array("<br>", "<br />", "<BR>", "<BR />"), "\n", $body);
-                $body=Format::html($body); //Balance html tags before stripping.
-                $body=Format::striptags($body); //Strip tags??
+                $body=Format::safe_html($body); //Balance html tags & neutralize unsafe tags.
             }
         }
 
         return $body;
     }
 
-    //email to ticket 
-    function createTicket($mid) { 
+    //email to ticket
+    function createTicket($mid) {
         global $ost;
 
         if(!($mailinfo = $this->getHeaderInfo($mid)))
@@ -387,7 +386,7 @@ class MailFetcher {
 
         if($ost->getConfig()->useEmailPriority())
             $vars['priorityId']=$this->getPriority($mid);
-       
+
         $ticket=null;
         $newticket=true;
         //Check the subject line for possible ID.
@@ -397,14 +396,14 @@ class MailFetcher {
             if(!($ticket=Ticket::lookupByExtId($tid, $vars['email'])))
                 $ticket=null;
         }
-        
+
         $errors=array();
         if($ticket) {
-            if(!($msgid=$ticket->postMessage($vars, 'Email')))
+            if(!($message=$ticket->postMessage($vars, 'Email')))
                 return false;
 
         } elseif (($ticket=Ticket::create($vars, $errors, 'Email'))) {
-            $msgid = $ticket->getLastMsgId();
+            $message = $ticket->getLastMessage();
         } else {
             //Report success if the email was absolutely rejected.
             if(isset($errors['errno']) && $errors['errno'] == 403)
@@ -421,19 +420,22 @@ class MailFetcher {
         }
 
         //Save attachments if any.
-        if($msgid 
+        if($message
                 && $ost->getConfig()->allowEmailAttachments()
-                && ($struct = imap_fetchstructure($this->mbox, $mid)) 
-                && $struct->parts 
+                && ($struct = imap_fetchstructure($this->mbox, $mid))
+                && $struct->parts
                 && ($attachments=$this->getAttachments($struct))) {
-                
+
             foreach($attachments as $a ) {
-                $file = array(
-                        'name'  => $a['name'],
-                        'type'  => $a['type'],
-                        'data'  => $this->decode(imap_fetchbody($this->mbox, $mid, $a['index']), $a['encoding'])
-                        );
-                $ticket->importAttachments(array($file), $msgid, 'M');
+                $file = array('name'  => $a['name'], 'type'  => $a['type']);
+
+                //Check the file  type
+                if(!$ost->isFileTypeAllowed($file))
+                    $file['error'] = 'Invalid file type (ext) for '.Format::htmlchars($file['name']);
+                else //only fetch the body if necessary TODO: Make it a callback.
+                    $file['data'] = $this->decode(imap_fetchbody($this->mbox, $mid, $a['index']), $a['encoding']);
+
+                $message->importAttachment($file);
             }
         }
 
@@ -491,11 +493,11 @@ class MailFetcher {
     /*
        MailFetcher::run()
 
-       Static function called to initiate email polling 
+       Static function called to initiate email polling
      */
     function run() {
         global $ost;
-      
+
         if(!$ost->getConfig()->isEmailPollingEnabled())
             return;
 
@@ -507,7 +509,7 @@ class MailFetcher {
             return;
         }
 
-        //Hardcoded error control... 
+        //Hardcoded error control...
         $MAXERRORS = 5; //Max errors before we start delayed fetch attempts
         $TIMEOUT = 10; //Timeout in minutes after max errors is reached.
 
diff --git a/include/class.mailparse.php b/include/class.mailparse.php
index 342fc5298b59f55a204d86248af0920e7dbe2b0e..3e412675a3ada7ff0d319b15f2e8a89f4b9cade9 100644
--- a/include/class.mailparse.php
+++ b/include/class.mailparse.php
@@ -19,16 +19,16 @@ require_once(PEAR_DIR.'Mail/mimeDecode.php');
 require_once(PEAR_DIR.'Mail/RFC822.php');
 
 class Mail_Parse {
-    
+
     var $mime_message;
     var $include_bodies;
     var $decode_headers;
     var $decode_bodies;
-    
+
     var $struct;
-    
+
     function Mail_parse($mimeMessage,$includeBodies=true,$decodeHeaders=TRUE,$decodeBodies=TRUE){
-        
+
         $this->mime_message=$mimeMessage;
         $this->include_bodies=$includeBodies;
         $this->decode_headers=$decodeHeaders;
@@ -42,9 +42,9 @@ class Mail_Parse {
                         'include_bodies'=> $this->include_bodies,
                         'decode_headers'=> $this->decode_headers,
                         'decode_bodies' => $this->decode_bodies);
-        $this->splitBodyHeader();    
+        $this->splitBodyHeader();
         $this->struct=Mail_mimeDecode::decode($params);
-        
+
         return (PEAR::isError($this->struct) || !(count($this->struct->headers)>1))?FALSE:TRUE;
     }
 
@@ -94,7 +94,7 @@ class Mail_Parse {
         }
         return $array;
     }
-    
+
 
     function getStruct(){
         return $this->struct;
@@ -109,8 +109,8 @@ class Mail_Parse {
     function getError(){
         return PEAR::isError($this->struct)?$this->struct->getMessage():'';
     }
-   
-    
+
+
     function getFromAddressList(){
         return Mail_Parse::parseAddressList($this->struct->headers['from']);
     }
@@ -119,7 +119,7 @@ class Mail_Parse {
         //Delivered-to incase it was a BBC mail.
        return Mail_Parse::parseAddressList($this->struct->headers['to']?$this->struct->headers['to']:$this->struct->headers['delivered-to']);
     }
-        
+
     function getCcAddressList(){
         return $this->struct->headers['cc']?Mail_Parse::parseAddressList($this->struct->headers['cc']):null;
     }
@@ -127,27 +127,27 @@ class Mail_Parse {
     function getMessageId(){
         return $this->struct->headers['message-id'];
     }
- 
+
     function getSubject(){
         return $this->struct->headers['subject'];
     }
-    
+
     function getBody(){
-        
+
         $body='';
         if(!($body=$this->getPart($this->struct,'text/plain'))) {
             if(($body=$this->getPart($this->struct,'text/html'))) {
                 //Cleanup the html.
-                $body=str_replace("</DIV><DIV>", "\n", $body);                        
+                $body=str_replace("</DIV><DIV>", "\n", $body);
                 $body=str_replace(array("<br>", "<br />", "<BR>", "<BR />"), "\n", $body);
-                $body=Format::striptags(Format::html($body));
+                $body=Format::safe_html($body); //Balance html tags & neutralize unsafe tags.
             }
         }
         return $body;
     }
-    
+
     function getPart($struct,$ctypepart) {
-        
+
         if($struct && !$struct->parts) {
             $ctype = @strtolower($struct->ctype_primary.'/'.$struct->ctype_secondary);
             if($ctype && strcasecmp($ctype,$ctypepart)==0)
@@ -164,7 +164,7 @@ class Mail_Parse {
         return $data;
     }
 
-     
+
     function mime_encode($text, $charset=null, $encoding='utf-8') {
         return Format::encode($text, $charset, $encoding);
     }
@@ -175,15 +175,15 @@ class Mail_Parse {
             $part=$this->getStruct();
 
         if($part && $part->disposition
-                && (!strcasecmp($part->disposition,'attachment') 
-                    || !strcasecmp($part->disposition,'inline') 
+                && (!strcasecmp($part->disposition,'attachment')
+                    || !strcasecmp($part->disposition,'inline')
                     || !strcasecmp($part->ctype_primary,'image'))){
-            
+
             if(!($filename=$part->d_parameters['filename']) && $part->d_parameters['filename*'])
                 $filename=$part->d_parameters['filename*']; //Do we need to decode?
-           
+
             $file=array(
-                    'name'  => $filename, 
+                    'name'  => $filename,
                     'type'  => strtolower($part->ctype_primary.'/'.$part->ctype_secondary),
                     'data'  => $this->mime_encode($part->body, $part->ctype_parameters['charset'])
                     );
@@ -245,8 +245,9 @@ class EmailDataParser {
     function EmailDataParser($stream=null) {
         $this->stream = $stream;
     }
-    
+
     function parse($stream) {
+        global $cfg;
 
         $contents ='';
         if(is_resource($stream)) {
@@ -260,7 +261,7 @@ class EmailDataParser {
         $parser= new Mail_Parse($contents);
         if(!$parser->decode()) //Decode...returns false on decoding errors
             return $this->err('Email parse failed ['.$parser->getError().']');
-        
+
         $data =array();
         //FROM address: who sent the email.
         if(($fromlist = $parser->getFromAddressList()) && !PEAR::isError($fromlist)) {
@@ -293,7 +294,7 @@ class EmailDataParser {
                     break;
             }
         }
-            
+
         $data['subject'] = Format::utf8encode($parser->getSubject());
         $data['message'] = Format::utf8encode(Format::stripEmptyLines($parser->getBody()));
         $data['header'] = $parser->getHeader();
@@ -301,8 +302,8 @@ class EmailDataParser {
         $data['priorityId'] = $parser->getPriority();
         $data['emailId'] = $emailId;
 
-        //attachments XXX: worry about encoding??
-        $data['attachments'] = $parser->getAttachments();
+        if($cfg && $cfg->allowEmailAttachments())
+            $data['attachments'] = $parser->getAttachments();
 
         return $data;
     }
diff --git a/include/class.osticket.php b/include/class.osticket.php
index c357ba0e619557954045828fe9b33d32a8ad0874..512d390e284bdd64b7587f523f71c2ec75712fe4 100644
--- a/include/class.osticket.php
+++ b/include/class.osticket.php
@@ -26,11 +26,11 @@ define('LOG_WARN',LOG_WARNING);
 class osTicket {
 
     var $loglevel=array(1=>'Error','Warning','Debug');
-    
+
     //Page errors.
     var $errors;
 
-    //System 
+    //System
     var $system;
 
 
@@ -47,7 +47,7 @@ class osTicket {
     var $csrf;
 
     function osTicket($cfgId) {
-        
+
         $this->config = Config::lookup($cfgId);
 
         //DB based session storage was added starting with v1.7
@@ -109,13 +109,13 @@ class osTicket {
         $name = $name?$name:$this->getCSRF()->getTokenName();
         if(isset($_POST[$name]) && $this->validateCSRFToken($_POST[$name]))
             return true;
-       
+
         if(isset($_SERVER['HTTP_X_CSRFTOKEN']) && $this->validateCSRFToken($_SERVER['HTTP_X_CSRFTOKEN']))
             return true;
 
         $msg=sprintf('Invalid CSRF token [%s] on %s',
                 ($_POST[$name].''.$_SERVER['HTTP_X_CSRFTOKEN']), THISPAGE);
-        $this->logWarning('Invalid CSRF Token '.$name, $msg);
+        $this->logWarning('Invalid CSRF Token '.$name, $msg, false);
 
         return false;
     }
@@ -129,7 +129,7 @@ class osTicket {
     }
 
     function isFileTypeAllowed($file, $mimeType='') {
-       
+
         if(!$file || !($allowedFileTypes=$this->getConfig()->getAllowedFileTypes()))
             return false;
 
@@ -146,38 +146,11 @@ class osTicket {
         return ($ext && is_array($allowed) && in_array(".$ext", $allowed));
     }
 
-    /* Function expects a well formatted array - see  Format::files()
-       It's up to the caller to reject the upload on error.
-     */
-    function validateFileUploads(&$files, $checkFileTypes=true) {
-       
-        $errors=0;
-        foreach($files as &$file) {
-            //skip no file upload "error" - why PHP calls it an error is beyond me.
-            if($file['error'] && $file['error']==UPLOAD_ERR_NO_FILE) continue;
-
-            if($file['error']) //PHP defined error!
-                $file['error'] = 'File upload error #'.$file['error'];
-            elseif(!$file['tmp_name'] || !is_uploaded_file($file['tmp_name']))
-                $file['error'] = 'Invalid or bad upload POST';
-            elseif($checkFileTypes && !$this->isFileTypeAllowed($file))
-                $file['error'] = 'Invalid file type for '.Format::htmlchars($file['name']);
-            elseif($file['size']>$this->getConfig()->getMaxFileSize())
-                $file['error'] = sprintf('File (%s) is too big. Maximum of %s allowed',
-                        Format::htmlchars($file['name']),
-                        Format::file_size($this->getConfig()->getMaxFileSize()));
-            
-            if($file['error']) $errors++;
-        }
-
-        return (!$errors);
-    }
-
     /* Replace Template Variables */
     function replaceTemplateVariables($input, $vars=array()) {
-        
+
         $replacer = new VariableReplacer();
-        $replacer->assign(array_merge($vars, 
+        $replacer->assign(array_merge($vars,
                     array('url' => $this->getConfig()->getBaseUrl())
                     ));
 
@@ -247,7 +220,7 @@ class osTicket {
 
 
     function alertAdmin($subject, $message, $log=false) {
-                
+
         //Set admin's email address
         if(!($to=$this->getConfig()->getAdminEmail()))
             $to=ADMIN_EMAIL;
@@ -258,7 +231,7 @@ class osTicket {
 
         //Try getting the alert email.
         $email=null;
-        if(!($email=$this->getConfig()->getAlertEmail())) 
+        if(!($email=$this->getConfig()->getAlertEmail()))
             $email=$this->getConfig()->getDefaultEmail(); //will take the default email.
 
         if($email) {
@@ -284,7 +257,7 @@ class osTicket {
     function logWarning($title, $message, $alert=true) {
         return $this->log(LOG_WARN, $title, $message, $alert);
     }
-    
+
     function logError($title, $error, $alert=true) {
         return $this->log(LOG_ERR, $title, $error, $alert);
     }
@@ -302,8 +275,8 @@ class osTicket {
         //We are providing only 3 levels of logs. Windows style.
         switch($priority) {
             case LOG_EMERG:
-            case LOG_ALERT: 
-            case LOG_CRIT: 
+            case LOG_ALERT:
+            case LOG_CRIT:
             case LOG_ERR:
                 $level=1; //Error
                 break;
@@ -333,9 +306,9 @@ class osTicket {
             ',log_type='.db_input($loglevel[$level]).
             ',log='.db_input($message).
             ',ip_address='.db_input($_SERVER['REMOTE_ADDR']);
-        
+
         mysql_query($sql); //don't use db_query to avoid possible loop.
-        
+
         return true;
     }
 
@@ -347,11 +320,48 @@ class osTicket {
         //System logs
         $sql='DELETE  FROM '.SYSLOG_TABLE.' WHERE DATE_ADD(created, INTERVAL '.$gp.' MONTH)<=NOW()';
         db_query($sql);
-        
+
         //TODO: Activity logs
 
         return true;
     }
+    /*
+     * Util functions
+     *
+     */
+
+    function get_var($index, $vars, $default='', $type=null) {
+
+        if(is_array($vars)
+                && array_key_exists($index, $vars)
+                && (!$type || gettype($vars[$index])==$type))
+            return $vars[$index];
+
+        return $default;
+    }
+
+    function get_db_input($index, $vars, $quote=true) {
+        return db_input($this->get_var($index, $vars), $quote);
+    }
+
+    function get_path_info() {
+        if(isset($_SERVER['PATH_INFO']))
+            return $_SERVER['PATH_INFO'];
+
+        if(isset($_SERVER['ORIG_PATH_INFO']))
+            return $_SERVER['ORIG_PATH_INFO'];
+
+        //TODO: conruct possible path info.
+
+        return null;
+    }
+
+    /* returns true if script is being executed via commandline */
+    function is_cli() {
+        return (!strcasecmp(substr(php_sapi_name(), 0, 3), 'cli')
+                || (!$_SERVER['REQUEST_METHOD'] && !$_SERVER['HTTP_HOST']) //Fallback when php-cgi binary is used via cli
+                );
+    }
 
     /**** static functions ****/
     function start($configId) {
diff --git a/include/class.pdf.php b/include/class.pdf.php
index b0650b7796de3c2932cc1f24e0dd6527f5a1f46a..f210149346c7d36d561bdca8bc3fb9ad59771226 100644
--- a/include/class.pdf.php
+++ b/include/class.pdf.php
@@ -21,11 +21,11 @@ require (FPDF_DIR . 'fpdf.php');
 
 class Ticket2PDF extends FPDF
 {
-	
+
 	var $includenotes = false;
-	
+
 	var $pageOffset = 0;
-	
+
     var $ticket = null;
 
 	function Ticket2PDF($ticket, $psize='Letter', $notes=false) {
@@ -47,7 +47,7 @@ class Ticket2PDF extends FPDF
     function getTicket() {
         return $this->ticket;
     }
-	
+
 	//report header...most stuff are hard coded for now...
 	function Header() {
         global $cfg;
@@ -66,7 +66,7 @@ class Ticket2PDF extends FPDF
         $this->Cell(0, 5, 'Date & Time based on GMT '.$_SESSION['TZ_OFFSET'], 0, 1, 'R');
 		$this->Ln(10);
 	}
-	
+
 	//Page footer baby
 	function Footer() {
         global $thisstaff;
@@ -94,10 +94,10 @@ class Ticket2PDF extends FPDF
 
         if(function_exists('iconv'))
             return iconv('UTF-8', 'windows-1252', $text);
-        
+
         return utf8_encode($text);
     }
-    
+
     function _print() {
 
         if(!($ticket=$this->getTicket()))
@@ -107,7 +107,7 @@ class Ticket2PDF extends FPDF
         $l = 35;
         $c = $w-$l;
 
-        
+
         $this->SetFont('Arial', 'B', 11);
         $this->cMargin = 0;
         $this->SetFont('Arial', 'B', 11);
@@ -215,7 +215,11 @@ class Ticket2PDF extends FPDF
                         'R'=>array(255, 224, 179),
                         'N'=>array(250, 250, 210));
         //Get ticket thread
-        if(($entries = $ticket->getThread(($this->includenotes)))) { 
+        $types = array('M', 'R');
+        if($this->includenotes)
+            $types[] = 'N';
+
+        if(($entries = $ticket->getThreadEntries($types))) {
             foreach($entries as $entry) {
 
                 $color = $colors[$entry['thread_type']];
@@ -228,11 +232,12 @@ class Ticket2PDF extends FPDF
                 $this->Cell($w/2, 7, $entry['poster'], 'TBR', 1, 'L', true);
                 $this->SetFont('');
                 $text= $entry['body'];
-                if($entry['attachments'] 
-                        && ($attachments = $ticket->getAttachments($entry['id'], $entry['thread_type']))) {
+                if($entry['attachments']
+                        && ($tentry=$ticket->getThreadEntry($entry['id']))
+                        && ($attachments = $tentry->getAttachments())) {
                     foreach($attachments as $attachment)
                         $files[]= $attachment['name'];
-                    
+
                     $text.="\nFiles Attached: [".implode(', ',$files)."]\n";
                 }
                 $this->WriteText($w*2, $text, 1);
@@ -240,6 +245,6 @@ class Ticket2PDF extends FPDF
             }
         }
 
-    }	
+    }
 }
 ?>
diff --git a/include/class.thread.php b/include/class.thread.php
index cae40fa3b0ab1c419b1ce2443a91c3481e6e57e2..705ba5ec5c623b40844d648b5cef6ccbfc945e5b 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -3,7 +3,7 @@
     class.thread.php
 
     Ticket thread
-    TODO: move thread related logic here from class.ticket.php
+    XXX: Please DO NOT add any ticket related logic! use ticket class.
 
     Peter Rotich <peter@osticket.com>
     Copyright (c)  2006-2013 osTicket
@@ -16,6 +16,188 @@
 **********************************************************************/
 include_once(INCLUDE_DIR.'class.ticket.php');
 
+//Ticket thread.
+class Thread {
+
+    var $id; // same as ticket ID.
+    var $ticket;
+
+    function Thread($ticket) {
+
+        $this->ticket = $ticket;
+
+        $this->id = 0;
+
+        $this->load();
+    }
+
+    function load() {
+
+        if(!$this->getTicketId())
+            return null;
+
+        $sql='SELECT ticket.ticket_id as id '
+            .' ,count(DISTINCT attach.attach_id) as attachments '
+            .' ,count(DISTINCT message.id) as messages '
+            .' ,count(DISTINCT response.id) as responses '
+            .' ,count(DISTINCT note.id) as notes '
+            .' FROM '.TICKET_TABLE.' ticket '
+            .' 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($this->getTicketId())
+            .' GROUP BY ticket.ticket_id';
+
+        if(!($res=db_query($sql)) || !db_num_rows($res))
+            return false;
+
+        $this->ht = db_fetch_array($res);
+
+        $this->id = $this->ht['id'];
+
+        return true;
+    }
+
+    function getId() {
+        return $this->id;
+    }
+
+    function getTicketId() {
+        return $this->getTicket()?$this->getTicket()->getId():0;
+    }
+
+    function getTicket() {
+        return $this->ticket;
+    }
+
+    function getNumAttachments() {
+        return $this->ht['attachments'];
+    }
+
+    function getNumMessages() {
+        return $this->ht['messages'];
+    }
+
+    function getNumResponses() {
+        return $this->ht['responses'];
+    }
+
+    function getNumNotes() {
+        return $this->ht['notes'];
+    }
+
+    function getCount() {
+        return $this->getNumMessages() + $this->getNumResponses();
+    }
+
+    function getMessages() {
+        return $this->getEntries('M');
+    }
+
+    function getResponses() {
+        return $this->getEntries('R');
+    }
+
+    function getNotes() {
+        return $this->getEntries('N');
+    }
+
+    function getEntries($type, $order='ASC') {
+
+        if(!$order || !in_array($order, array('DESC','ASC')))
+            $order='ASC';
+
+        $sql='SELECT thread.* '
+            .' ,count(DISTINCT attach.attach_id) as attachments '
+            .' FROM '.TICKET_THREAD_TABLE.' thread '
+            .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach
+                ON (thread.ticket_id=attach.ticket_id
+                        AND thread.id=attach.ref_id
+                        AND thread.thread_type=attach.ref_type) '
+            .' WHERE  thread.ticket_id='.db_input($this->getTicketId());
+
+        if($type && is_array($type))
+            $sql.=' AND thread.thread_type IN('.implode(',', db_input($type)).')';
+        elseif($type)
+            $sql.=' AND thread.thread_type='.db_input($type);
+
+        $sql.=' GROUP BY thread.id '
+             .' ORDER BY thread.created '.$order;
+
+        $entries = array();
+        if(($res=db_query($sql)) && db_num_rows($res))
+            while($rec=db_fetch_array($res))
+                $entries[] = $rec;
+
+        return $entries;
+    }
+
+    function getEntry($id) {
+        return ThreadEntry::lookup($id, $this->getTicketId());
+    }
+
+    function addNote($vars, &$errors) {
+
+        //Add ticket Id.
+        $vars['ticketId'] = $this->getTicketId();
+
+        return Note::create($vars, $errors);
+    }
+
+    function addMessage($vars, &$errors) {
+
+        $vars['ticketId'] = $this->getTicketId();
+        $vars['staffId'] = 0;
+
+        return Message::create($vars, $errors);
+    }
+
+    function addResponse($vars, &$errors) {
+
+        $vars['ticketId'] = $this->getTicketId();
+
+        return Response::create($vars, $errors);
+    }
+
+    function deleteAttachments() {
+
+        $deleted=0;
+        // Clear reference table
+        $res=db_query('DELETE FROM '.TICKET_ATTACHMENT_TABLE.' WHERE ticket_id='.db_input($this->getTicketId()));
+        if ($res && db_affected_rows())
+            $deleted = AttachmentFile::deleteOrphans();
+
+        return $deleted;
+    }
+
+    function delete() {
+
+        $res=db_query('DELETE FROM '.TICKET_THREAD_TABLE.' WHERE ticket_id='.db_input($this->getTicketId()));
+        if(!$res || !db_affected_rows())
+            return false;
+
+        $this->deleteAttachments();
+
+        return true;
+    }
+
+    /* static */
+    function lookup($ticket) {
+
+        return ($ticket
+                && is_object($ticket)
+                && ($thread = new Thread($ticket))
+                && $thread->getId()
+                )?$thread:null;
+    }
+}
+
+
 Class ThreadEntry {
 
     var $id;
@@ -23,7 +205,10 @@ Class ThreadEntry {
 
     var $staff;
     var $ticket;
-    
+
+    var $attachments;
+
+
     function ThreadEntry($id, $type='', $ticketId=0) {
         $this->load($id, $type, $ticketId);
     }
@@ -33,12 +218,14 @@ Class ThreadEntry {
         if(!$id && !($id=$this->getId()))
             return false;
 
-        $sql='SELECT thread.* '
+        $sql='SELECT thread.*, info.* '
             .' ,count(DISTINCT attach.attach_id) as attachments '
             .' FROM '.TICKET_THREAD_TABLE.' thread '
-            .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach 
-                ON (thread.ticket_id=attach.ticket_id 
-                        AND thread.id=attach.ref_id 
+            .' LEFT JOIN '.TICKET_EMAIL_INFO_TABLE.' info
+                ON (thread.id=info.message_id) '
+            .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach
+                ON (thread.ticket_id=attach.ticket_id
+                        AND thread.id=attach.ref_id
                         AND thread.thread_type=attach.ref_type) '
             .' WHERE  thread.id='.db_input($id);
 
@@ -49,7 +236,7 @@ Class ThreadEntry {
             $sql.=' AND thread.ticket_id='.db_input($ticketId);
 
         $sql.=' GROUP BY thread.id ';
-        
+
         if(!($res=db_query($sql)) || !db_num_rows($res))
             return false;
 
@@ -57,6 +244,7 @@ Class ThreadEntry {
         $this->id = $this->ht['id'];
 
         $this->staff = $this->ticket = null;
+        $this->attachments = array();
 
         return true;
     }
@@ -122,13 +310,186 @@ Class ThreadEntry {
     }
 
     function getStaff() {
-      
+
         if(!$this->staff && $this->getStaffId())
             $this->staff = Staff::lookup($this->getStaffId());
 
         return $this->staff;
     }
 
+    function getEmailHeader() {
+        return $this->ht['headers'];
+    }
+
+    function isAutoResponse() {
+        return $this->getEmailHeader()?TicketFilter::isAutoResponse($this->getEmailHeader()):false;
+    }
+
+    //Web uploads - caller is expected to format, validate and set any errors.
+    function uploadFiles($files) {
+
+        if(!$files || !is_array($files))
+            return false;
+
+        $uploaded=array();
+        foreach($files as $file) {
+            if($file['error'] && $file['error']==UPLOAD_ERR_NO_FILE)
+                continue;
+
+            if(!$file['error']
+                    && ($id=AttachmentFile::upload($file))
+                    && $this->saveAttachment($id))
+                $uploaded[]=$id;
+            else {
+                if(!$file['error'])
+                    $error = 'Unable to upload file - '.$file['name'];
+                elseif(is_numeric($file['error']))
+                    $error ='Error #'.$file['error']; //TODO: Transplate to string.
+                else
+                    $error = $file['error'];
+                /*
+                 Log the error as an internal note.
+                 XXX: We're doing it here because it will eventually become a thread post comment (hint: comments coming!)
+                 XXX: logNote must watch for possible loops
+               */
+                $this->getTicket()->logNote('File Upload Error', $error, 'SYSTEM', false);
+            }
+
+        }
+
+        return $uploaded;
+    }
+
+    function importAttachments($attachments) {
+
+        if(!$attachments || !is_array($attachments))
+            return null;
+
+        $files = array();
+        foreach($attachments as  $attachment)
+            if(($id=$this->importAttachment($attachment)))
+                $files[] = $id;
+
+        return $files;
+    }
+
+    /* Emailed & API attachments handler */
+    function importAttachment($attachment) {
+
+        if(!$attachment || !is_array($attachment))
+            return null;
+
+        $id=0;
+        if (!$attachment['error'] && ($id=$this->saveAttachment($attachment)))
+            $files[] = $id;
+        else {
+            $error = $attachment['error'];
+
+            if(!$error)
+                $error = 'Unable to import attachment - '.$attachment['name'];
+
+            $this->getTicket()->logNote('File Import Error', $error, 'SYSTEM', false);
+        }
+
+        return $id;
+    }
+
+   /*
+    Save attachment to the DB.
+    @file is a mixed var - can be ID or file hashtable.
+    */
+    function saveAttachment($file) {
+
+        if(!($fileId=is_numeric($file)?$file:AttachmentFile::save($file)))
+            return 0;
+
+        $sql ='INSERT INTO '.TICKET_ATTACHMENT_TABLE.' SET created=NOW() '
+             .' ,file_id='.db_input($fileId)
+             .' ,ticket_id='.db_input($this->getTicketId())
+             .' ,ref_id='.db_input($this->getId())
+             .' ,ref_type='.db_input($this->getType());
+
+        return (db_query($sql) && ($id=db_insert_id()))?$id:0;
+    }
+
+    function saveAttachments($files) {
+        $ids=array();
+        foreach($files as $file)
+           if(($id=$this->saveAttachment($file)))
+               $ids[] = $id;
+
+        return $ids;
+    }
+
+    function getAttachments() {
+
+        if($this->attachments)
+            return $this->attachments;
+
+        //XXX: inner join the file table instead?
+        $sql='SELECT a.attach_id, f.id as file_id, f.size, f.hash as file_hash, f.name '
+            .' FROM '.FILE_TABLE.' f '
+            .' INNER JOIN '.TICKET_ATTACHMENT_TABLE.' a ON(f.id=a.file_id) '
+            .' WHERE a.ticket_id='.db_input($this->getTicketId())
+            .' AND a.ref_id='.db_input($this->getId())
+            .' AND a.ref_type='.db_input($this->getType());
+
+        $this->attachments = array();
+        if(($res=db_query($sql)) && db_num_rows($res)) {
+            while($rec=db_fetch_array($res))
+                $this->attachments[] = $rec;
+        }
+
+        return $this->attachments;
+    }
+
+    function getAttachmentsLinks($file='attachment.php', $target='', $separator=' ') {
+
+        $str='';
+        foreach($this->getAttachments() as $attachment ) {
+            /* The hash can be changed  but must match validation in @file */
+            $hash=md5($attachment['file_id'].session_id().$attachment['file_hash']);
+            $size = '';
+            if($attachment['size'])
+                $size=sprintf('<em>(%s)</em>', Format::file_size($attachment['size']));
+
+            $str.=sprintf('<a class="Icon file" href="%s?id=%d&h=%s" target="%s">%s</a>%s&nbsp;%s',
+                    $file, $attachment['attach_id'], $hash, $target, Format::htmlchars($attachment['name']), $size, $separator);
+        }
+
+        return $str;
+    }
+
+
+    /* Returns file names with id as key */
+    function getFiles() {
+
+        $files = array();
+        foreach($this->getAttachments() as $attachment)
+            $files[$attachment['file_id']] = $attachment['name'];
+
+        return $files;
+    }
+
+
+    /* save email info
+     * TODO: Refactor it to include outgoing emails on responses.
+     */
+
+    function saveEmailInfo($vars) {
+
+        if(!$vars || !$vars['mid'])
+            return 0;
+
+        $sql='INSERT INTO '.TICKET_EMAIL_INFO_TABLE
+            .' SET message_id='.db_input($this->getId()) //TODO: change it to thread_id
+            .', email_mid='.db_input($vars['mid']) //TODO: change it to mid.
+            .', headers='.db_input($vars['header']);
+
+        return db_query($sql)?db_insert_id():0;
+    }
+
+
     /* variables */
 
     function asVar() {
@@ -164,12 +525,59 @@ Class ThreadEntry {
     /* static calls */
 
     function lookup($id, $tid=0, $type='') {
-        return ($id 
-                && is_numeric($id) 
-                && ($e = new ThreadEntry($id, $type, $tid)) 
+        return ($id
+                && is_numeric($id)
+                && ($e = new ThreadEntry($id, $type, $tid))
                 && $e->getId()==$id
                 )?$e:null;
     }
+
+    //new entry ... we're trusting the caller to check validity of the data.
+    function create($vars) {
+
+        //Must have...
+        if(!$vars['ticketId'] || !$vars['type'] || !in_array($vars['type'], array('M','R','N')))
+            return false;
+
+        $sql=' INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW() '
+            .' ,thread_type='.db_input($vars['type'])
+            .' ,ticket_id='.db_input($vars['ticketId'])
+            .' ,title='.db_input(Format::sanitize($vars['title']))
+            .' ,body='.db_input(Format::sanitize($vars['body']))
+            .' ,staff_id='.db_input($vars['staffId'])
+            .' ,poster='.db_input($vars['poster'])
+            .' ,source='.db_input($vars['source']);
+
+        if(isset($vars['pid']))
+            $sql.=' ,pid='.db_input($vars['pid']);
+
+        if($vars['ip_address'])
+            $sql.=' ,ip_address='.db_input($vars['ip_address']);
+
+        //echo $sql;
+        if(!db_query($sql) || !($entry=self::lookup(db_insert_id(), $vars['ticketId'])))
+            return false;
+
+        /************* ATTACHMENTS *****************/
+
+        //Upload/save attachments IF ANY
+        if($vars['files']) //expects well formatted and VALIDATED files array.
+            $entry->uploadFiles($vars['files']);
+
+        //Emailed or API attachments
+        if($vars['attachments'])
+            $entry->importAttachments($vars['attachments']);
+
+        //Canned attachments...
+        if($vars['cannedattachments'] && is_array($vars['cannedattachments']))
+            $entry->saveAttachments($vars['cannedattachments']);
+
+        return $entry;
+    }
+
+    function add($vars) {
+        return ($entry=self::create($vars))?$entry->getId():0;
+    }
 }
 
 /* Message - Ticket thread entry of type message */
@@ -183,11 +591,30 @@ class Message extends ThreadEntry {
         return $this->getTitle();
     }
 
-    function lookup($id, $tid, $type='M') {
-                
-        return ($id 
+    function create($vars, &$errors) {
+        return self::lookup(self::add($vars, $errors));
+    }
+
+    function add($vars, &$errors) {
+
+        if(!$vars || !is_array($vars) || !$vars['ticketId'])
+            $errors['err'] = 'Missing or invalid data';
+        elseif(!$vars['message'])
+            $errors['message'] = 'Message required';
+
+        if($errors) return false;
+
+        $vars['type'] = 'M';
+        $vars['body'] = $vars['message'];
+
+        return ThreadEntry::add($vars);
+    }
+
+    function lookup($id, $tid=0, $type='M') {
+
+        return ($id
                 && is_numeric($id)
-                && ($m = new Message($id, $tid)) 
+                && ($m = new Message($id, $tid))
                 && $m->getId()==$id
                 )?$m:null;
     }
@@ -208,11 +635,33 @@ class Response extends ThreadEntry {
         return $this->getStaff();
     }
 
-    function lookup($id, $tid, $type='R') {
-                
-        return ($id 
+    function create($vars, &$errors) {
+        return self::lookup(self::add($vars, $errors));
+    }
+
+    function add($vars, &$errors) {
+
+        if(!$vars || !is_array($vars) || !$vars['ticketId'])
+            $errors['err'] = 'Missing or invalid data';
+        elseif(!$vars['response'])
+            $errors['response'] = 'Response required';
+
+        if($errors) return false;
+
+        $vars['type'] = 'R';
+        $vars['body'] = $vars['response'];
+        if(!$vars['pid'] && $vars['msgId'])
+            $vars['pid'] = $vars['msgId'];
+
+        return ThreadEntry::add($vars);
+    }
+
+
+    function lookup($id, $tid=0, $type='R') {
+
+        return ($id
                 && is_numeric($id)
-                && ($r = new Response($id, $tid))           
+                && ($r = new Response($id, $tid))
                 && $r->getId()==$id
                 )?$r:null;
     }
@@ -229,11 +678,33 @@ class Note extends ThreadEntry {
         return $this->getBody();
     }
 
-    function lookup($id, $tid, $type='N') {
-                
-        return ($id 
+    /* static */
+    function create($vars, &$errors) {
+        return self::lookup(self::add($vars, $errors));
+    }
+
+    function add($vars, &$errors) {
+
+        //Check required params.
+        if(!$vars || !is_array($vars) || !$vars['ticketId'])
+            $errors['err'] = 'Missing or invalid data';
+        elseif(!$vars['note'])
+            $errors['note'] = 'Note required';
+
+        if($errors) return false;
+
+        //TODO: use array_intersect_key  when we move to php 5 to extract just what we need.
+        $vars['type'] = 'N';
+        $vars['body'] = $vars['note'];
+
+        return ThreadEntry::add($vars);
+    }
+
+    function lookup($id, $tid=0, $type='N') {
+
+        return ($id
                 && is_numeric($id)
-                && ($n = new Note($id, $tid))          
+                && ($n = new Note($id, $tid))
                 && $n->getId()==$id
                 )?$n:null;
     }
diff --git a/include/class.ticket.php b/include/class.ticket.php
index a7a16e4644ba357db5f619ec50760de71de60a20..17d2c56b3545733d3569261eacb1d8bd79328070 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -34,29 +34,12 @@ include_once(INCLUDE_DIR.'class.canned.php');
 class Ticket {
 
     var $id;
-    var $extid;
-    var $email;
-    var $status;
-    var $created;
-    var $reopened;
-    var $updated;
-    var $lastrespdate;
-    var $lastmsgdate;
-    var $duedate;
-    var $priority;
-    var $priority_id;
-    var $fullname;
-    var $staff_id;
-    var $team_id;
-    var $dept_id;
-    var $topic_id;
-    var $dept_name;
-    var $subject;
-    var $helptopic;
-    var $overdue;
+    var $number;
+
+    var $ht;
 
     var $lastMsgId;
-    
+
     var $dept;  //Dept obj
     var $sla;   // SLA obj
     var $staff; //Staff obj
@@ -64,25 +47,22 @@ class Ticket {
     var $team;  //Team obj
     var $topic; //Topic obj
     var $tlock; //TicketLock obj
-    
-    function Ticket($id){
+
+    var $thread; //Thread obj.
+
+    function Ticket($id) {
         $this->id = 0;
         $this->load($id);
     }
-    
+
     function load($id=0) {
 
         if(!$id && !($id=$this->getId()))
             return false;
 
-        //TODO: delete helptopic field in ticket table.
-       
         $sql='SELECT  ticket.*, lock_id, dept_name, priority_desc '
-            .' ,IF(sla.id IS NULL, NULL, DATE_ADD(ticket.created, INTERVAL sla.grace_period HOUR)) as sla_duedate ' 
+            .' ,IF(sla.id IS NULL, NULL, DATE_ADD(ticket.created, INTERVAL sla.grace_period HOUR)) as sla_duedate '
             .' ,count(attach.attach_id) as attachments '
-            .' ,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 '.SLA_TABLE.' sla ON (ticket.sla_id=sla.id AND sla.isactive=1) '
@@ -92,12 +72,6 @@ class Ticket {
                 .'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';
 
@@ -105,35 +79,12 @@ class Ticket {
         if(!($res=db_query($sql)) || !db_num_rows($res))
             return false;
 
-        
-        $this->ht=db_fetch_array($res);
-        
+
+        $this->ht = db_fetch_array($res);
+
         $this->id       = $this->ht['ticket_id'];
-        $this->extid    = $this->ht['ticketID'];
-         
-        $this->email    = $this->ht['email'];
-        $this->fullname = $this->ht['name'];
-        $this->status   = $this->ht['status'];
-        $this->created  = $this->ht['created'];
-        $this->reopened = $this->ht['reopened'];
-        $this->updated  = $this->ht['updated'];
-        $this->duedate  = $this->ht['duedate'];
-        $this->closed   = $this->ht['closed'];
-        $this->lastmsgdate  = $this->ht['lastmessagedate'];
-        $this->lastrespdate = $this->ht['lastresponsedate'];
-        
-        $this->lock_id  = $this->ht['lock_id'];
-        $this->priority_id = $this->ht['priority_id'];
-        $this->priority = $this->ht['priority_desc'];
-        $this->staff_id = $this->ht['staff_id'];
-        $this->team_id = $this->ht['team_id']; 
-        $this->dept_id  = $this->ht['dept_id'];
-        $this->dept_name = $this->ht['dept_name'];
-        $this->sla_id = $this->ht['sla_id'];
-        $this->topic_id = $this->ht['topic_id'];
-        $this->subject = $this->ht['subject'];
-        $this->overdue = $this->ht['isoverdue'];
-        
+        $this->number   = $this->ht['ticketID'];
+
         //Reset the sub classes (initiated ondemand)...good for reloads.
         $this->staff = null;
         $this->client = null;
@@ -143,14 +94,18 @@ class Ticket {
         $this->tlock = null;
         $this->stats = null;
         $this->topic = null;
-        
+        $this->thread = null;
+
+        //REQUIRED: Preload thread obj - checked on lookup!
+        $this->getThread();
+
         return true;
     }
-        
+
     function reload() {
         return $this->load();
     }
-    
+
     function isOpen() {
         return (strcasecmp($this->getStatus(),'Open')==0);
     }
@@ -168,9 +123,9 @@ class Ticket {
     }
 
     function isOverdue() {
-        return ($this->overdue);
+        return ($this->ht['isoverdue']);
     }
-    
+
     function isAnswered() {
        return ($this->ht['isanswered']);
     }
@@ -195,10 +150,10 @@ class Ticket {
         if(!is_object($client) && !($client=Client::lookup($client)))
             return false;
 
-        if(!strcasecmp($client->getEmail(),$this->getEmail()))
+        if(!strcasecmp($client->getEmail(), $this->getEmail()))
             return true;
 
-        return ($cfg && $cfg->showRelatedTickets() 
+        return ($cfg && $cfg->showRelatedTickets()
             && $client->getTicketId()==$this->getExtId());
     }
 
@@ -208,15 +163,15 @@ class Ticket {
     }
 
     function getExtId() {
-        return  $this->extid;
+        return  $this->getNumber();
     }
 
     function getNumber() {
-        return $this->getExtId();
+        return $this->number;
     }
-   
-    function getEmail(){
-        return $this->email;
+
+    function getEmail() {
+        return $this->ht['email'];
     }
 
     function getAuthToken() {
@@ -224,25 +179,25 @@ class Ticket {
         return md5($this->getId() . $this->getEmail() . SECRET_SALT);
     }
 
-    function getName(){
-        return $this->fullname;
+    function getName() {
+        return $this->ht['name'];
     }
 
     function getSubject() {
-        return $this->subject;
+        return $this->ht['subject'];
     }
 
     /* Help topic title  - NOT object -> $topic */
     function getHelpTopic() {
 
-        if(!$this->helpTopic && ($topic=$this->getTopic()))
-            $this->helpTopic = $topic->getName();
-            
-        return $this->helpTopic;
+        if(!$this->ht['helptopic'] && ($topic=$this->getTopic()))
+            $this->ht['helptopic'] = $topic->getName();
+
+        return $this->ht['helptopic'];
     }
-   
-    function getCreateDate(){
-        return $this->created;
+
+    function getCreateDate() {
+        return $this->ht['created'];
     }
 
     function getOpenDate() {
@@ -250,24 +205,24 @@ class Ticket {
     }
 
     function getReopenDate() {
-        return $this->reopened;
+        return $this->ht['reopened'];
     }
-    
-    function getUpdateDate(){
-        return $this->updated;
+
+    function getUpdateDate() {
+        return $this->ht['updated'];
     }
 
-    function getDueDate(){
-        return $this->duedate;
+    function getDueDate() {
+        return $this->ht['duedate'];
     }
 
-    function getSLADuedate() {
+    function getSLADueDate() {
         return $this->ht['sla_duedate'];
     }
 
     function getEstDueDate() {
 
-        //Real due date 
+        //Real due date
         if(($duedate=$this->getDueDate()))
             return $duedate;
 
@@ -275,30 +230,34 @@ class Ticket {
         return $this->getSLADueDate();
     }
 
-    function getCloseDate(){
-        return $this->closed;
+    function getCloseDate() {
+        return $this->ht['closed'];
     }
 
-    function getStatus(){
-        return $this->status;
+    function getStatus() {
+        return $this->ht['status'];
     }
-   
-    function getDeptId(){
-       return $this->dept_id;
+
+    function getDeptId() {
+       return $this->ht['dept_id'];
     }
-   
-    function getDeptName(){
-       return $this->dept_name;
+
+    function getDeptName() {
+
+        if(!$this->ht['dept_name'] && ($dept = $this->getDept()))
+            $this->ht['dept_name'] = $dept->getName();
+
+       return $this->ht['dept_name'];
     }
 
     function getPriorityId() {
-        return $this->priority_id;
+        return $this->ht['priority_id'];
     }
-    
-    function getPriority() {
-        return $this->priority;
+
+    function getPriority() { //TODO: Make it an obj.
+        return  $this->ht['priority_desc'];
     }
-     
+
     function getPhone() {
         return $this->ht['phone'];
     }
@@ -318,7 +277,7 @@ class Ticket {
     function getSource() {
         return $this->ht['source'];
     }
-    
+
     function getIP() {
         return $this->ht['ip_address'];
     }
@@ -341,24 +300,24 @@ class Ticket {
                     'duedate'   =>  $this->getDueDate()?(Format::userdate('m/d/Y', Misc::db2gmtime($this->getDueDate()))):'',
                     'time'  =>  $this->getDueDate()?(Format::userdate('G:i', Misc::db2gmtime($this->getDueDate()))):'',
                     );
-                  
+
         return $info;
     }
 
     function getLockId() {
-        return $this->lock_id;
+        return $this->ht['lock_id'];
     }
-    
-    function getLock(){
-        
+
+    function getLock() {
+
         if(!$this->tlock && $this->getLockId())
-            $this->tlock= TicketLock::lookup($this->getLockId(),$this->getId());
-        
+            $this->tlock= TicketLock::lookup($this->getLockId(), $this->getId());
+
         return $this->tlock;
     }
-    
+
     function acquireLock($staffId, $lockTime) {
-       
+
         if(!$staffId or !$lockTime) //Lockig disabled?
             return null;
 
@@ -369,18 +328,18 @@ class Ticket {
 
             //Lock already exits...renew it
             $lock->renew($lockTime); //New clock baby.
-            
+
             return $lock;
         }
         //No lock on the ticket or it is expired
-        $this->tlock=null; //clear crap
-        $this->lock_id=TicketLock::acquire($this->getId(), $staffId, $lockTime); //Create a new lock..
+        $this->tlock = null; //clear crap
+        $this->ht['lock_id'] = TicketLock::acquire($this->getId(), $staffId, $lockTime); //Create a new lock..
         //load and return the newly created lock if any!
         return $this->getLock();
     }
-    
-    function getDept(){
-        
+
+    function getDept() {
+
         if(!$this->dept && $this->getDeptId())
             $this->dept= Dept::lookup($this->getDeptId());
 
@@ -394,12 +353,12 @@ class Ticket {
 
         return $this->client;
     }
-    
-    function getStaffId(){
-        return $this->staff_id;
+
+    function getStaffId() {
+        return $this->ht['staff_id'];
     }
 
-    function getStaff(){
+    function getStaff() {
 
         if(!$this->staff && $this->getStaffId())
             $this->staff= Staff::lookup($this->getStaffId());
@@ -407,11 +366,11 @@ class Ticket {
         return $this->staff;
     }
 
-    function getTeamId(){
-        return $this->team_id;
+    function getTeamId() {
+        return $this->ht['team_id'];
     }
 
-    function getTeam(){
+    function getTeam() {
 
         if(!$this->team && $this->getTeamId())
             $this->team = Team::lookup($this->getTeamId());
@@ -431,11 +390,11 @@ class Ticket {
     }
 
     function getAssignees() {
-     
+
         $assignees=array();
         if($staff=$this->getStaff())
             $assignees[] = $staff->getName();
-                       
+
         if($team=$this->getTeam())
             $assignees[] = $team->getName();
 
@@ -448,10 +407,10 @@ class Ticket {
     }
 
     function getTopicId() {
-        return $this->topic_id;
+        return $this->ht['topic_id'];
     }
 
-    function getTopic() { 
+    function getTopic() {
 
         if(!$this->topic && $this->getTopicId())
             $this->topic = Topic::lookup($this->getTopicId());
@@ -459,9 +418,9 @@ class Ticket {
         return $this->topic;
     }
 
- 
+
     function getSLAId() {
-        return $this->sla_id;
+        return $this->ht['sla_id'];
     }
 
     function getSLA() {
@@ -483,7 +442,7 @@ class Ticket {
 
         if(!($res=db_query($sql)) || !db_num_rows($res))
             return null;
-            
+
         list($id)=db_fetch_row($res);
 
         return Staff::lookup($id);
@@ -491,19 +450,7 @@ class Ticket {
     }
 
     function getLastMessageDate() {
-
-        if($this->lastmsgdate)
-            return $this->lastmsgdate;
-
-        //for old versions...XXX: still needed????
-        $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);
-
-        return $this->lastmsgdate;
+        return $this->ht['lastmessage'];
     }
 
     function getLastMsgDate() {
@@ -511,35 +458,28 @@ class Ticket {
     }
 
     function getLastResponseDate() {
-               
-        if($this->lastrespdate)
-            return $this->lastrespdate;
-
-        $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);
-
-        return $this->lastrespdate;
+        return $this->ht['lastresponse'];
     }
 
     function getLastRespDate() {
         return $this->getLastResponseDate();
     }
 
-        
+
     function getLastMsgId() {
         return $this->lastMsgId;
     }
 
-    function getRelatedTicketsCount(){
+    function getLastMessage() {
+        return Message::lookup($this->getLastMsgId(), $this->getId());
+    }
+
+    function getThread() {
 
-        $sql='SELECT count(*)  FROM '.TICKET_TABLE
-            .' WHERE email='.db_input($this->getEmail());
+        if(!$this->thread)
+            $this->thread = Thread::lookup($this);
 
-        return db_result(db_query($sql));
+        return $this->thread;
     }
 
     function getThreadCount() {
@@ -547,121 +487,42 @@ class Ticket {
     }
 
     function getNumMessages() {
-        return $this->ht['messages'];
+        return $this->getThread()->getNumMessages();
     }
 
     function getNumResponses() {
-        return $this->ht['responses'];
+        return $this->getThread()->getNumResponses();
     }
 
     function getNumNotes() {
-        return $this->ht['notes'];
+        return $this->getThread()->getNumNotes();
     }
 
     function getMessages() {
-        return $this->getThreadByType('M');
+        return $this->getThreadEntries('M');
     }
 
-    function getResponses($msgId=0) {
-        return $this->getThreadByType('R', $msgId);
+    function getResponses() {
+        return $this->getThreadEntries('R');
     }
 
     function getNotes() {
-        return $this->getThreadByType('N');
+        return $this->getThreadEntries('N');
     }
 
     function getClientThread() {
-        return $this->getThreadWithoutNotes();
-    }
-
-    function getThreadWithNotes() {
-        return $this->getThread(true);
-    }
-    
-    function getThreadWithoutNotes() {
-        return $this->getThread(false);
+        return $this->getThreadEntries(array('M', 'R'));
     }
 
-    function getThread($includeNotes=false, $order='') {
-
-        $treadtypes=array('M', 'R'); // messages and responses.
-        if($includeNotes) //Include notes??
-            $treadtypes[] = 'N';
-
-        return $this->getThreadByType($treadtypes, $order);
+    function getThreadEntry($id) {
+        return $this->getThread()->getEntry($id);
     }
-        
-    function getThreadByType($type, $order='ASC') {
-
-        if(!$order || !in_array($order, array('DESC','ASC')))
-            $order='ASC';
-
-        $sql='SELECT thread.* '
-            .' ,count(DISTINCT attach.attach_id) as attachments '
-            .' FROM '.TICKET_THREAD_TABLE.' thread '
-            .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach 
-                ON (thread.ticket_id=attach.ticket_id 
-                        AND thread.id=attach.ref_id 
-                        AND thread.thread_type=attach.ref_type) '
-            .' WHERE  thread.ticket_id='.db_input($this->getId());
-
-        if($type && is_array($type))
-            $sql.=" AND thread.thread_type IN('".implode("','", $type)."')";
-        elseif($type)
-            $sql.=' AND thread.thread_type='.db_input($type);
-
-        $sql.=' GROUP BY thread.id '
-             .' ORDER BY thread.created '.$order;
-
-        $thread=array();
-        if(($res=db_query($sql)) && db_num_rows($res))
-            while($rec=db_fetch_array($res))
-                $thread[] = $rec;
-
-        return $thread;
-    }
-
-    function getAttachments($refId=0, $type=null) {
 
-        if($refId && !$type)
-            return NULL;
-
-        //XXX: inner join the file table instead?
-        $sql='SELECT a.attach_id, f.id as file_id, f.size, f.hash as file_hash, f.name '
-            .' FROM '.FILE_TABLE.' f '
-            .' INNER JOIN '.TICKET_ATTACHMENT_TABLE.' a ON(f.id=a.file_id) '
-            .' WHERE a.ticket_id='.db_input($this->getId());
-       
-        if($refId) 
-            $sql.=' AND a.ref_id='.db_input($refId);
-
-        if($type)
-            $sql.=' AND a.ref_type='.db_input($type);
-
-        $attachments = array();
-        if(($res=db_query($sql)) && db_num_rows($res)) {
-            while($rec=db_fetch_array($res))
-                $attachments[] = $rec;
-        }
-
-        return $attachments;
+    function getThreadEntries($type, $order='') {
+        return $this->getThread()->getEntries($type, $order);
     }
 
-    function getAttachmentsLinks($refId, $type, $separator=' ',$target='') {
-
-        $str='';
-        foreach($this->getAttachments($refId, $type) as $attachment ) {
-            /* The has here can be changed  but must match validation in attachment.php */
-            $hash=md5($attachment['file_id'].session_id().$attachment['file_hash']); 
-            if($attachment['size'])
-                $size=sprintf('<em>(%s)</em>', Format::file_size($attachment['size']));
-                
-            $str.=sprintf('<a class="Icon file" href="attachment.php?id=%d&h=%s" target="%s">%s</a>%s&nbsp;%s',
-                    $attachment['attach_id'], $hash, $target, Format::htmlchars($attachment['name']), $size, $separator);
-        }
 
-        return $str;
-    }
 
     /* -------------------- Setters --------------------- */
     function setLastMsgId($msgid) {
@@ -671,10 +532,10 @@ class Ticket {
     function setPriority($priorityId) {
 
         //XXX: what happens to SLA priority???
-        
-        if(!$priorityId || $priorityId==$this->getPriorityId()) 
+
+        if(!$priorityId || $priorityId==$this->getPriorityId())
             return ($priorityId);
-        
+
         $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW() '
             .', priority_id='.db_input($priorityId)
             .' WHERE ticket_id='.db_input($this->getId());
@@ -683,31 +544,33 @@ class Ticket {
     }
 
     //DeptId can NOT be 0. No orphans please!
-    function setDeptId($deptId){
-       
+    function setDeptId($deptId) {
+
         //Make sure it's a valid department//
         if(!($dept=Dept::lookup($deptId)) || $dept->getId()==$this->getDeptId())
             return false;
 
-      
+
         $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW(), dept_id='.db_input($deptId)
             .' WHERE ticket_id='.db_input($this->getId());
 
         return (db_query($sql) && db_affected_rows());
     }
- 
+
     //Set staff ID...assign/unassign/release (id can be 0)
     function setStaffId($staffId) {
 
         if(!is_numeric($staffId)) return false;
-       
+
         $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW(), staff_id='.db_input($staffId)
             .' WHERE ticket_id='.db_input($this->getId());
 
         if (!db_query($sql)  || !db_affected_rows())
             return false;
-        
-        $this->staff_id = $staffId;
+
+        $this->staff = null;
+        $this->ht['staff_id'] = $staffId;
+
         return true;
     }
 
@@ -751,9 +614,9 @@ class Ticket {
 
     //Set team ID...assign/unassign/release (id can be 0)
     function setTeamId($teamId) {
-        
+
         if(!is_numeric($teamId)) return false;
-      
+
         $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW(), team_id='.db_input($teamId)
             .' WHERE ticket_id='.db_input($this->getId());
 
@@ -763,7 +626,7 @@ class Ticket {
     //Status helper.
     function setStatus($status) {
 
-        if(strcasecmp($this->getStatus(),$status)==0)
+        if(strcasecmp($this->getStatus(), $status)==0)
             return true; //No changes needed.
 
         switch(strtolower($status)) {
@@ -818,11 +681,11 @@ class Ticket {
     }
 
     //Close the ticket
-    function close(){
+    function close() {
         global $thisstaff;
-        
+
         $sql='UPDATE '.TICKET_TABLE.' SET closed=NOW(),isoverdue=0, duedate=NULL, updated=NOW(), status='.db_input('closed');
-        if($thisstaff) //Give the closing  staff credit. 
+        if($thisstaff) //Give the closing  staff credit.
             $sql.=', staff_id='.db_input($thisstaff->getId());
 
         $sql.=' WHERE ticket_id='.db_input($this->getId());
@@ -837,14 +700,14 @@ class Ticket {
     }
 
     //set status to open on a closed ticket.
-    function reopen($isanswered=0){
+    function reopen($isanswered=0) {
 
         $sql='UPDATE '.TICKET_TABLE.' SET updated=NOW(), reopened=NOW() '
             .' ,status='.db_input('open')
             .' ,isanswered='.db_input($isanswered)
             .' WHERE ticket_id='.db_input($this->getId());
 
-        //TODO: log reopen event here 
+        //TODO: log reopen event here
 
         $this->logEvent('reopened', 'closed');
         return (db_query($sql) && db_affected_rows());
@@ -854,46 +717,46 @@ class Ticket {
         global $cfg;
 
         //Log stuff here...
-        
+
         if(!$autorespond && !$alertstaff) return true; //No alerts to send.
 
         /* ------ SEND OUT NEW TICKET AUTORESP && ALERTS ----------*/
-        
+
         $this->reload(); //get the new goodies.
         $dept= $this->getDept();
 
         if(!$dept || !($tpl = $dept->getTemplate()))
             $tpl= $cfg->getDefaultTemplate();
-        
+
         if(!$tpl) return false;  //bail out...missing stuff.
 
         if(!$dept || !($email=$dept->getAutoRespEmail()))
             $email =$cfg->getDefaultEmail();
 
         //Send auto response - if enabled.
-        if($autorespond && $email && $cfg->autoRespONNewTicket() 
-                && $dept->autoRespONNewTicket() 
+        if($autorespond && $email && $cfg->autoRespONNewTicket()
+                && $dept->autoRespONNewTicket()
                 &&  ($msg=$tpl->getAutoRespMsgTemplate())) {
-            
-            $msg = $this->replaceVars($msg, 
+
+            $msg = $this->replaceVars($msg,
                     array('message' => $message,
                           'signature' => ($dept && $dept->isPublic())?$dept->getSignature():'')
                     );
 
             if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
                 $msg['body'] ="\n$tag\n\n".$msg['body'];
-            
+
             $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body']);
         }
-        
+
         if(!($email=$cfg->getAlertEmail()))
             $email =$cfg->getDefaultEmail();
-          
+
         //Send alert to out sleepy & idle staff.
         if($alertstaff && $email
-                && $cfg->alertONNewTicket() 
+                && $cfg->alertONNewTicket()
                 && ($msg=$tpl->getNewTicketAlertMsgTemplate())) {
-                    
+
             $msg = $this->replaceVars($msg, array('message' => $message));
 
             $recipients=$sentlist=array();
@@ -903,26 +766,26 @@ class Ticket {
                 $email->sendAlert($cfg->getAdminEmail(), $msg['subj'], $alert);
                 $sentlist[]=$cfg->getAdminEmail();
             }
-              
+
             //Only alerts dept members if the ticket is NOT assigned.
             if($cfg->alertDeptMembersONNewTicket() && !$this->isAssigned()) {
                 if(($members=$dept->getMembers()))
                     $recipients=array_merge($recipients, $members);
             }
-            
+
             if($cfg->alertDeptManagerONNewTicket() && $dept && ($manager=$dept->getManager()))
                 $recipients[]= $manager;
-               
-            foreach( $recipients as $k=>$staff){
-                if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue;
+
+            foreach( $recipients as $k=>$staff) {
+                if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
                 $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']);
                 $email->sendAlert($staff->getEmail(), $msg['subj'], $alert);
                 $sentlist[] = $staff->getEmail();
             }
-           
-           
+
+
         }
-        
+
         return true;
     }
 
@@ -937,43 +800,43 @@ class Ticket {
 
         //Send notice to user.
         $dept = $this->getDept();
-                    
+
         if(!$dept || !($tpl=$dept->getTemplate()))
             $tpl=$cfg->getDefaultTemplate();
-            
+
         if(!$dept || !($email=$dept->getAutoRespEmail()))
             $email=$cfg->getDefaultEmail();
 
         if($tpl && ($msg=$tpl->getOverlimitMsgTemplate()) && $email) {
-            
-            $msg = $this->replaceVars($msg, 
+
+            $msg = $this->replaceVars($msg,
                         array('signature' => ($dept && $dept->isPublic())?$dept->getSignature():''));
-            
+
             $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body']);
         }
 
         $client= $this->getClient();
-        
+
         //Alert admin...this might be spammy (no option to disable)...but it is helpful..I think.
         $alert='Max. open tickets reached for '.$this->getEmail()."\n"
               .'Open ticket: '.$client->getNumOpenTickets()."\n"
               .'Max Allowed: '.$cfg->getMaxOpenTickets()."\n\nNotice sent to the user.";
-            
+
         $ost->alertAdmin('Overlimit Notice', $alert);
-       
+
         return true;
     }
 
-    function onResponse(){
+    function onResponse() {
         db_query('UPDATE '.TICKET_TABLE.' SET isanswered=1,lastresponse=NOW(), updated=NOW() WHERE ticket_id='.db_input($this->getId()));
     }
 
-    function onMessage($autorespond=true, $alert=true){
+    function onMessage($autorespond=true, $alert=true) {
         global $cfg;
 
         db_query('UPDATE '.TICKET_TABLE.' SET isanswered=0,lastmessage=NOW() WHERE ticket_id='.db_input($this->getId()));
-            
-        //auto-assign to closing staff or last respondent 
+
+        //auto-assign to closing staff or last respondent
         if(!($staff=$this->getStaff()) || !$staff->isAvailable()) {
             if($cfg->autoAssignReopenedTickets() && ($lastrep=$this->getLastRespondent()) && $lastrep->isAvailable()) {
                 $this->setStaffId($lastrep->getId()); //direct assignment;
@@ -1001,7 +864,7 @@ class Ticket {
 
         if(!$dept || !($email = $dept->getAutoRespEmail()))
             $email = $cfg->getDefaultEmail();
-      
+
         //If enabled...send confirmation to user. ( New Message AutoResponse)
         if($email && $tpl && ($msg=$tpl->getNewMessageAutorepMsgTemplate())) {
 
@@ -1011,7 +874,7 @@ class Ticket {
             //Reply separator tag.
             if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
                 $msg['body'] ="\n$tag\n\n".$msg['body'];
-        
+
             $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body']);
         }
     }
@@ -1028,7 +891,7 @@ class Ticket {
 
         $comments = $comments?$comments:'Ticket assignment';
         $assigner = $thisstaff?$thisstaff:'SYSTEM (Auto Assignment)';
-        
+
         //Log an internal note - no alerts on the internal note.
         $this->logNote('Ticket Assigned to '.$assignee->getName(), $comments, $assigner, false);
 
@@ -1060,7 +923,7 @@ class Ticket {
         //Get the message template
         if($email && $recipients && $tpl && ($msg=$tpl->getAssignedAlertMsgTemplate())) {
 
-            $msg = $this->replaceVars($msg, 
+            $msg = $this->replaceVars($msg,
                         array('comments' => $comments,
                               'assignee' => $assignee,
                               'assigner' => $assigner
@@ -1069,7 +932,7 @@ class Ticket {
             //Send the alerts.
             $sentlist=array();
             foreach( $recipients as $k=>$staff) {
-                if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue;
+                if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
                 $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']);
                 $email->sendAlert($staff->getEmail(), $msg['subj'], $alert);
                 $sentlist[] = $staff->getEmail();
@@ -1100,7 +963,7 @@ class Ticket {
 
         //Get the message template
         if($tpl && ($msg=$tpl->getOverdueAlertMsgTemplate()) && $email) {
-            
+
             $msg = $this->replaceVars($msg, array('comments' => $comments));
 
             //recipients
@@ -1121,8 +984,8 @@ class Ticket {
                 $recipients[]= $manager;
 
             $sentlist=array();
-            foreach( $recipients as $k=>$staff){
-                if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue;
+            foreach( $recipients as $k=>$staff) {
+                if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
                 $alert = str_replace("%{recipient}", $staff->getFirstName(), $msg['body']);
                 $email->sendAlert($staff->getEmail(), $msg['subj'], $alert);
                 $sentlist[] = $staff->getEmail();
@@ -1131,9 +994,9 @@ class Ticket {
         }
 
         return true;
-    
+
     }
-   
+
     //ticket obj as variable = ticket number.
     function asVar() {
        return $this->getNumber();
@@ -1161,17 +1024,17 @@ class Ticket {
                 break;
             case 'create_date':
                 return Format::date(
-                        $cfg->getDateTimeFormat(), 
+                        $cfg->getDateTimeFormat(),
                         Misc::db2gmtime($this->getCreateDate()),
                         $cfg->getTZOffset(),
                         $cfg->observeDaylightSaving());
                 break;
              case 'due_date':
                 $duedate ='';
-                if($this->getDueDate())
+                if($this->getEstDueDate())
                     $duedate = Format::date(
                             $cfg->getDateTimeFormat(),
-                            Misc::db2gmtime($this->getDueDate()),
+                            Misc::db2gmtime($this->getEstDueDate()),
                             $cfg->getTZOffset(),
                             $cfg->observeDaylightSaving());
 
@@ -1211,9 +1074,9 @@ class Ticket {
     }
 
     function markOverdue($whine=true) {
-        
+
         global $cfg;
-        
+
         if($this->isOverdue())
             return true;
 
@@ -1231,24 +1094,31 @@ class Ticket {
 
     function clearOverdue() {
 
-        if(!$this->isOverdue()) 
+        if(!$this->isOverdue())
             return true;
 
+        //NOTE: Previously logged overdue event is NOT annuled.
+
         $sql='UPDATE '.TICKET_TABLE.' SET isoverdue=0, updated=NOW() ';
+
         //clear due date if it's in the past
         if($this->getDueDate() && strtotime($this->getDueDate())<=time())
             $sql.=', duedate=NULL';
 
+        //Clear SLA if est. due date is in the past
+        if($this->getSLADueDate() && strtotime($this->getSLADueDate())<=time())
+            $sql.=', sla_id=0 ';
+
         $sql.=' WHERE ticket_id='.db_input($this->getId());
 
         return (db_query($sql) && db_affected_rows());
     }
 
-    //Dept Tranfer...with alert.. done by staff 
+    //Dept Tranfer...with alert.. done by staff
     function transfer($deptId, $comments, $alert = true) {
-        
+
         global $cfg, $thisstaff;
-      
+
         if(!$thisstaff || !$thisstaff->canTransferTickets())
             return false;
 
@@ -1256,8 +1126,8 @@ class Ticket {
 
         if(!$deptId || !$this->setDeptId($deptId))
             return false;
-       
-        // Reopen ticket if closed 
+
+        // Reopen ticket if closed
         if($this->isClosed()) $this->reopen();
 
         $this->reload();
@@ -1265,14 +1135,14 @@ class Ticket {
         // Set SLA of the new department
         if(!$this->getSLAId())
             $this->selectSLAId();
-                  
+
         /*** log the transfer comments as internal note - with alerts disabled - ***/
         $title='Ticket transfered from '.$currentDept.' to '.$this->getDeptName();
-        $comments=$comments?$comments:$title; 
+        $comments=$comments?$comments:$title;
         $this->logNote($title, $comments, $thisstaff, false);
 
         $this->logEvent('transferred');
-        
+
         //Send out alerts if enabled AND requested
         if(!$alert || !$cfg->alertONTransfer() || !($dept=$this->getDept())) return true; //no alerts!!
 
@@ -1280,16 +1150,16 @@ class Ticket {
          //Get template.
          if(!($tpl = $dept->getTemplate()))
              $tpl= $cfg->getDefaultTemplate();
-        
+
          //Email to use!
          if(!($email=$cfg->getAlertEmail()))
              $email =$cfg->getDefaultEmail();
-                
-         //Get the message template 
+
+         //Get the message template
          if($tpl && ($msg=$tpl->getTransferAlertMsgTemplate()) && $email) {
-            
+
              $msg = $this->replaceVars($msg, array('comments' => $comments, 'staff' => $thisstaff));
-            //recipients            
+            //recipients
             $recipients=array();
             //Assigned staff or team... if any
             if($this->isAssigned() && $cfg->alertAssignedONTransfer()) {
@@ -1306,11 +1176,11 @@ class Ticket {
             //Always alert dept manager??
             if($cfg->alertDeptManagerONTransfer() && $dept && ($manager=$dept->getManager()))
                 $recipients[]= $manager;
-             
+
             $sentlist=array();
-            foreach( $recipients as $k=>$staff){
-                if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue;
-                $alert = str_replace('%{recipient}',$staff->getFirstName(), $msg['body']);
+            foreach( $recipients as $k=>$staff) {
+                if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
+                $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']);
                 $email->sendAlert($staff->getEmail(), $msg['subj'], $alert);
                 $sentlist[] = $staff->getEmail();
             }
@@ -1323,7 +1193,7 @@ class Ticket {
 
         if(!is_object($staff) && !($staff=Staff::lookup($staff)))
             return false;
-        
+
         if(!$this->setStaffId($staff->getId()))
             return false;
 
@@ -1357,7 +1227,7 @@ class Ticket {
         global $thisstaff;
 
         $rv=0;
-        $id=preg_replace("/[^0-9]/", "",$assignId);
+        $id=preg_replace("/[^0-9]/", "", $assignId);
         if($assignId[0]=='t') {
             $rv=$this->assignToTeam($id, $note, $alert);
         } elseif($assignId[0]=='s' || is_numeric($assignId)) {
@@ -1368,7 +1238,7 @@ class Ticket {
 
         return $rv;
     }
-    
+
     //unassign primary assignee
     function unassign() {
 
@@ -1391,52 +1261,40 @@ class Ticket {
 
         return true;
     }
-    
+
     function release() {
         return $this->unassign();
     }
 
     //Insert message from client
-    function postMessage($vars, $source='', $alerts=true) {
+    function postMessage($vars, $origin='', $alerts=true) {
         global $cfg;
-       
-        if(!$vars || !$vars['message'])
-            return 0;
-        
+
         //Strip quoted reply...on emailed replies
-        if(!strcasecmp($source, 'Email') 
-                && $cfg->stripQuotedReply() 
+        if(!strcasecmp($origin, 'Email')
+                && $cfg->stripQuotedReply()
                 && ($tag=$cfg->getReplySeparator()) && strpos($vars['message'], $tag))
-            list($vars['message']) = split($tag, $vars['message']);
-
-        # XXX: Refuse auto-response messages? (via email) XXX: No - but kill our auto-responder.
-
-
-        $sql='INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW()'
-            .' ,thread_type="M" '
-            .' ,ticket_id='.db_input($this->getId())
-            # XXX: Put Subject header into the 'title' field
-            .' ,body='.db_input(Format::striptags($vars['message'])) //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']);
-    
-        if(!db_query($sql) || !($msgid=db_insert_id()))
-            return 0; //bail out....
-
-        $this->setLastMsgId($msgid);
-        
-        if (isset($vars['mid'])) {
-            $sql='INSERT INTO '.TICKET_EMAIL_INFO_TABLE
-                .' SET message_id='.db_input($msgid)
-                .', email_mid='.db_input($vars['mid'])
-                .', headers='.db_input($vars['header']);
-            db_query($sql);
-        }
+            if(list($msg) = split($tag, $vars['message']))
+                $vars['message'] = $msg;
 
-        if(!$alerts) return $msgid; //Our work is done...
+        if($vars['ip'])
+            $vars['ip_address'] = $vars['ip'];
+        elseif(!$vars['ip_address'] && $_SERVER['REMOTE_ADDR'])
+            $vars['ip_address'] = $_SERVER['REMOTE_ADDR'];
+
+        $errors = array();
+        if(!($message = $this->getThread()->addMessage($vars, $errors)))
+            return null;
+
+        $this->setLastMsgId($message->getId());
+
+        if (isset($vars['mid']))
+            $message->saveEmailInfo($vars);
+
+        if(!$alerts) return $message; //Our work is done...
 
         $autorespond = true;
-        if ($autorespond && $vars['header'] && TicketFilter::isAutoResponse($vars['header']))
+        if ($autorespond && $message->isAutoResponse())
             $autorespond=false;
 
         $this->onMessage($autorespond); //must be called b4 sending alerts to staff.
@@ -1452,33 +1310,33 @@ class Ticket {
         //If enabled...send alert to staff (New Message Alert)
         if($cfg->alertONNewMessage() && $tpl && $email && ($msg=$tpl->getNewMessageAlertMsgTemplate())) {
 
-            $msg = $this->replaceVars($msg, array('message' => $vars['message']));
+            $msg = $this->replaceVars($msg, array('message' => $message));
 
             //Build list of recipients and fire the alerts.
             $recipients=array();
             //Last respondent.
             if($cfg->alertLastRespondentONNewMessage() || $cfg->alertAssignedONNewMessage())
                 $recipients[]=$this->getLastRespondent();
-                
+
             //Assigned staff if any...could be the last respondent
-            
+
             if($this->isAssigned() && ($staff=$this->getStaff()))
                 $recipients[]=$staff;
-                
+
             //Dept manager
             if($cfg->alertDeptManagerONNewMessage() && $dept && ($manager=$dept->getManager()))
                 $recipients[]=$manager;
-                
+
             $sentlist=array(); //I know it sucks...but..it works.
-            foreach( $recipients as $k=>$staff){
+            foreach( $recipients as $k=>$staff) {
                 if(!$staff || !$staff->getEmail() || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
                 $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']);
                 $email->sendAlert($staff->getEmail(), $msg['subj'], $alert);
                 $sentlist[] = $staff->getEmail();
             }
         }
-        
-        return $msgid;
+
+        return $message;
     }
 
     function postCannedReply($canned, $msgId, $alert=true) {
@@ -1492,16 +1350,17 @@ class Ticket {
             $files[] = $file['id'];
 
         $info = array('msgId' => $msgId,
+                      'poster' => 'SYSTEM (Canned Reply)',
                       'response' => $this->replaceVars($canned->getResponse()),
                       'cannedattachments' => $files);
 
         $errors = array();
-        if(!($respId=$this->postReply($info, $errors, false)))
-            return false;
+        if(!($response=$this->postReply($info, $errors, false)))
+            return null;
 
         $this->markUnAnswered();
 
-        if(!$alert) return $respId;
+        if(!$alert) return $response;
 
         $dept = $this->getDept();
 
@@ -1518,65 +1377,41 @@ class Ticket {
             else
                 $signature='';
 
-            $msg = $this->replaceVars($msg, array('response' => $info['response'], 'signature' => $signature));
+            $msg = $this->replaceVars($msg, array('response' => $response, 'signature' => $signature));
 
             if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
                 $msg['body'] ="\n$tag\n\n".$msg['body'];
 
-            $attachments =($cfg->emailAttachments() && $files)?$this->getAttachments($respId, 'R'):array();
+            $attachments =($cfg->emailAttachments() && $files)?$response->getAttachments():array();
             $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body'], $attachments);
         }
 
-        return $respId;
+        return $response;
     }
 
-    /* public */ 
+    /* public */
     function postReply($vars, &$errors, $alert = true) {
         global $thisstaff, $cfg;
 
-        if(!$vars['msgId'])
-            $errors['msgId'] ='Missing messageId - internal error';
-        if(!$vars['response'])
-            $errors['response'] = 'Response message required';
 
-        if($errors) return 0;
+        if(!$vars['poster'] && $thisstaff)
+            $vars['poster'] = $thisstaff->getName();
 
-        $poster = $thisstaff?$thisstaff->getName():'SYSTEM (Canned Reply)';
+        if(!$vars['staffId'] && $thisstaff)
+            $vars['staffId'] = $thisstaff->getId();
 
-        $sql='INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW() '
-            .' ,thread_type="R"'
-            .' ,ticket_id='.db_input($this->getId())
-            .' ,pid='.db_input($vars['msgId'])
-            .' ,body='.db_input(Format::striptags($vars['response']))
-            .' ,staff_id='.db_input($thisstaff?$thisstaff->getId():0)
-            .' ,poster='.db_input($poster)
-            .' ,ip_address='.db_input($thisstaff?$thisstaff->getIP():'');
-
-        if(!db_query($sql) || !($respId=db_insert_id()))
-            return false;
+        if(!($response = $this->getThread()->addResponse($vars, $errors)))
+            return null;
 
         //Set status - if checked.
         if(isset($vars['reply_ticket_status']) && $vars['reply_ticket_status'])
             $this->setStatus($vars['reply_ticket_status']);
 
-        /* We can NOT recover from attachment related failures at this point */
-        $attachments = array();
-        //Web based upload.. note that we're not "validating" attachments on response.
-        if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments'])))
-            $attachments=$this->uploadAttachments($files, $respId, 'R');
-
-        //Canned attachments...
-        if($vars['cannedattachments'] && is_array($vars['cannedattachments'])) {
-            foreach($vars['cannedattachments'] as $fileId)
-                if($fileId && $this->saveAttachment($fileId, $respId, 'R'))
-                    $attachments[] = $fileId;
-        }
-
         $this->onResponse(); //do house cleaning..
         $this->reload();
 
         /* email the user??  - if disabled - the bail out */
-        if(!$alert) return $respId;
+        if(!$alert) return $response;
 
         $dept = $this->getDept();
 
@@ -1594,24 +1429,24 @@ class Ticket {
                 $signature=$dept->getSignature();
             else
                 $signature='';
-            
-            $msg = $this->replaceVars($msg, 
-                    array('response' => $vars['response'], 'signature' => $signature, 'staff' => $thisstaff));
+
+            $msg = $this->replaceVars($msg,
+                    array('response' => $response, 'signature' => $signature, 'staff' => $thisstaff));
 
             if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
                 $msg['body'] ="\n$tag\n\n".$msg['body'];
 
             //Set attachments if emailing.
-            $attachments =($cfg->emailAttachments() && $attachments)?$this->getAttachments($respId,'R'):array();
+            $attachments = $cfg->emailAttachments()?$response->getAttachments():array();
             //TODO: setup  5 param (options... e.g mid trackable on replies)
             $email->send($this->getEmail(), $msg['subj'], $msg['body'], $attachments);
         }
 
-        return $respId;
+        return $response;
     }
 
     //Activity log - saved as internal notes WHEN enabled!!
-    function logActivity($title,$note){
+    function logActivity($title, $note) {
         global $cfg;
 
         if(!$cfg || !$cfg->logTicketActivity())
@@ -1659,42 +1494,23 @@ class Ticket {
                 $alert);
     }
 
-    function postNote($vars, &$errors, $poster, $alert=true) {        
+    function postNote($vars, &$errors, $poster, $alert=true) {
         global $cfg, $thisstaff;
 
-        if(!$vars || !is_array($vars))
-            $errors['err'] = 'Missing or invalid data';
-        elseif(!$vars['note'])
-            $errors['note'] = 'Note required';
-
-        if($errors) return false;
-		
-        $staffId = 0;
+        //Who is posting the note - staff or system?
+        $vars['staffId'] = 0;
+        $vars['poster'] = 'SYSTEM';
         if($poster && is_object($poster)) {
-            $staffId = $poster->getId();
-            $poster = $poster->getName();
-        } elseif(!$poster) {
-            $poster ='SYSTEM';
+            $vars['staffId'] = $poster->getId();
+            $vars['poster'] = $poster->getName();
+        }elseif($poster) { //string
+            $vars['poster'] = $poster;
         }
 
-        //TODO: move to class.thread.php
+        if(!($note=$this->getThread()->addNote($vars, $errors)))
+            return null;
 
-        $sql= 'INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW() '.
-                ',thread_type="N"'.
-                ',ticket_id='.db_input($this->getId()).
-                ',title='.db_input(Format::striptags($vars['title']?$vars['title']:'[No Title]')).
-                ',body='.db_input(Format::striptags($vars['note'])).
-                ',staff_id='.db_input($staffId).
-                ',poster='.db_input($poster);
-        //echo $sql;
-        if(!db_query($sql) || !($id=db_insert_id()))
-            return false;
-                
-        //Upload attachments IF ANY - TODO: validate attachment types??
-        if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments'])))
-            $attachments = $this->uploadAttachments($files, $id, 'N');
-            
-        //Set state: Error on state change not critical! 
+        //Set state: Error on state change not critical!
         if(isset($vars['state']) && $vars['state']) {
             if($this->setState($vars['state']))
                 $this->reload();
@@ -1702,11 +1518,8 @@ class Ticket {
 
         // If alerts are not enabled then return a success.
         if(!$alert || !$cfg->alertONNewNote() || !($dept=$this->getDept()))
-            return $id;
+            return $note;
 
-        //Note obj.
-        $note = Note::lookup($id, $this->getId());
-        
         if(!($tpl = $dept->getTemplate()))
             $tpl= $cfg->getDefaultTemplate();
 
@@ -1715,36 +1528,36 @@ class Ticket {
 
 
         if($tpl && ($msg=$tpl->getNoteAlertMsgTemplate()) && $email) {
-                   
+
             $msg = $this->replaceVars($msg, array('note' => $note));
 
-            // Alert recipients    
+            // Alert recipients
             $recipients=array();
-            
+
             //Last respondent.
             if($cfg->alertLastRespondentONNewNote())
                 $recipients[]=$this->getLastRespondent();
-            
+
             //Assigned staff if any...could be the last respondent
             if($cfg->alertAssignedONNewNote() && $this->isAssigned() && $this->getStaffId())
                 $recipients[]=$this->getStaff();
-                
+
             //Dept manager
             if($cfg->alertDeptManagerONNewNote() && $dept && $dept->getManagerId())
                 $recipients[]=$dept->getManager();
 
-            $attachments =($attachments)?$this->getAttachments($id, 'N'):array();
+            $attachments = $note->getAttachments();
             $sentlist=array();
             foreach( $recipients as $k=>$staff) {
                 if(!$staff || !is_object($staff) || !$staff->getEmail() || !$staff->isAvailable()) continue;
-                if(in_array($staff->getEmail(),$sentlist) || ($staffId && $staffId==$staff->getId())) continue; 
-                $alert = str_replace('%{recipient}',$staff->getFirstName(), $msg['body']);
+                if(in_array($staff->getEmail(), $sentlist) || ($staffId && $staffId==$staff->getId())) continue;
+                $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']);
                 $email->sendAlert($staff->getEmail(), $msg['subj'], $alert, $attachments);
                 $sentlist[] = $staff->getEmail();
             }
         }
-        
-        return $id;
+
+        return $note;
     }
 
     //Print ticket... export the ticket thread as PDF.
@@ -1757,123 +1570,25 @@ class Ticket {
         exit;
     }
 
-    //online based attached files.
-    function uploadAttachments($files, $refid, $type, $checkFileTypes=false) {
-        global $ost;
+    function delete() {
 
-        $uploaded=array();
-        $ost->validateFileUploads($files, $checkFileTypes); //Validator sets errors - if any
-        foreach($files as $file) {
-            if(!$file['error'] 
-                    && ($id=AttachmentFile::upload($file)) 
-                    && $this->saveAttachment($id, $refid, $type))
-                $uploaded[]=$id;
-            elseif($file['error']!=UPLOAD_ERR_NO_FILE) { 
-                
-                // log file upload errors as interal notes + syslog debug.
-                if($file['error'] && gettype($file['error'])=='string')
-                    $error = $file['error'];
-                else
-                    $error ='Error #'.$file['error'];
-
-                $this->logNote('File Upload Error', $error, 'SYSTEM', false);
-               
-                $ost->logDebug('File Upload Error (Ticket #'.$this->getExtId().')', $error);
-            }
-            
-        }
-        
-        return $uploaded;
-    }
-
-    /* Wrapper or uploadAttachments 
-       - used on client interface 
-       - file type check is forced
-       - $_FILES  is passed.
-    */
-    function uploadFiles($files, $refid, $type) {
-        return $this->uploadAttachments(Format::files($files), $refid, $type, true);    
-    }
-
-    /* Emailed & API attachments handler */
-    function importAttachments($attachments, $refid, $type, $checkFileTypes=true) {
-        global $ost;
-
-        if(!$attachments || !is_array($attachments)) return null;
-
-        $files = array();        
-        foreach ($attachments as &$info) {
-            //Do error checking...
-            if ($checkFileTypes && !$ost->isFileTypeAllowed($info))
-                $info['error'] = 'Invalid file type (ext) for '.Format::htmlchars($info['name']);
-            elseif ($info['encoding'] && !strcasecmp($info['encoding'], 'base64')) {
-                if(!($info['data'] = base64_decode($info['data'], true)))
-                    $info['error'] = sprintf('%s: Poorly encoded base64 data', Format::htmlchars($info['name']));
-            }
-
-            if($info['error']) {
-                $this->logNote('File Import Error', $info['error'], 'SYSTEM', false);
-            } elseif (($id=$this->saveAttachment($info, $refid, $type))) {
-                $files[] = $id;
-            }
-        }
-
-        return $files;
-    }
-
-
-    /*
-       Save attachment to the DB. upload/import (above).
-       
-       @file is a mixed var - can be ID or file hash.
-     */
-    function saveAttachment($file, $refid, $type) {
-
-        if(!$refid || !$type || !($fileId=is_numeric($file)?$file:AttachmentFile::save($file)))
-            return 0;
-
-        $sql ='INSERT INTO '.TICKET_ATTACHMENT_TABLE.' SET created=NOW() '
-             .' ,ticket_id='.db_input($this->getId())
-             .' ,file_id='.db_input($fileId)
-             .' ,ref_id='.db_input($refid)
-             .' ,ref_type='.db_input($type);
-
-        return (db_query($sql) && ($id=db_insert_id()))?$id:0;
-    }
-    
-
-
-    function deleteAttachments(){
-        
-        $deleted=0;
-        // Clear reference table
-        $res=db_query('DELETE FROM '.TICKET_ATTACHMENT_TABLE.' WHERE ticket_id='.db_input($this->getId()));
-        if ($res && db_affected_rows())
-            $deleted = AttachmentFile::deleteOrphans();
-
-        return $deleted;
-    }
-
-
-    function delete(){
-        
-        $sql='DELETE FROM '.TICKET_TABLE.' WHERE ticket_id='.$this->getId().' LIMIT 1';
+        $sql = 'DELETE FROM '.TICKET_TABLE.' WHERE ticket_id='.$this->getId().' LIMIT 1';
         if(!db_query($sql) || !db_affected_rows())
             return false;
 
-        db_query('DELETE FROM '.TICKET_THREAD_TABLE.' WHERE ticket_id='.db_input($this->getId()));
-        $this->deleteAttachments();
-        
+        //delete just orphaned ticket thread & associated attachments.
+        $this->getThread()->delete();
+
         return true;
     }
 
     function update($vars, &$errors) {
 
         global $cfg, $thisstaff;
-        
+
         if(!$cfg || !$thisstaff || !$thisstaff->canEditTickets())
             return false;
-         
+
         $fields=array();
         $fields['name']     = array('type'=>'string',   'required'=>1, 'error'=>'Name required');
         $fields['email']    = array('type'=>'email',    'required'=>1, 'error'=>'Valid email required');
@@ -1889,7 +1604,7 @@ class Ticket {
         if(!Validator::process($fields, $vars, $errors) && !$errors['err'])
             $errors['err'] = 'Missing or invalid data - check the errors and try again';
 
-        if($vars['duedate']) {     
+        if($vars['duedate']) {
             if($this->isClosed())
                 $errors['duedate']='Duedate can NOT be set on a closed ticket';
             elseif(!$vars['time'] || strpos($vars['time'],':')===false)
@@ -1899,7 +1614,7 @@ class Ticket {
             elseif(strtotime($vars['duedate'].' '.$vars['time'])<=time())
                 $errors['duedate']='Due date must be in the future';
         }
-        
+
         //Make sure phone extension is valid
         if($vars['phone_ext'] ) {
             if(!is_numeric($vars['phone_ext']) && !$errors['phone'])
@@ -1920,11 +1635,11 @@ class Ticket {
             .' ,topic_id='.db_input($vars['topicId'])
             .' ,sla_id='.db_input($vars['slaId'])
             .' ,duedate='.($vars['duedate']?db_input(date('Y-m-d G:i',Misc::dbtime($vars['duedate'].' '.$vars['time']))):'NULL');
-             
+
         if($vars['duedate']) { //We are setting new duedate...
             $sql.=' ,isoverdue=0';
         }
-             
+
         $sql.=' WHERE ticket_id='.db_input($this->getId());
 
         if(!db_query($sql) || !db_affected_rows())
@@ -1935,20 +1650,20 @@ class Ticket {
 
         $this->logNote('Ticket Updated', $vars['note'], $thisstaff);
         $this->reload();
-        
+
         return true;
     }
 
-   
+
    /*============== Static functions. Use Ticket::function(params); ==================*/
     function getIdByExtId($extId, $email=null) {
-        
-        if(!$extId || !is_numeric($extId)) 
+
+        if(!$extId || !is_numeric($extId))
             return 0;
 
         $sql ='SELECT  ticket_id FROM '.TICKET_TABLE.' ticket '
              .' WHERE ticketID='.db_input($extId);
-        
+
         if($email)
             $sql.=' AND email='.db_input($email);
 
@@ -1959,9 +1674,14 @@ class Ticket {
     }
 
 
-   
+
     function lookup($id) { //Assuming local ID is the only lookup used!
-        return ($id && is_numeric($id) && ($ticket= new Ticket($id)) && $ticket->getId()==$id)?$ticket:null;    
+        return ($id
+                && is_numeric($id)
+                && ($ticket= new Ticket($id))
+                && $ticket->getId()==$id
+                && $ticket->getThread())
+            ?$ticket:null;
     }
 
     function lookupByExtId($id, $email=null) {
@@ -1980,7 +1700,7 @@ class Ticket {
         return $id;
     }
 
-    function getIdByMessageId($mid,$email) {
+    function getIdByMessageId($mid, $email) {
 
         if(!$mid || !$email)
             return 0;
@@ -1996,7 +1716,7 @@ class Ticket {
         return $id;
     }
 
-    function getOpenTicketsByEmail($email){
+    function getOpenTicketsByEmail($email) {
 
         $sql='SELECT count(*) as open FROM '.TICKET_TABLE.' WHERE status='.db_input('open').' AND email='.db_input($email);
         if(($res=db_query($sql)) && db_num_rows($res))
@@ -2005,28 +1725,39 @@ class Ticket {
         return $num;
     }
 
-    /* Quick staff's tickets stats */ 
+    /* Quick staff's tickets stats */
     function getStaffStats($staff) {
         global $cfg;
-        
+
         /* Unknown or invalid staff */
         if(!$staff || (!is_object($staff) && !($staff=Staff::lookup($staff))) || !$staff->isStaff() || $cfg->getDBVersion())
             return null;
 
-
         $sql='SELECT count(open.ticket_id) as open, count(answered.ticket_id) as answered '
             .' ,count(overdue.ticket_id) as overdue, count(assigned.ticket_id) as assigned, count(closed.ticket_id) as closed '
             .' FROM '.TICKET_TABLE.' ticket '
             .' LEFT JOIN '.TICKET_TABLE.' open
-                ON (open.ticket_id=ticket.ticket_id AND open.status=\'open\' AND open.isanswered=0) '
+                ON (open.ticket_id=ticket.ticket_id
+                        AND open.status=\'open\'
+                        AND open.isanswered=0
+                        '.((!($cfg->showAssignedTickets() || $staff->showAssignedTickets()))?
+                        ' AND open.staff_id=0 ':'').') '
             .' LEFT JOIN '.TICKET_TABLE.' answered
-                ON (answered.ticket_id=ticket.ticket_id AND answered.status=\'open\' AND answered.isanswered=1) '
+                ON (answered.ticket_id=ticket.ticket_id
+                        AND answered.status=\'open\'
+                        AND answered.isanswered=1) '
             .' LEFT JOIN '.TICKET_TABLE.' overdue
-                ON (overdue.ticket_id=ticket.ticket_id AND overdue.status=\'open\' AND overdue.isoverdue=1) '
+                ON (overdue.ticket_id=ticket.ticket_id
+                        AND overdue.status=\'open\'
+                        AND overdue.isoverdue=1) '
             .' LEFT JOIN '.TICKET_TABLE.' assigned
-                ON (assigned.ticket_id=ticket.ticket_id AND assigned.status=\'open\' AND assigned.staff_id='.db_input($staff->getId()).')'
+                ON (assigned.ticket_id=ticket.ticket_id
+                        AND assigned.status=\'open\'
+                        AND assigned.staff_id='.db_input($staff->getId()).')'
             .' LEFT JOIN '.TICKET_TABLE.' closed
-                ON (closed.ticket_id=ticket.ticket_id AND closed.status=\'closed\' AND closed.staff_id='.db_input($staff->getId()).')'
+                ON (closed.ticket_id=ticket.ticket_id
+                        AND closed.status=\'closed\'
+                        AND closed.staff_id='.db_input($staff->getId()).')'
             .' WHERE (ticket.staff_id='.db_input($staff->getId());
 
         if(($teams=$staff->getTeams()))
@@ -2040,13 +1771,12 @@ class Ticket {
         if(!$cfg || !($cfg->showAssignedTickets() || $staff->showAssignedTickets()))
             $sql.=' AND (ticket.staff_id=0 OR ticket.staff_id='.db_input($staff->getId()).') ';
 
-     
         return db_fetch_array(db_query($sql));
     }
 
 
-    /* Quick client's tickets stats 
-       @email - valid email. 
+    /* Quick client's tickets stats
+       @email - valid email.
      */
     function getClientStats($email) {
 
@@ -2068,7 +1798,7 @@ class Ticket {
      * The mother of all functions...You break it you fix it!
      *
      *  $autorespond and $alertstaff overwrites config settings...
-     */      
+     */
     function create($vars, &$errors, $origin, $autorespond=true, $alertstaff=true) {
         global $ost, $cfg, $thisclient, $_FILES;
 
@@ -2084,14 +1814,14 @@ class Ticket {
             }
 
             //Make sure the open ticket limit hasn't been reached. (LOOP CONTROL)
-            if($cfg->getMaxOpenTickets()>0 && strcasecmp($origin,'staff') 
+            if($cfg->getMaxOpenTickets()>0 && strcasecmp($origin,'staff')
                     && ($client=Client::lookupByEmail($vars['email']))
                     && ($openTickets=$client->getNumOpenTickets())
                     && ($openTickets>=$cfg->getMaxOpenTickets()) ) {
 
                 $errors['err']="You've reached the maximum open tickets allowed.";
-                $ost->logWarning('Ticket denied -'.$vars['email'], 
-                        sprintf('Max open tickets (%d) reached for %s ', 
+                $ost->logWarning('Ticket denied -'.$vars['email'],
+                        sprintf('Max open tickets (%d) reached for %s ',
                             $cfg->getMaxOpenTickets(), $vars['email']));
 
                 return 0;
@@ -2101,12 +1831,12 @@ class Ticket {
         //Init ticket filters...
         $ticket_filter = new TicketFilter($origin, $vars);
         // Make sure email contents should not be rejected
-        if($ticket_filter 
+        if($ticket_filter
                 && ($filter=$ticket_filter->shouldReject())) {
             $errors['err']='Ticket denied. Error #403';
             $errors['errno'] = 403;
-            $ost->logWarning('Ticket denied', 
-                    sprintf('Ticket rejected ( %s) by filter "%s"', 
+            $ost->logWarning('Ticket denied',
+                    sprintf('Ticket rejected ( %s) by filter "%s"',
                         $vars['email'], $filter->getName()));
 
             return 0;
@@ -2138,7 +1868,7 @@ class Ticket {
         }
         $fields['priorityId']   = array('type'=>'int',      'required'=>0, 'error'=>'Invalid Priority');
         $fields['phone']        = array('type'=>'phone',    'required'=>0, 'error'=>'Valid phone # required');
-        
+
         if(!Validator::process($fields, $vars, $errors) && !$errors['err'])
             $errors['err'] ='Missing or invalid data - check the errors and try again';
 
@@ -2206,7 +1936,7 @@ class Ticket {
         $deptId=$deptId?$deptId:$cfg->getDefaultDeptId();
         $topicId=$vars['topicId']?$vars['topicId']:0;
         $ipaddress=$vars['ip']?$vars['ip']:$_SERVER['REMOTE_ADDR'];
-        
+
         //We are ready son...hold on to the rails.
         $extId=Ticket::genExtRandID();
         $sql='INSERT INTO '.TICKET_TABLE.' SET created=NOW() '
@@ -2220,7 +1950,7 @@ class Ticket {
             .' ,subject='.db_input(Format::striptags($vars['subject']))
             .' ,phone="'.db_input($vars['phone'],false).'"'
             .' ,phone_ext='.db_input($vars['phone_ext']?$vars['phone_ext']:'')
-            .' ,ip_address='.db_input($ipaddress) 
+            .' ,ip_address='.db_input($ipaddress)
             .' ,source='.db_input($source);
 
         //Make sure the origin is staff - avoid firebug hack!
@@ -2232,17 +1962,20 @@ class Ticket {
             return null;
 
         /* -------------------- POST CREATE ------------------------ */
-        
-        if(!$cfg->useRandomIds()){
+
+        if(!$cfg->useRandomIds()) {
             //Sequential ticketIDs support really..really suck arse.
             $extId=$id; //To make things really easy we are going to use autoincrement ticket_id.
-            db_query('UPDATE '.TICKET_TABLE.' SET ticketID='.db_input($extId).' WHERE ticket_id='.$id.' LIMIT 1'); 
+            db_query('UPDATE '.TICKET_TABLE.' SET ticketID='.db_input($extId).' WHERE ticket_id='.$id.' LIMIT 1');
             //TODO: RETHING what happens if this fails?? [At the moment on failure random ID is used...making stuff usable]
         }
 
         $dept = $ticket->getDept();
+
         //post the message.
-        $msgid=$ticket->postMessage($vars , $source, false);
+        unset($vars['cannedattachments']); //Ticket::open() might have it set as part of  open & respond.
+        $vars['title'] = $vars['subject']; //Use the initial subject as title of the post.
+        $message = $ticket->postMessage($vars , $origin, false);
 
         // Configure service-level-agreement for this ticket
         $ticket->selectSLAId($vars['slaId']);
@@ -2260,10 +1993,8 @@ class Ticket {
 
         # Messages that are clearly auto-responses from email systems should
         # not have a return 'ping' message
-        if ($autorespond && $vars['header'] &&
-                TicketFilter::isAutoResponse(Mail_Parse::splitHeaders($vars['header']))) {
+        if ($autorespond && $message && $message->isAutoResponse())
             $autorespond=false;
-        }
 
         //Don't auto respond to mailer daemons.
         if( $autorespond &&
@@ -2274,8 +2005,8 @@ class Ticket {
 
         //post canned auto-response IF any (disables new ticket auto-response).
         if ($vars['cannedResponseId']
-            && $ticket->postCannedReply($vars['cannedResponseId'], $msgid, $autorespond)) {
-                $ticket->markUnAnswered(); //Leave the ticket as unanswred. 
+            && $ticket->postCannedReply($vars['cannedResponseId'], $message->getId(), $autorespond)) {
+                $ticket->markUnAnswered(); //Leave the ticket as unanswred.
                 $autorespond = false;
         }
 
@@ -2285,7 +2016,7 @@ class Ticket {
             $autorespond=false;
 
         /***** See if we need to send some alerts ****/
-        $ticket->onNewTicket($vars['message'], $autorespond, $alertstaff);
+        $ticket->onNewTicket($message, $autorespond, $alertstaff);
 
         /************ check if the user JUST reached the max. open tickets limit **********/
         if($cfg->getMaxOpenTickets()>0
@@ -2293,7 +2024,7 @@ class Ticket {
                     && ($client->getNumOpenTickets()==$cfg->getMaxOpenTickets())) {
             $ticket->onOpenLimit(($autorespond && strcasecmp($origin, 'staff')));
         }
-        
+
         /* Start tracking ticket lifecycle events */
         $ticket->logEvent('created');
 
@@ -2303,33 +2034,34 @@ class Ticket {
     }
 
     function open($vars, &$errors) {
-        global $thisstaff,$cfg;
+        global $thisstaff, $cfg;
 
         if(!$thisstaff || !$thisstaff->canCreateTickets()) return false;
-        
+
+        if($vars['source'] && !in_array(strtolower($vars['source']),array('email','phone','other')))
+            $errors['source']='Invalid source - '.Format::htmlchars($vars['source']);
+
         if(!$vars['issue'])
             $errors['issue']='Summary of the issue required';
         else
             $vars['message']=$vars['issue'];
 
-        if($vars['source'] && !in_array(strtolower($vars['source']),array('email','phone','other')))
-            $errors['source']='Invalid source - '.Format::htmlchars($vars['source']);
-
         if(!($ticket=Ticket::create($vars, $errors, 'staff', false, (!$vars['assignId']))))
             return false;
 
         $vars['msgId']=$ticket->getLastMsgId();
-        $respId = 0;
-        
+
         // post response - if any
-        if($vars['response']) {
+        $response = null;
+        if($vars['response'] && $thisstaff->canPostReply()) {
             $vars['response'] = $ticket->replaceVars($vars['response']);
-            if(($respId=$ticket->postReply($vars, $errors, false))) {
+            if(($response=$ticket->postReply($vars, $errors, false))) {
                 //Only state supported is closed on response
                 if(isset($vars['ticket_state']) && $thisstaff->canCloseTickets())
                     $ticket->setState($vars['ticket_state']);
             }
         }
+
         //Post Internal note
         if($vars['assignId'] && $thisstaff->canAssignTickets()) { //Assign ticket to staff or team.
             $ticket->assign($vars['assignId'], $vars['note']);
@@ -2340,24 +2072,24 @@ class Ticket {
         }
 
         $ticket->reload();
-        
+
         if(!$cfg->notifyONNewStaffTicket() || !isset($vars['alertuser']))
             return $ticket; //No alerts.
 
         //Send Notice to user --- if requested AND enabled!!
-                
+
         $dept=$ticket->getDept();
         if(!$dept || !($tpl=$dept->getTemplate()))
             $tpl=$cfg->getDefaultTemplate();
-                                
+
         if(!$dept || !($email=$dept->getEmail()))
             $email =$cfg->getDefaultEmail();
 
         if($tpl && ($msg=$tpl->getNewTicketNoticeMsgTemplate()) && $email) {
-                        
+
             $message = $vars['issue'];
-            if($vars['response'])
-                $message.="\n\n".$vars['response'];
+            if($response)
+                $message.="\n\n".$response->getBody();
 
             if($vars['signature']=='mine')
                 $signature=$thisstaff->getSignature();
@@ -2365,23 +2097,23 @@ class Ticket {
                 $signature=$dept->getSignature();
             else
                 $signature='';
-            
-            $msg = $ticket->replaceVars($msg, 
+
+            $msg = $ticket->replaceVars($msg,
                     array('message' => $message, 'signature' => $signature));
 
             if($cfg->stripQuotedReply() && ($tag=trim($cfg->getReplySeparator())))
                 $msg['body'] ="\n$tag\n\n".$msg['body'];
 
-            $attachments =($cfg->emailAttachments() && $respId)?$ticket->getAttachments($respId,'R'):array();
+            $attachments =($cfg->emailAttachments() && $response)?$response->getAttachments():array();
             $email->send($ticket->getEmail(), $msg['subj'], $msg['body'], $attachments);
         }
 
         return $ticket;
-    
+
     }
-   
+
     function checkOverdue() {
-       
+
         $sql='SELECT ticket_id FROM '.TICKET_TABLE.' T1 '
             .' INNER JOIN '.SLA_TABLE.' T2 ON (T1.sla_id=T2.id AND T2.isactive=1) '
             .' WHERE status=\'open\' AND isoverdue=0 '
@@ -2400,6 +2132,6 @@ class Ticket {
 
         }
    }
-    
+
 }
 ?>
diff --git a/include/class.upgrader.php b/include/class.upgrader.php
index 7e45e9e6a0e33933148ac7133b280c2759f09fd4..8b86b9e5724f07ea3fbb4bf3132a554e79ca7586 100644
--- a/include/class.upgrader.php
+++ b/include/class.upgrader.php
@@ -23,13 +23,16 @@ class Upgrader extends SetupWizard {
     var $sqldir;
     var $signature;
 
+    var $state;
+    var $mode;
+
     function Upgrader($signature, $prefix, $sqldir) {
 
         $this->signature = $signature;
-        $this->shash = substr($signature, 0, 8);
         $this->prefix = $prefix;
         $this->sqldir = $sqldir;
         $this->errors = array();
+        $this->mode = 'ajax'; //
 
         //Disable time limit if - safe mode is set.
         if(!ini_get('safe_mode'))
@@ -38,6 +41,8 @@ class Upgrader extends SetupWizard {
         //Init persistent state of upgrade.
         $this->state = &$_SESSION['ost_upgrader']['state'];
 
+        $this->mode = &$_SESSION['ost_upgrader']['mode'];
+
         //Init the task Manager.
         if(!isset($_SESSION['ost_upgrader'][$this->getShash()]))
             $_SESSION['ost_upgrader'][$this->getShash()]['tasks']=array();
@@ -45,8 +50,8 @@ class Upgrader extends SetupWizard {
         //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);
+        //Database migrater
+        $this->migrater = null;
     }
 
     function onError($error) {
@@ -87,7 +92,7 @@ class Upgrader extends SetupWizard {
     }
 
     function getShash() {
-        return $this->shash;
+        return  substr($this->getSchemaSignature(), 0, 8);
     }
 
     function getTablePrefix() {
@@ -106,13 +111,31 @@ class Upgrader extends SetupWizard {
         $this->state = $state;
     }
 
+    function getMode() {
+        return $this->mode;
+    }
+
+    function setMode($mode) {
+        $this->mode = $mode;
+    }
+
+    function getMigrater() {
+        if(!$this->migrater)
+            $this->migrater = new DatabaseMigrater($this->signature, SCHEMA_SIGNATURE, $this->sqldir);
+
+        return  $this->migrater;
+    }
+
     function getPatches() {
-        return $this->migrater->getPatches();
+        $patches = array();
+        if($this->getMigrater())
+            $patches = $this->getMigrater()->getPatches();
+
+        return $patches;
     }
 
     function getNextPatch() {
-        $p = $this->getPatches();
-        return (count($p)) ? $p[0] : false;
+        return (($p=$this->getPatches()) && count($p)) ? $p[0] : false;
     }
 
     function getNextVersion() {
@@ -140,7 +163,7 @@ class Upgrader extends SetupWizard {
         $action='Upgrade osTicket to '.$this->getVersion();
         if($this->getNumPendingTasks() && ($task=$this->getNextTask())) {
             $action = $task['desc'];
-            if($task['status']) //Progress report... 
+            if($task['status']) //Progress report...
                 $action.=' ('.$task['status'].')';
         } elseif($this->isUpgradable() && ($nextversion = $this->getNextVersion())) {
             $action = "Upgrade to $nextversion";
@@ -161,9 +184,9 @@ class Upgrader extends SetupWizard {
             foreach($tasks as $k => $task) {
                 if(!$task['done'])
                     $pending[$k] = $task;
-            }  
+            }
         }
-        
+
         return $pending;
     }
 
@@ -198,7 +221,11 @@ class Upgrader extends SetupWizard {
         if(!($tasks=$this->getPendingTasks()))
             return true; //Nothing to do.
 
-        $ost->logDebug('Upgrader', sprintf('There are %d pending upgrade tasks', count($tasks)));
+        $c = count($tasks);
+        $ost->logDebug(
+                sprintf('Upgrader - %s (%d pending tasks).', $this->getShash(), $c),
+                sprintf('There are %d pending upgrade tasks for %s patch', $c, $this->getShash())
+                );
         $start_time = Misc::micro_time();
         foreach($tasks as $k => $task) {
             //TODO: check time used vs. max execution - break if need be
@@ -211,7 +238,7 @@ class Upgrader extends SetupWizard {
 
         return $this->getPendingTasks();
     }
-    
+
     function upgrade() {
         global $ost;
 
@@ -227,18 +254,20 @@ class Upgrader extends SetupWizard {
             if (!$this->load_sql_file($patch, $this->getTablePrefix()))
                 return false;
 
-            //clear previous patch info - 
+            //clear previous patch info -
             unset($_SESSION['ost_upgrader'][$this->getShash()]);
 
             $phash = substr(basename($patch), 0, 17);
+            $shash = substr($phash, 9, 8);
 
             //Log the patch info
-            $logMsg = "Patch $phash applied ";
+            $logMsg = "Patch $phash applied successfully ";
             if(($info = $this->readPatchInfo($patch)) && $info['version'])
                 $logMsg.= ' ('.$info['version'].') ';
 
-            $ost->logDebug('Upgrader - Patch applied', $logMsg);
-            
+            $ost->logDebug("Upgrader - $shash applied", $logMsg);
+            $this->signature = $shash; //Update signature to the *new* HEAD
+
             //Check if the said patch has scripted tasks
             if(!($tasks=$this->getTasksForPatch($phash))) {
                 //Break IF elapsed time is greater than 80% max time allowed.
@@ -250,16 +279,14 @@ class Upgrader extends SetupWizard {
             }
 
             //We have work to do... set the tasks and break.
-            $shash = substr($phash, 9, 8);
             $_SESSION['ost_upgrader'][$shash]['tasks'] = $tasks;
             $_SESSION['ost_upgrader'][$shash]['state'] = 'upgrade';
-            
-            $ost->logDebug('Upgrader', sprintf('Found %d tasks to be executed for %s',
-                            count($tasks), $shash));
             break;
-
         }
 
+        //Reset the migrater
+        $this->migrater = null;
+
         return true;
 
     }
@@ -286,9 +313,9 @@ class Upgrader extends SetupWizard {
                 break;
         }
 
-        //Check IF SQL cleanup exists. 
+        //Check IF SQL cleanup exists.
         $file=$this->getSQLDir().$phash.'.cleanup.sql';
-        if(file_exists($file)) 
+        if(file_exists($file))
             $tasks[] = array('func' => 'cleanup',
                              'desc' => 'Post-upgrade cleanup!',
                              'phash' => $phash);
@@ -306,7 +333,7 @@ class Upgrader extends SetupWizard {
         if(!file_exists($file)) //No cleanup script.
             return 0;
 
-        //We have a cleanup script  ::XXX: Don't abort on error? 
+        //We have a cleanup script  ::XXX: Don't abort on error?
         if($this->load_sql_file($file, $this->getTablePrefix(), false, true))
             return 0;
 
@@ -317,7 +344,7 @@ class Upgrader extends SetupWizard {
 
     function migrateAttachments2DB($taskId) {
         global $ost;
-        
+
         if(!($max_time = ini_get('max_execution_time')))
             $max_time = 30; //Default to 30 sec batches.
 
@@ -330,7 +357,7 @@ class Upgrader extends SetupWizard {
 
     function migrateSessionFile2DB($taskId) {
         # How about 'dis for a hack?
-        osTicketSession::write(session_id(), session_encode()); 
+        osTicketSession::write(session_id(), session_encode());
         return 0;
     }
 
diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php
index 7f1751872e3ec4d2db60f0855dd433edbafb2d3a..8eda7eca323e9cf5a9cc8b142575acd9a72d59de 100644
--- a/include/client/tickets.inc.php
+++ b/include/client/tickets.inc.php
@@ -28,7 +28,7 @@ if($sort && $sortOptions[$sort])
     $order_by =$sortOptions[$sort];
 
 $order_by=$order_by?$order_by:'ticket_created';
-if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) 
+if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])])
     $order=$orderWays[strtoupper($_REQUEST['order'])];
 
 $order=$order?$order:'ASC';
@@ -69,7 +69,8 @@ if($search) {
 }
 
 $total=db_count('SELECT count(DISTINCT ticket.ticket_id) '.$qfrom.' '.$qwhere);
-$pageNav=new Pagenate($total,$page, PAGE_LIMIT);
+$page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1;
+$pageNav=new Pagenate($total, $page, PAGE_LIMIT);
 $pageNav->setURL('tickets.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']));
 
 //more stuff...
@@ -95,8 +96,15 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting
     <input type="text" name="q" size="20" value="<?php echo Format::htmlchars($_REQUEST['q']); ?>">
     <select name="status">
         <option value="">&mdash; Any Status &mdash;</option>
-        <option value="open" <?php echo ($status=='open')?'selected="selected"':'';?>>Open</option>
-        <option value="closed" <?php echo ($status=='closed')?'selected="selected"':'';?>>Closed</option>
+        <option value="open"
+            <?php echo ($status=='open')?'selected="selected"':'';?>>Open (<?php echo $thisclient->getNumOpenTickets(); ?>)</option>
+        <?php
+        if($thisclient->getNumClosedTickets()) {
+            ?>
+        <option value="closed"
+            <?php echo ($status=='closed')?'selected="selected"':'';?>>Closed (<?php echo $thisclient->getNumClosedTickets(); ?>)</option>
+        <?php
+        } ?>
     </select>
     <input type="submit" value="Go">
 </form>
@@ -144,7 +152,7 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting
             ?>
             <tr id="<?php echo $row['ticketID']; ?>">
                 <td class="centered">
-                <a class="Icon <?php echo strtolower($row['source']); ?>Ticket" title="<?php echo $row['email']; ?>" 
+                <a class="Icon <?php echo strtolower($row['source']); ?>Ticket" title="<?php echo $row['email']; ?>"
                     href="tickets.php?id=<?php echo $row['ticketID']; ?>"><?php echo $ticketID; ?></a>
                 </td>
                 <td>&nbsp;<?php echo Format::db_date($row['created']); ?></td>
@@ -165,7 +173,7 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting
     </tbody>
 </table>
 <?php
-if($res && $num>0) { 
+if($res && $num>0) {
     echo '<div>&nbsp;Page:'.$pageNav->getPageLinks().'&nbsp;</div>';
 }
 ?>
diff --git a/include/client/view.inc.php b/include/client/view.inc.php
index fa8a5b4a5420df21565f463b79c59ac33ecceca7..6684413c753e61bcb3188b94f5c8581754e9aa2b 100644
--- a/include/client/view.inc.php
+++ b/include/client/view.inc.php
@@ -72,7 +72,9 @@ if($ticket->getThreadCount() && ($thread=$ticket->getClientThread())) {
             <tr><th><?php echo Format::db_datetime($entry['created']); ?> &nbsp;&nbsp;<span><?php echo $poster; ?></span></th></tr>
             <tr><td><?php echo Format::display($entry['body']); ?></td></tr>
             <?php
-            if($entry['attachments'] && ($links=$ticket->getAttachmentsLinks($entry['id'], $entry['thread_type']))) { ?>
+            if($entry['attachments']
+                    && ($tentry=$ticket->getThreadEntry($entry['id']))
+                    && ($links=$tentry->getAttachmentsLinks())) { ?>
                 <tr><td class="info"><?php echo $links; ?></td></tr>
             <?php
             } ?>
diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php
index 60d61257fe3ba781a4e98ae3bfd6ebab280f4259..4d3f47f6f90849c6b4b6ed313fa95bcb96df2cc4 100644
--- a/include/staff/settings-tickets.inc.php
+++ b/include/staff/settings-tickets.inc.php
@@ -96,7 +96,7 @@ if(!($maxfileuploads=ini_get('max_file_uploads')))
                 <input type="checkbox" name="show_related_tickets" value="1" <?php echo $config['show_related_tickets'] ?'checked="checked"':''; ?> >
                 <em>(Show all related tickets on user login - otherwise access is restricted to one ticket view per login)</em>
             </td>
-        </tr>        
+        </tr>
         <tr>
             <td width="180">Show Notes Inline:</td>
             <td>
@@ -154,7 +154,7 @@ if(!($maxfileuploads=ini_get('max_file_uploads')))
         </tr>
         <tr>
             <th colspan="2">
-                <em><b>Attachments</b>:  Size setting mainly apply to web tickets.</em>
+                <em><b>Attachments</b>:  Size and max. uploads setting mainly apply to web tickets.</em>
             </th>
         </tr>
         <tr>
@@ -166,14 +166,14 @@ if(!($maxfileuploads=ini_get('max_file_uploads')))
             </td>
         </tr>
         <tr>
-            <td width="180">Emailed Attachments:</td>
+            <td width="180">Emailed/API Attachments:</td>
             <td>
-                <input type="checkbox" name="allow_email_attachments" <?php echo $config['allow_email_attachments']?'checked="checked"':''; ?>> Accept emailed files
+                <input type="checkbox" name="allow_email_attachments" <?php echo $config['allow_email_attachments']?'checked="checked"':''; ?>> Accept emailed/API attachments.
                     &nbsp;<font class="error">&nbsp;<?php echo $errors['allow_email_attachments']; ?></font>
             </td>
         </tr>
         <tr>
-            <td width="180">Online Attachments:</td>
+            <td width="180">Online/Web Attachments:</td>
             <td>
                 <input type="checkbox" name="allow_online_attachments" <?php echo $config['allow_online_attachments']?'checked="checked"':''; ?> >
                     Allow web upload &nbsp;&nbsp;&nbsp;&nbsp;
diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php
index f36c1e0bcd97dcf1fd067fd71e585675283788f5..ed29b87391f632efbdddc886db8809edfefb51ee 100644
--- a/include/staff/ticket-open.inc.php
+++ b/include/staff/ticket-open.inc.php
@@ -219,6 +219,10 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 <textarea name="issue" cols="21" rows="8" style="width:80%;"><?php echo $info['issue']; ?></textarea>
             </td>
         </tr>
+        <?php
+        //is the user allowed to post replies??
+        if($thisstaff->canPostReply()) {
+            ?>
         <tr>
             <th colspan="2">
                 <em><strong>Response</strong>: Optional response to the above issue.</em>
@@ -270,8 +274,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                             </div>
                         </td>
                     </tr>
-            <?php
-            } ?>
+                <?php
+                } ?>
 
             <?php
             if($thisstaff->canCloseTickets()) { ?>
@@ -304,6 +308,9 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
             </table>
             </td>
         </tr>
+        <?php
+        } //end canPostReply
+        ?>
         <tr>
             <th colspan="2">
                 <em><strong>Internal Note</strong>: Optional internal note (recommended on assignment) <font class="error">&nbsp;<?php echo $errors['note']; ?></font></em>
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index 2a1a7aeb6953582744ba7cbebc549c4eda3d2183..45f25805b215f158cf0496da5f4c61340e307737 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -306,7 +306,9 @@ if(!$cfg->showNotesInline()) { ?>
                 </td>
             </tr>
             <?php
-            if($note['attachments'] && ($links=$ticket->getAttachmentsLinks($note['id'],'N'))) {?>
+             if($note['attachments'] 
+                    && ($tentry=$ticket->getThreadEntry($note['id'])) 
+                    && ($links=$tentry->getAttachmentsLinks())) { ?>
             <tr>
                 <td class="info" colspan="2"><?php echo $links; ?></td>
             </tr>
@@ -325,7 +327,10 @@ if(!$cfg->showNotesInline()) { ?>
     <?php
     $threadTypes=array('M'=>'message','R'=>'response', 'N'=>'note');
     /* -------- Messages & Responses & Notes (if inline)-------------*/
-    if(($thread=$ticket->getThread($cfg->showNotesInline()))) {
+    $types = array('M', 'R');
+    if($cfg->showNotesInline())
+        $types[] = 'N';
+    if(($thread=$ticket->getThreadEntries($types))) {
        foreach($thread as $entry) {
            ?>
         <table class="<?php echo $threadTypes[$entry['thread_type']]; ?>" cellspacing="0" cellpadding="1" width="940" border="0">
@@ -336,7 +341,9 @@ if(!$cfg->showNotesInline()) { ?>
             </tr>
             <tr><td colspan=3><?php echo Format::display($entry['body']); ?></td></tr>
             <?php
-            if($entry['attachments'] && ($links=$ticket->getAttachmentsLinks($entry['id'], $entry['thread_type']))) {?>
+            if($entry['attachments'] 
+                    && ($tentry=$ticket->getThreadEntry($entry['id']))
+                    && ($links=$tentry->getAttachmentsLinks())) {?>
             <tr>
                 <td class="info" colspan=3><?php echo $links; ?></td>
             </tr>
@@ -512,7 +519,7 @@ if(!$cfg->showNotesInline()) { ?>
         <input type="hidden" name="a" value="postnote">
         <table border="0" cellspacing="0" cellpadding="3">
             <?php 
-            if($errors['note']) {?>
+            if($errors['postnote']) {?>
             <tr>
                 <td width="160">&nbsp;</td>
                 <td class="error"><?php echo $errors['postnote']; ?></td>
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index ecbedfc668bda09277ebd21ef71e4bd921391ebb..ed5916196bcbb731cd1ced3ed4db19e4a2ea80bb 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -19,10 +19,9 @@ if($search) {
       $searchTerm='';
   }
 }
-$showoverdue=$showanswered=$showassigned=false;
+$showoverdue=$showanswered=false;
 $staffId=0; //Nothing for now...TODO: Allow admin and manager to limit tickets to single staff level.
-//show Assigned To column, if enabled. Admins and managers can overwrite system settings!
-$showassigned=(($cfg->showAssignedTickets() || $thisstaff->showAssignedTickets()) && !$search);
+$showassigned= true; //show Assigned To column - defaults to true 
 
 //Get status we are actually going to use on the query...making sure it is clean!
 $status=null;
@@ -32,7 +31,7 @@ switch(strtolower($_REQUEST['status'])){ //Status is overloaded
         break;
     case 'closed':
         $status='closed';
-        $showassigned=false;
+        $showassigned=true; //closed by.
         break;
     case 'overdue':
         $status='open';
@@ -51,7 +50,7 @@ switch(strtolower($_REQUEST['status'])){ //Status is overloaded
         break;
     default:
         if(!$search)
-            $status='open';
+            $_REQUEST['status']=$status='open';
 }
 
 $qwhere ='';
@@ -77,8 +76,8 @@ if($status) {
     $qwhere.=' AND status='.db_input(strtolower($status));    
 }
 
-//Overloaded sub-statuses  - you've got to just have faith!
-if($staffId && ($staffId==$thisstaff->getId())) { //Staff's assigned tickets.
+//Queues: Overloaded sub-statuses  - you've got to just have faith!
+if($staffId && ($staffId==$thisstaff->getId())) { //My tickets
     $results_type='Assigned Tickets';
     $qwhere.=' AND ticket.staff_id='.db_input($staffId);
     $showassigned=false; //My tickets...already assigned to the staff.
@@ -86,14 +85,20 @@ if($staffId && ($staffId==$thisstaff->getId())) { //Staff's assigned tickets.
     $qwhere.=' AND isoverdue=1 ';
 }elseif($showanswered) { ////Answered
     $qwhere.=' AND isanswered=1 ';
-}elseif(!$search && !$cfg->showAnsweredTickets() && !strcasecmp($status,'open')) {
-    $qwhere.=' AND isanswered=0 ';
+}elseif(!strcasecmp($status, 'open') && !$search) { //Open queue (on search OPEN means all open tickets - regardless of state).
+    //Showing answered tickets on open queue??
+    if(!$cfg->showAnsweredTickets()) 
+        $qwhere.=' AND isanswered=0 ';
+
+    /* Showing assigned tickets on open queue? 
+       Don't confuse it with show assigned To column -> F'it it's confusing - just trust me!
+     */
+    if(!($cfg->showAssignedTickets() || $thisstaff->showAssignedTickets())) {
+        $qwhere.=' AND ticket.staff_id=0 '; //XXX: NOT factoring in team assignments - only staff assignments.
+        $showassigned=false; //Not showing Assigned To column since assigned tickets are not part of open queue
+    }
 }
 
-//******* Showing assigned tickets? (don't confuse it with show assigned To column). F'it it's confusing - just trust me! ***/
-if(!($cfg->showAssignedTickets() || $thisstaff->showAssignedTickets()) && strcasecmp($status,'closed') && !$search)
-    $sql.=' AND (ticket.staff_id=0 OR ticket.staff_id='.db_input($thisstaff->getId()).') ';
-
 //Search?? Somebody...get me some coffee 
 $deep_search=false;
 if($search):
@@ -137,25 +142,36 @@ if($search):
         $qwhere.=' AND ticket.dept_id='.db_input($_REQUEST['deptId']);
         $qstr.='&deptId='.urlencode($_REQUEST['deptId']);
     }
+
+    //Help topic
+    if($_REQUEST['topicId']) {
+        $qwhere.=' AND ticket.topic_id='.db_input($_REQUEST['topicId']);
+        $qstr.='&topicId='.urlencode($_REQUEST['topicId']);
+    }
         
     //Assignee 
-    if($_REQUEST['assignee'] && strcasecmp($_REQUEST['status'], 'closed'))  {
+    if(isset($_REQUEST['assignee']) && strcasecmp($_REQUEST['status'], 'closed'))  {
         $id=preg_replace("/[^0-9]/", "", $_REQUEST['assignee']);
         $assignee = $_REQUEST['assignee'];
         $qstr.='&assignee='.urlencode($_REQUEST['assignee']);
-        $qwhere.= ' AND ( ';
+        $qwhere.= ' AND ( 
+                ( ticket.status="open" ';
                   
         if($assignee[0]=='t')
-            $qwhere.='  (ticket.team_id='.db_input($id). ' AND ticket.status="open") ';
+            $qwhere.='  AND ticket.team_id='.db_input($id);
         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") ';
+            $qwhere.='  AND ticket.staff_id='.db_input($id);
+        elseif(is_numeric($id))
+            $qwhere.='  AND ticket.staff_id='.db_input($id);
         
+       $qwhere.=' ) ';
                    
         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']);
+        }elseif(isset($_REQUEST['staffId'])) {
+            $qwhere.= ' OR ticket.status="closed" ';
+            $qstr.='&staffId='.urlencode($_REQUEST['staffId']);
         }
             
         $qwhere.= ' ) ';
@@ -263,7 +279,8 @@ $qselect.=' ,count(attach.attach_id) as attachments '
          .' ,IF(ticket.duedate IS NULL,IF(sla.id IS NULL, NULL, DATE_ADD(ticket.created, INTERVAL sla.grace_period HOUR)), ticket.duedate) as duedate '
          .' ,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 ';
+         .' ,IF(staff.staff_id IS NULL,team.name,CONCAT_WS(" ", staff.lastname, staff.firstname)) as assigned '
+         .' ,IF(ptopic.topic_pid IS NULL, topic.topic, CONCAT_WS(" / ", ptopic.topic, topic.topic)) as helptopic ';
 
 $qfrom.=' LEFT JOIN '.TICKET_PRIORITY_TABLE.' pri ON (ticket.priority_id=pri.priority_id) '
        .' LEFT JOIN '.TICKET_LOCK_TABLE.' tlock ON (ticket.ticket_id=tlock.ticket_id AND tlock.expire>NOW() 
@@ -272,7 +289,10 @@ $qfrom.=' LEFT JOIN '.TICKET_PRIORITY_TABLE.' pri ON (ticket.priority_id=pri.pri
        .' 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) '
-       .' LEFT JOIN '.SLA_TABLE.' sla ON (ticket.sla_id=sla.id AND sla.isactive=1) ';
+       .' LEFT JOIN '.SLA_TABLE.' sla ON (ticket.sla_id=sla.id AND sla.isactive=1) '
+       .' LEFT JOIN '.TOPIC_TABLE.' topic ON (ticket.topic_id=topic.topic_id) '
+       .' LEFT JOIN '.TOPIC_TABLE.' ptopic ON (ptopic.topic_id=topic.topic_pid) ';
+
 
 $query="$qselect $qfrom $qwhere $qgroup ORDER BY $order_by $order LIMIT ".$pageNav->getStart().",".$pageNav->getLimit();
 //echo $query;
@@ -346,20 +366,23 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting..
             <?php
             }
 
-            if($showassigned){ ?>
-            <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>
-            <?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>
-            <?php 
+            if($showassigned ) { 
+                //Closed by
+                if(!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>
+                <?php
+                } else { //assigned to ?>
+                    <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>
+                <?php
+                }
             } else { ?>
-            <th width="150">
-                <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>
+                <th width="150">
+                    <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>
@@ -379,7 +402,7 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting..
                     $flag='overdue';
 
                 $lc='';
-                if($showassigned || !strcasecmp($status,'closed')) {
+                if($showassigned) {
                     if($row['staff_id'])
                         $lc=sprintf('<span class="Icon staffAssigned">%s</span>',Format::truncate($row['staff'],40));
                     elseif($row['team_id'])
@@ -544,6 +567,11 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting..
             <select id="status" name="status">
                 <option value="">&mdash; Any Status &mdash;</option>
                 <option value="open">Open</option>
+                <?php
+                if(!$cfg->showAnsweredTickets()) {?>
+                <option value="answered">Answered</option>
+                <?php
+                } ?>
                 <option value="overdue">Overdue</option>
                 <option value="closed">Closed</option>
             </select>
@@ -563,7 +591,9 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting..
         <fieldset class="owner">
             <label for="assignee">Assigned To:</label>
             <select id="assignee" name="assignee">
-                <option value="0">&mdash; Anyone &mdash;</option>
+                <option value="">&mdash; Anyone &mdash;</option>
+                <option value="0">&mdash; Unassigned &mdash;</option>
+                <option value="<?php echo $thisstaff->getId(); ?>">Me</option>
                 <?php
                 if(($users=Staff::getStaffMembers())) {
                     echo '<OPTGROUP label="Staff Members ('.count($users).')">';
@@ -587,6 +617,7 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting..
             <label for="staffId">Closed By:</label>
             <select id="staffId" name="staffId">
                 <option value="0">&mdash; Anyone &mdash;</option>
+                <option value="<?php echo $thisstaff->getId(); ?>">Me</option>
                 <?php
                 if(($users=Staff::getStaffMembers())) {
                     foreach($users as $id => $name)
@@ -595,6 +626,18 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting..
                 ?>
             </select>
         </fieldset>
+        <fieldset>
+            <label for="topicId">Help Topic:</label>
+            <select id="topicId" name="topicId">
+                <option value="" selected >&mdash; All Help Topics &mdash;</option>
+                <?php
+                if($topics=Topic::getHelpTopics()) {
+                    foreach($topics 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">
diff --git a/include/upgrader/upgrade.inc.php b/include/upgrader/upgrade.inc.php
index fae6947d94849c6cfbd4a7df849df708e470f1de..b157b6a030e4e0e23165a4a5720e31fe114ea133 100644
--- a/include/upgrader/upgrade.inc.php
+++ b/include/upgrader/upgrade.inc.php
@@ -1,5 +1,16 @@
 <?php
 if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied');
+
+//See if we need to switch the mode of upgrade...e.g from ajax (default) to manual
+if(($mode = $ost->get_var('m', $_GET)) &&  $mode!=$upgrader->getMode()) {
+    //Set Persistent mode/
+    $upgrader->setMode($mode);
+    //Log warning about ajax calls - most likely culprit is AcceptPathInfo directive.
+    if($mode=='manual')
+        $ost->logWarning('Ajax calls are failing',
+                'Make sure your server has AcceptPathInfo directive set to "ON" or get technical help');
+}
+
 $action=$upgrader->getNextAction();
 ?>
 <h2>osTicket Upgrade</h2>
@@ -9,7 +20,7 @@ $action=$upgrader->getNextAction();
              <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><?php echo $action ?></h2>
+            <h2 id="task"><?php echo $action ?></h2>
             <p>The upgrade wizard will now attempt to upgrade your database and core settings!</p>
             <ul>
                 <li>Database enhancements</li>
@@ -20,8 +31,9 @@ $action=$upgrader->getNextAction();
                 <form method="post" action="upgrade.php" id="upgrade">
                     <?php csrf_token(); ?>
                     <input type="hidden" name="s" value="upgrade">
+                    <input type="hidden" id="mode" name="m" value="<?php echo $upgrader->getMode(); ?>">
                     <input type="hidden" name="sh" value="<?php echo $upgrader->getSchemaSignature(); ?>">
-                    <input class="btn"  type="submit" name="submit" value="Do It Now!">
+                    <input class="btn"  type="submit" name="submit" value="Upgrade Now!">
                 </form>
             </div>
     </div>
@@ -33,9 +45,11 @@ $action=$upgrader->getNextAction();
     </div>
     <div class="clear"></div>
     <div id="upgrading">
-        <h4><?php echo $action; ?></h4>
+        <h4 id="action"><?php echo $action; ?></h4>
         Please wait... while we upgrade your osTicket installation!
-        <div id="msg" style="font-weight: bold;padding-top:10px;">Smile!</div>
+        <div id="msg" style="font-weight: bold;padding-top:10px;">
+            <?php echo sprintf("%s - Relax!", $thisstaff->getFirstName()); ?>
+        </div>
     </div>
 </div>
-<div class="clear"></div>`
+<div class="clear"></div>
diff --git a/js/jquery.multifile.js b/js/jquery.multifile.js
index 9b9a23b7d47065e1ecdea827f4b4d2cc05574dc6..0c0dd4a1cc4fa75e59a0bc232acde768dede0b60 100644
--- a/js/jquery.multifile.js
+++ b/js/jquery.multifile.js
@@ -3,7 +3,9 @@
 
     Multifile plugin that allows users to upload multiple files at once in unobstructive manner - cleaner interface.
 
-    Allows limiting number of files and file type(s) using file extension.
+    Allows limiting number of files
+    Whitelist file type(s) using file extension
+    Limit file sizes.
 
     NOTE:
     * Files are not uploaded until the form is submitted
@@ -50,7 +52,21 @@
 
                 if(fObj.data('files')>=settings.max_uploads || (fObj.data('files')+file.count)>settings.max_uploads) {
                     alert('You have reached the maximum number of files ('+ settings.max_uploads+') allowed per upload');
-                } else if($.fn.multifile.checkFileTypes(file, settings.allowedFileTypes)) {
+                } else if(!$.fn.multifile.checkFileTypes(file, settings.allowedFileTypes)) {
+                    var msg = 'Selected file type is NOT allowed';
+                    if(file.count>1)
+                        msg = 'File type of one or more of the selected files is NOT allowed';
+
+                    alert('Error: '+msg);
+                    $this.replaceWith(new_input);
+                } else if(!$.fn.multifile.checkFileSize(file, settings.max_file_size)) {
+                    var msg = 'Selected file exceeds allowed size';
+                    if(file.count>1)
+                        msg = 'File size of one or more of the selected files exceeds allowed size';
+
+                    alert('Error: '+msg);
+                    $this.replaceWith(new_input);
+                } else {
                     $this.hide();
                     
                     settings
@@ -61,15 +77,6 @@
                     fObj.data('files', fObj.data('files')+file.count);
                     if(fObj.data('files')<settings.max_uploads)
                         $this.after(new_input);
-
-                } else {
-                    var msg = 'Selected file type is NOT allowed';
-                    if(file.count>1)
-                        msg = 'File type of one or more of the selected files is NOT allowed';
-
-                    alert('Error: '+msg);
-                    
-                    $this.replaceWith(new_input);
                 }
         
             }
@@ -126,6 +133,20 @@
           if(filenames[i] && $.inArray('.'+filenames[i].split('.').pop(), allowedFileTypes) == -1)
               return false;
 
+      return true;
+  };  
+  
+  $.fn.multifile.checkFileSize = function(file, MaxFileSize) {
+
+      //Size info not available or max file is not set (let server-side handle it).
+      if(!MaxFileSize || !file.size)
+          return true;
+     
+      var filesizes = $.map(file.size.split(','), $.trim);
+      for (var i = 0, _len = filesizes.length; i < _len; i++)
+          if(filesizes[i] > MaxFileSize)
+              return false;
+
       return true;
   };
 
@@ -150,16 +171,20 @@
     file.count = 1; 
     // check for HTML5 FileList support
     if ( !!global.FileList ) {
-      if ( input.files.length == 1 )
+      if ( input.files.length == 1 ) {
         file.name = input.files[0].name;
-      else { //Multi-select
+        file.size = '' + input.files[0].size;
+      } else { //Multi-select
         // We do this in order to support `multiple` files.
         // You can't display them separately because they 
         // belong to only one file input.  It is impossible
         // to remove just one of the files.
         file.name = input.files[0].name;
-        for (var i = 1, _len = input.files.length; i < _len; i++)
+        file.size = '' + input.files[0].size;
+        for (var i = 1, _len = input.files.length; i < _len; i++) {
           file.name += ', ' + input.files[i].name;
+          file.size += ', ' + input.files[i].size;
+        }
 
         file.count = i;
       }
@@ -173,6 +198,7 @@
   //Default options 
   $.fn.multifile.defaults = { 
                               max_uploads: 1,
-                              file_types: '.*'
+                              file_types: '.*',
+                              max_file_size: 0
                             };
 })(jQuery, this);
diff --git a/js/osticket.js b/js/osticket.js
index ceca388f0d15339967933876f29f43688d5854cf..4a0e87b030c0f0ebc26cc21521cf0586acff88bb 100644
--- a/js/osticket.js
+++ b/js/osticket.js
@@ -64,6 +64,7 @@ $(document).ready(function(){
      $('.multifile').multifile({
         container:   '.uploads',
         max_uploads: ($config && $config.max_file_uploads)?$config.max_file_uploads:1,
-        file_types:  ($config && $config.file_types)?$config.file_types:".*"
+        file_types:  ($config && $config.file_types)?$config.file_types:".*",
+        max_file_size: ($config && $config.max_file_size)?$config.max_file_size:0
        });
 });
diff --git a/main.inc.php b/main.inc.php
index 4ec8bbe8a5f2c8f5a005b2c966c9bbec80c5e53f..c4b10edc0fa5af958a25126b77bc7409338e7b06 100644
--- a/main.inc.php
+++ b/main.inc.php
@@ -13,8 +13,8 @@
     See LICENSE.TXT for details.
 
     vim: expandtab sw=4 ts=4 sts=4:
-**********************************************************************/    
-    
+**********************************************************************/
+
     #Disable direct access.
     if(!strcasecmp(basename($_SERVER['SCRIPT_NAME']),basename(__FILE__))) die('kwaheri rafiki!');
 
@@ -44,9 +44,22 @@
     if (defined('E_DEPRECATED')) # 5.3.0
         $error_reporting &= ~(E_DEPRECATED | E_USER_DEPRECATED);
     error_reporting($error_reporting); //Respect whatever is set in php.ini (sysadmin knows better??)
+
     #Don't display errors
-    ini_set('display_errors',1);
-    ini_set('display_startup_errors',1);
+    ini_set('display_errors', 0);
+    ini_set('display_startup_errors', 0);
+
+    //Default timezone
+    if (!ini_get('date.timezone')) {
+        if(function_exists('date_default_timezone_set')) {
+            if(@date_default_timezone_get()) //Let PHP determine the timezone.
+                @date_default_timezone_set(@date_default_timezone_get());
+            else //Default to EST - if PHP can't figure it out.
+                date_default_timezone_set('America/New_York');
+        } else { //Default when all fails. PHP < 5.
+            ini_set('date.timezone', 'America/New_York');
+        }
+    }
 
     #Set Dir constants
     if(!defined('ROOT_PATH')) define('ROOT_PATH','./'); //root path. Damn directories
@@ -62,7 +75,7 @@
     /*############## Do NOT monkey with anything else beyond this point UNLESS you really know what you are doing ##############*/
 
     #Current version && schema signature (Changes from version to version)
-    define('THIS_VERSION','1.7-RC5'); //Shown on admin panel
+    define('THIS_VERSION','1.7-RC6'); //Shown on admin panel
     define('SCHEMA_SIGNATURE', 'd959a00e55c75e0c903b9e37324fd25d'); //MD5 signature of the db schema. (used to trigger upgrades)
     #load config info
     $configfile='';
@@ -70,7 +83,7 @@
         $configfile=ROOT_DIR.'ostconfig.php';
     elseif(file_exists(INCLUDE_DIR.'settings.php')) { //OLD config file.. v 1.6 RC5
         $configfile=INCLUDE_DIR.'settings.php';
-        //Die gracefully on upgraded v1.6 RC5 installation - otherwise script dies with confusing message. 
+        //Die gracefully on upgraded v1.6 RC5 installation - otherwise script dies with confusing message.
         if(!strcasecmp(basename($_SERVER['SCRIPT_NAME']), 'settings.php'))
             die('Please rename config file include/settings.php to include/ost-config.php to continue!');
     } elseif(file_exists(INCLUDE_DIR.'ost-config.php')) //NEW config file v 1.6 stable ++
@@ -82,18 +95,18 @@
 
     require($configfile);
     define('CONFIG_FILE',$configfile); //used in admin.php to check perm.
-   
+
    //Path separator
     if(!defined('PATH_SEPARATOR')){
         if(strpos($_ENV['OS'],'Win')!==false || !strcasecmp(substr(PHP_OS, 0, 3),'WIN'))
             define('PATH_SEPARATOR', ';' ); //Windows
-        else 
+        else
             define('PATH_SEPARATOR',':'); //Linux
     }
 
     //Set include paths. Overwrite the default paths.
     ini_set('include_path', './'.PATH_SEPARATOR.INCLUDE_DIR.PATH_SEPARATOR.PEAR_DIR);
-   
+
 
     #include required files
     require(INCLUDE_DIR.'class.osticket.php');
@@ -121,7 +134,7 @@
     #Session related
     define('SESSION_SECRET', MD5(SECRET_SALT)); //Not that useful anymore...
     define('SESSION_TTL', 86400); // Default 24 hours
-   
+
     define('DEFAULT_MAX_FILE_UPLOADS',ini_get('max_file_uploads')?ini_get('max_file_uploads'):5);
     define('DEFAULT_PRIORITY_ID',1);
 
@@ -157,24 +170,24 @@
     define('TICKET_LOCK_TABLE',TABLE_PREFIX.'ticket_lock');
     define('TICKET_EVENT_TABLE',TABLE_PREFIX.'ticket_event');
     define('TICKET_EMAIL_INFO_TABLE',TABLE_PREFIX.'ticket_email_info');
-  
+
     define('EMAIL_TABLE',TABLE_PREFIX.'email');
     define('EMAIL_TEMPLATE_TABLE',TABLE_PREFIX.'email_template');
 
     define('FILTER_TABLE',TABLE_PREFIX.'filter');
     define('FILTER_RULE_TABLE',TABLE_PREFIX.'filter_rule');
-    
+
     define('BANLIST_TABLE',TABLE_PREFIX.'email_banlist'); //Not in use anymore....as of v 1.7
 
     define('SLA_TABLE',TABLE_PREFIX.'sla');
 
     define('API_KEY_TABLE',TABLE_PREFIX.'api_key');
-    define('TIMEZONE_TABLE',TABLE_PREFIX.'timezone'); 
+    define('TIMEZONE_TABLE',TABLE_PREFIX.'timezone');
 
     #Global overwrite
     if($_SERVER['HTTP_X_FORWARDED_FOR']) //Can contain multiple IPs - use the last one.
         $_SERVER['REMOTE_ADDR'] =  array_pop(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));
-   
+
     #Connect to the DB && get configuration from database
     $ferror=null;
     if (!db_connect(DBHOST,DBUSER,DBPASS) || !db_select_database(DBNAME)) {
@@ -191,7 +204,7 @@
         die("<b>Fatal Error:</b> Contact system administrator.");
         exit;
     }
-    
+
     //Init
     $session = $ost->getSession();
 
diff --git a/open.php b/open.php
index 79c55a91713f62e6d0595368fa7f18de65489bf6..0ce680707f6930cc324d3c4952a7bdf550ff60e6 100644
--- a/open.php
+++ b/open.php
@@ -1,59 +1,59 @@
-<?php
-/*********************************************************************
-    open.php
-
-    New tickets handle.
-
-    Peter Rotich <peter@osticket.com>
-    Copyright (c)  2006-2013 osTicket
-    http://www.osticket.com
-
-    Released under the GNU General Public License WITHOUT ANY WARRANTY.
-    See LICENSE.TXT for details.
-
-    vim: expandtab sw=4 ts=4 sts=4:
-**********************************************************************/
-require('client.inc.php');
-define('SOURCE','Web'); //Ticket source.
-$inc='open.inc.php';    //default include.
-$errors=array();
-if($_POST):
-    $_POST['deptId']=$_POST['emailId']=0; //Just Making sure we don't accept crap...only topicId is expected.
-    if($thisclient) {
-        $_POST['name']=$thisclient->getName();
-        $_POST['email']=$thisclient->getEmail();
-    } elseif($cfg->isCaptchaEnabled()) {
-        if(!$_POST['captcha'])
-            $errors['captcha']='Enter text shown on the image';
-        elseif(strcmp($_SESSION['captcha'],md5($_POST['captcha'])))
-            $errors['captcha']='Invalid - try again!';
-    }
-
-    //Ticket::create...checks for errors..
-    if(($ticket=Ticket::create($_POST,$errors,SOURCE))){
-        $msg='Support ticket request created';
-        //Upload attachments...         
-        if($cfg->allowOnlineAttachments() && $_FILES['attachments'])
-            $ticket->uploadFiles($_FILES['attachments'], $ticket->getLastMsgId(), 'M');
-
-        //Logged in...simply view the newly created ticket.
-        if($thisclient && $thisclient->isValid()) {
-            if(!$cfg->showRelatedTickets())
-                $_SESSION['_client']['key']= $ticket->getExtId(); //Resetting login Key to the current ticket!
-            session_write_close();
-            session_regenerate_id();
-            @header('Location: tickets.php?id='.$ticket->getExtId());
-        }
-        //Thank the user and promise speedy resolution!
-        $inc='thankyou.inc.php';
-    }else{
-        $errors['err']=$errors['err']?$errors['err']:'Unable to create a ticket. Please correct errors below and try again!';
-    }
-endif;
-
-//page
-$nav->setActiveNav('new');
-require(CLIENTINC_DIR.'header.inc.php');
-require(CLIENTINC_DIR.$inc);
-require(CLIENTINC_DIR.'footer.inc.php');
-?>
+<?php
+/*********************************************************************
+    open.php
+
+    New tickets handle.
+
+    Peter Rotich <peter@osticket.com>
+    Copyright (c)  2006-2013 osTicket
+    http://www.osticket.com
+
+    Released under the GNU General Public License WITHOUT ANY WARRANTY.
+    See LICENSE.TXT for details.
+
+    vim: expandtab sw=4 ts=4 sts=4:
+**********************************************************************/
+require('client.inc.php');
+define('SOURCE','Web'); //Ticket source.
+$inc='open.inc.php';    //default include.
+$errors=array();
+if($_POST):
+    $vars = $_POST;
+    $vars['deptId']=$vars['emailId']=0; //Just Making sure we don't accept crap...only topicId is expected.
+    if($thisclient) {
+        $vars['name']=$thisclient->getName();
+        $vars['email']=$thisclient->getEmail();
+    } elseif($cfg->isCaptchaEnabled()) {
+        if(!$_POST['captcha'])
+            $errors['captcha']='Enter text shown on the image';
+        elseif(strcmp($_SESSION['captcha'],md5($_POST['captcha'])))
+            $errors['captcha']='Invalid - try again!';
+    }
+
+    if(!$errors && $cfg->allowOnlineAttachments() && $_FILES['attachments'])
+        $vars['files'] = AttachmentFile::format($_FILES['attachments'], true);
+
+    //Ticket::create...checks for errors..
+    if(($ticket=Ticket::create($vars, $errors, SOURCE))){
+        $msg='Support ticket request created';
+        //Logged in...simply view the newly created ticket.
+        if($thisclient && $thisclient->isValid()) {
+            if(!$cfg->showRelatedTickets())
+                $_SESSION['_client']['key']= $ticket->getExtId(); //Resetting login Key to the current ticket!
+            session_write_close();
+            session_regenerate_id();
+            @header('Location: tickets.php?id='.$ticket->getExtId());
+        }
+        //Thank the user and promise speedy resolution!
+        $inc='thankyou.inc.php';
+    }else{
+        $errors['err']=$errors['err']?$errors['err']:'Unable to create a ticket. Please correct errors below and try again!';
+    }
+endif;
+
+//page
+$nav->setActiveNav('new');
+require(CLIENTINC_DIR.'header.inc.php');
+require(CLIENTINC_DIR.$inc);
+require(CLIENTINC_DIR.'footer.inc.php');
+?>
diff --git a/scp/ajax.php b/scp/ajax.php
index e4100f4e31c4f4028086e4608785df4fcdebba41..0f0771d3742efe983fe325fff52fe937f31e8dc5 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -27,7 +27,7 @@ require('staff.inc.php');
 ini_set('display_errors','0'); //Disable error display
 ini_set('display_startup_errors','0');
 
-//TODO: disable direct access via the browser? i,e All request must have REFER? 
+//TODO: disable direct access via the browser? i,e All request must have REFER?
 if(!defined('INCLUDE_DIR'))	Http::response(500, 'Server configuration error');
 
 require_once INCLUDE_DIR.'/class.dispatcher.php';
@@ -65,5 +65,5 @@ $dispatcher = patterns('',
 );
 
 # Call the respective function
-print $dispatcher->resolve($_SERVER['PATH_INFO']);
+print $dispatcher->resolve($ost->get_path_info());
 ?>
diff --git a/scp/canned.php b/scp/canned.php
index edd4a4c36f185e3e379337e4d59882264388328c..c085e4116c2530d21db6932d22696aa2b981eb6b 100644
--- a/scp/canned.php
+++ b/scp/canned.php
@@ -44,7 +44,7 @@ if($_POST && $thisstaff->canManageCannedResponses()) {
                     }
                 }
                 //Upload NEW attachments IF ANY - TODO: validate attachment types??
-                if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments'])))
+                if($_FILES['attachments'] && ($files=AttachmentFile::format($_FILES['attachments'])))
                     $canned->uploadAttachments($files);
 
                 $canned->reload();
@@ -58,7 +58,7 @@ if($_POST && $thisstaff->canManageCannedResponses()) {
                 $msg='Canned response added successfully';
                 $_REQUEST['a']=null;
                 //Upload attachments
-                if($_FILES['attachments'] && ($c=Canned::lookup($id)) && ($files=Format::files($_FILES['attachments'])))
+                if($_FILES['attachments'] && ($c=Canned::lookup($id)) && ($files=AttachmentFile::format($_FILES['attachments'])))
                     $c->uploadAttachments($files);
 
             } elseif(!$errors['err']) {
diff --git a/scp/js/scp.js b/scp/js/scp.js
index cf32e42d8b638ff1b465cb23eb9a196e72be31ba..aa80487bddfcf77353bbc5b3307e92b38c1ec37b 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -259,7 +259,8 @@ $(document).ready(function(){
      $('.multifile').multifile({
         container:   '.uploads',
         max_uploads: ($config && $config.max_file_uploads)?$config.max_file_uploads:1,
-        file_types:  ($config && $config.file_types)?$config.file_types:".*"
+        file_types:  ($config && $config.file_types)?$config.file_types:".*",
+        max_file_size: ($config && $config.max_file_size)?$config.max_file_size:0
         });
 
     /* Datepicker */
@@ -369,6 +370,7 @@ $(document).ready(function(){
                 break;
             case 'open':
             case 'overdue':
+            case 'answered':
                 $('select#staffId').find('option:first').attr('selected', 'selected').parent('select');
                 $('select#staffId').attr('disabled','disabled');
                 $('select#assignee').removeAttr('disabled');
diff --git a/scp/js/upgrader.js b/scp/js/upgrader.js
index 1631ac5cd5a7a156498f44e6f13020dae875dde2..2ac77d99f86c5db1b2a54d36f4afc709e2b9bad5 100644
--- a/scp/js/upgrader.js
+++ b/scp/js/upgrader.js
@@ -1,5 +1,5 @@
 jQuery(function($) {
-            
+
     $("#overlay").css({
         opacity : 0.3,
         top     : 0,
@@ -12,18 +12,21 @@ jQuery(function($) {
         top  : ($(window).height() / 3),
         left : ($(window).width() / 2 - 160)
         });
-        
+
     $('form#upgrade').submit(function(e) {
-        e.preventDefault();
         var form = $(this);
         $('input[type=submit]', this).attr('disabled', 'disabled');
         $('#overlay, #upgrading').show();
-        doTasks('upgrade.php',form.serialize());
-
-        return false;
-        });
+        if($('input#mode', form).val() == 'manual') {
+            return  true;
+        } else {
+            e.preventDefault();
+            autoUpgrade('upgrade.php',form.serialize());
+            return false;
+        }
+      });
 
-    function doTasks(url, data) {
+    function autoUpgrade(url, data) {
         function _lp(count) {
             $.ajax({
                 type: 'POST',
@@ -33,26 +36,34 @@ jQuery(function($) {
                 data: data,
                 dataType: 'text',
                 success: function(res) {
-                    if (res) { 
-                        $('#loading #msg').html(res);
-                    }
+                    $('#main #task').html(res);
+                    $('#upgrading #action').html(res);
+                    $('#upgrading #msg').html('Still busy... smile #'+count);
                 },
                 statusCode: {
                     200: function() {
-                        setTimeout(function() { _lp(count+1); }, 2);
+                        setTimeout(function() { _lp(count+1); }, 200);
                     },
 
                     201: function() {
-                        $('#loading #msg').html("We're done... cleaning up!");
+                        $('#upgrading #msg').html("Cleaning up!...");
                         setTimeout(function() { location.href =url+'?c='+count+'&r='+Math.floor((Math.random()*100)+1); }, 3000);
                     }
                 },
-                error: function() {
-                    $('#loading #msg').html("Something went wrong");
-                    setTimeout(function() { location.href =url+'?c='+count+'&r='+Math.floor((Math.random()*100)+1); }, 1000);
+                error: function(jqXHR, textStatus, errorThrown) {
+                    $('#upgrading #action').html('Error occurred. Aborting...');
+                    switch(jqXHR.status) {
+                        case 404:
+                            $('#upgrading #msg').html("Manual upgrade required (ajax failed)");
+                            setTimeout(function() { location.href =url+'?m=manual&c='+count+'&r='+Math.floor((Math.random()*100)+1); }, 2000);
+                            break;
+                        default:
+                            $('#upgrading #msg').html("Something went wrong");
+                            setTimeout(function() { location.href =url+'?c='+count+'&r='+Math.floor((Math.random()*100)+1); }, 2000);
+                    }
                 }
             });
         };
-        _lp(0);
+        _lp(1);
     }
 });
diff --git a/scp/tickets.php b/scp/tickets.php
index 9e436f200fe08f3348423d46f0118ba101fa0813..0a9ba30a443e88034c12c453989fc4537822d184 100644
--- a/scp/tickets.php
+++ b/scp/tickets.php
@@ -1,9 +1,9 @@
 <?php
 /*************************************************************************
     tickets.php
-    
+
     Handles all tickets related actions.
- 
+
     Peter Rotich <peter@osticket.com>
     Copyright (c)  2006-2013 osTicket
     http://www.osticket.com
@@ -46,23 +46,25 @@ if($_POST && !$errors):
                 $errors['err'] = 'Action denied. Contact admin for access';
             else {
 
-                if(!$_POST['msgId'])
-                    $errors['err']='Missing message ID - Internal error';
                 if(!$_POST['response'])
                     $errors['response']='Response required';
-            
                 //Use locks to avoid double replies
                 if($lock && $lock->getStaffId()!=$thisstaff->getId())
                     $errors['err']='Action Denied. Ticket is locked by someone else!';
-            
+
                 //Make sure the email is not banned
                 if(!$errors['err'] && TicketFilter::isBanned($ticket->getEmail()))
                     $errors['err']='Email is in banlist. Must be removed to reply.';
             }
 
             $wasOpen =($ticket->isOpen());
+
             //If no error...do the do.
-            if(!$errors && ($respId=$ticket->postReply($_POST, $errorsi, isset($_POST['emailreply'])))) {
+            $vars = $_POST;
+            if(!$errors && $_FILES['attachments'])
+                $vars['files'] = AttachmentFile::format($_FILES['attachments']);
+
+            if(!$errors && ($response=$ticket->postReply($vars, $errors, isset($_POST['emailreply'])))) {
                 $msg='Reply posted successfully';
                 $ticket->reload();
                 if($ticket->isClosed() && $wasOpen)
@@ -73,7 +75,7 @@ if($_POST && !$errors):
             }
             break;
         case 'transfer': /** Transfer ticket **/
-            //Check permission 
+            //Check permission
             if(!$thisstaff->canTransferTickets())
                 $errors['err']=$errors['transfer'] = 'Action Denied. You are not allowed to transfer tickets.';
             else {
@@ -85,13 +87,13 @@ if($_POST && !$errors):
                     $errors['deptId'] = 'Ticket already in the department';
                 elseif(!($dept=Dept::lookup($_POST['deptId'])))
                     $errors['deptId'] = 'Unknown or invalid department';
-            
+
                 //Transfer message - required.
                 if(!$_POST['transfer_comments'])
                     $errors['transfer_comments'] = 'Transfer comments required';
                 elseif(strlen($_POST['transfer_comments'])<5)
                     $errors['transfer_comments'] = 'Transfer comments too short!';
-           
+
                 //If no errors - them attempt the transfer.
                 if(!$errors && $ticket->transfer($_POST['deptId'], $_POST['transfer_comments'])) {
                     $msg = 'Ticket transferred successfully to '.$ticket->getDeptName();
@@ -112,7 +114,7 @@ if($_POST && !$errors):
              else {
 
                  $id = preg_replace("/[^0-9]/", "",$_POST['assignId']);
-                 $claim = (is_numeric($_POST['assignId']) && $_POST['assignId']==$thisstaff->getId()); 
+                 $claim = (is_numeric($_POST['assignId']) && $_POST['assignId']==$thisstaff->getId());
 
                  if(!$_POST['assignId'] || !$id)
                      $errors['assignId'] = 'Select assignee';
@@ -132,7 +134,7 @@ if($_POST && !$errors):
                      $errors['assign_comments'] = 'Assignment comments required';
                  elseif(strlen($_POST['assign_comments'])<5)
                          $errors['assign_comments'] = 'Comment too short';
-                 
+
                  if(!$errors && $ticket->assign($_POST['assignId'], $_POST['assign_comments'], !$claim)) {
                      if($claim) {
                          $msg = 'Ticket is NOW assigned to you!';
@@ -146,7 +148,7 @@ if($_POST && !$errors):
                      $errors['assign'] = 'Correct the error(s) below and try again!';
                  }
              }
-            break; 
+            break;
         case 'postnote': /* Post Internal Note */
             //Make sure the staff can set desired state
             if($_POST['state']) {
@@ -158,12 +160,22 @@ if($_POST && !$errors):
             }
 
             $wasOpen = ($ticket->isOpen());
-            if(($noteId=$ticket->postNote($_POST, $errors, $thisstaff))) {
+
+            $vars = $_POST;
+            if($_FILES['attachments'])
+                $vars['files'] = AttachmentFile::format($_FILES['attachments']);
+
+            if(($note=$ticket->postNote($vars, $errors, $thisstaff))) {
+
                 $msg='Internal note posted successfully';
                 if($wasOpen && $ticket->isClosed())
                     $ticket = null; //Going back to main listing.
+
             } else {
-                $errors['err'] = 'Unable to post internal note - missing or invalid data.';
+
+                if(!$errors['err'])
+                    $errors['err'] = 'Unable to post internal note - missing or invalid data.';
+
                 $errors['postnote'] = 'Unable to post the note. Correct the error(s) below and try again!';
             }
             break;
@@ -195,9 +207,9 @@ if($_POST && !$errors):
                             $note = $_POST['ticket_status_notes'];
                         else
                             $note='Ticket closed (without comments)';
-                        
+
                         $ticket->logNote('Ticket Closed', $note, $thisstaff);
-                        
+
                         //Going back to main listing.
                         TicketLock::removeStaffLocks($thisstaff->getId(), $ticket->getId());
                         $page=$ticket=null;
@@ -299,7 +311,7 @@ if($_POST && !$errors):
                     } elseif(Banlist::remove($ticket->getEmail())) {
                         $msg = 'Email removed from banlist';
                     } elseif(!BanList::includes($ticket->getEmail())) {
-                        $warn = 'Email is not in the banlist'; 
+                        $warn = 'Email is not in the banlist';
                     } else {
                         $errors['err']='Unable to remove the email from banlist. Try again.';
                     }
@@ -333,7 +345,7 @@ if($_POST && !$errors):
         switch($_POST['a']) {
             case 'mass_process':
                 if(!$thisstaff->canManageTickets())
-                    $errors['err']='You do not have permission to mass manage tickets. Contact admin for such access';    
+                    $errors['err']='You do not have permission to mass manage tickets. Contact admin for such access';
                 elseif(!$_POST['tids'] || !is_array($_POST['tids']))
                     $errors['err']='No tickets selected. You must select at least one ticket.';
                 else {
@@ -364,7 +376,7 @@ if($_POST && !$errors):
                             if($thisstaff->canCloseTickets()) {
                                 $note='Ticket closed without response by '.$thisstaff->getName();
                                 foreach($_POST['tids'] as $k=>$v) {
-                                    if(($t=Ticket::lookup($v)) && $t->isOpen() && @$t->close()) { 
+                                    if(($t=Ticket::lookup($v)) && $t->isOpen() && @$t->close()) {
                                         $i++;
                                         $t->logNote('Ticket Closed', $note, $thisstaff);
                                     }
@@ -401,7 +413,7 @@ if($_POST && !$errors):
                                 foreach($_POST['tids'] as $k=>$v) {
                                     if(($t=Ticket::lookup($v)) && @$t->delete()) $i++;
                                 }
-                        
+
                                 //Log a warning
                                 if($i) {
                                     $log = sprintf('%s (%s) just deleted %d ticket(s)',
@@ -429,13 +441,19 @@ if($_POST && !$errors):
                 $ticket=null;
                 if(!$thisstaff || !$thisstaff->canCreateTickets()) {
                      $errors['err']='You do not have permission to create tickets. Contact admin for such access';
-                }elseif(($ticket=Ticket::open($_POST, $errors))) {
-                    $msg='Ticket created successfully';
-                    $_REQUEST['a']=null;
-                    if(!$ticket->checkStaffAccess($thisstaff) || $ticket->isClosed())
-                        $ticket=null;
-                }elseif(!$errors['err']) {
-                    $errors['err']='Unable to create the ticket. Correct the error(s) and try again';
+                } else {
+                    $vars = $_POST;
+                    if($_FILES['attachments'])
+                        $vars['files'] = AttachmentFile::format($_FILES['attachments']);
+
+                    if(($ticket=Ticket::open($vars, $errors))) {
+                        $msg='Ticket created successfully';
+                        $_REQUEST['a']=null;
+                        if(!$ticket->checkStaffAccess($thisstaff) || $ticket->isClosed())
+                            $ticket=null;
+                    } elseif(!$errors['err']) {
+                        $errors['err']='Unable to create the ticket. Correct the error(s) and try again';
+                    }
                 }
                 break;
         }
@@ -450,7 +468,7 @@ $stats= $thisstaff->getTicketsStats();
 //Navigation
 $nav->setTabActive('tickets');
 if($cfg->showAnsweredTickets()) {
-    $nav->addSubMenu(array('desc'=>'Open ('.($stats['open']+$stats['answered']).')',
+    $nav->addSubMenu(array('desc'=>'Open ('.number_format($stats['open']+$stats['answered']).')',
                             'title'=>'Open Tickets',
                             'href'=>'tickets.php',
                             'iconclass'=>'Ticket'),
@@ -458,7 +476,7 @@ if($cfg->showAnsweredTickets()) {
 } else {
 
     if($stats) {
-        $nav->addSubMenu(array('desc'=>'Open ('.$stats['open'].')',
+        $nav->addSubMenu(array('desc'=>'Open ('.number_format($stats['open']).')',
                                'title'=>'Open Tickets',
                                'href'=>'tickets.php',
                                'iconclass'=>'Ticket'),
@@ -466,11 +484,11 @@ if($cfg->showAnsweredTickets()) {
     }
 
     if($stats['answered']) {
-        $nav->addSubMenu(array('desc'=>'Answered ('.$stats['answered'].')',
+        $nav->addSubMenu(array('desc'=>'Answered ('.number_format($stats['answered']).')',
                                'title'=>'Answered Tickets',
                                'href'=>'tickets.php?status=answered',
                                'iconclass'=>'answeredTickets'),
-                            ($_REQUEST['status']=='answered')); 
+                            ($_REQUEST['status']=='answered'));
     }
 }
 
@@ -478,7 +496,7 @@ if($stats['assigned']) {
     if(!$ost->getWarning() && $stats['assigned']>10)
         $ost->setWarning($stats['assigned'].' tickets assigned to you! Do something about it!');
 
-    $nav->addSubMenu(array('desc'=>'My Tickets ('.$stats['assigned'].')',
+    $nav->addSubMenu(array('desc'=>'My Tickets ('.number_format($stats['assigned']).')',
                            'title'=>'Assigned Tickets',
                            'href'=>'tickets.php?status=assigned',
                            'iconclass'=>'assignedTickets'),
@@ -486,7 +504,7 @@ if($stats['assigned']) {
 }
 
 if($stats['overdue']) {
-    $nav->addSubMenu(array('desc'=>'Overdue ('.$stats['overdue'].')',
+    $nav->addSubMenu(array('desc'=>'Overdue ('.number_format($stats['overdue']).')',
                            'title'=>'Stale Tickets',
                            'href'=>'tickets.php?status=overdue',
                            'iconclass'=>'overdueTickets'),
@@ -497,14 +515,14 @@ if($stats['overdue']) {
 }
 
 if($thisstaff->showAssignedOnly() && $stats['closed']) {
-    $nav->addSubMenu(array('desc'=>'My Closed Tickets ('.$stats['closed'].')',
+    $nav->addSubMenu(array('desc'=>'My Closed Tickets ('.number_format($stats['closed']).')',
                            'title'=>'My Closed Tickets',
                            'href'=>'tickets.php?status=closed',
                            'iconclass'=>'closedTickets'),
                         ($_REQUEST['status']=='closed'));
 } else {
 
-    $nav->addSubMenu(array('desc'=>'Closed Tickets',
+    $nav->addSubMenu(array('desc'=>'Closed Tickets ('.number_format($stats['closed']).')',
                            'title'=>'Closed Tickets',
                            'href'=>'tickets.php?status=closed',
                            'iconclass'=>'closedTickets'),
@@ -515,7 +533,7 @@ if($thisstaff->canCreateTickets()) {
     $nav->addSubMenu(array('desc'=>'New Ticket',
                            'href'=>'tickets.php?a=open',
                            'iconclass'=>'newTicket'),
-                        ($_REQUEST['a']=='open'));    
+                        ($_REQUEST['a']=='open'));
 }
 
 
@@ -524,7 +542,7 @@ if($ticket) {
     $ost->setPageTitle('Ticket #'.$ticket->getNumber());
     $nav->setActiveSubMenu(-1);
     $inc = 'ticket-view.inc.php';
-    if($_REQUEST['a']=='edit' && $thisstaff->canEditTickets()) 
+    if($_REQUEST['a']=='edit' && $thisstaff->canEditTickets())
         $inc = 'ticket-edit.inc.php';
     elseif($_REQUEST['a'] == 'print' && !$ticket->pdfExport($_REQUEST['psize'], $_REQUEST['notes']))
         $errors['err'] = 'Internal error: Unable to export the ticket to PDF for print.';
diff --git a/setup/doc/api.md b/setup/doc/api.md
index 7509616aa2406b842f56cacd7a6c0693b23415ca..f40d7423c68730d2a20a2df58b36a6a1b2104a17 100644
--- a/setup/doc/api.md
+++ b/setup/doc/api.md
@@ -27,3 +27,4 @@ Resources
 ---------
 
 - [Tickets](api/tickets.md)
+- [Tasks](api/tasks.md)
diff --git a/setup/doc/api/tasks.md b/setup/doc/api/tasks.md
new file mode 100644
index 0000000000000000000000000000000000000000..b40bbab21290188145b4488d34c145b1d313ea8e
--- /dev/null
+++ b/setup/doc/api/tasks.md
@@ -0,0 +1,9 @@
+Tasks
+=======
+The API supports tasks execution via the HTTP API. Cron is the only supported task as the moment.
+
+Execute Cron Job
+---------------
+
+Cron job can be executed, remotely, by making a post to `POST /api/tasks/cron`. See `scripts/rcron.php`
+
diff --git a/setup/doc/api/tickets.md b/setup/doc/api/tickets.md
index fc79b1aead833a9f4cf9fd3a214635dcba9e52fc..a8bfd39f1b0791d9bb097484d0d09364e1ef1749 100644
--- a/setup/doc/api/tickets.md
+++ b/setup/doc/api/tickets.md
@@ -9,7 +9,7 @@ Create a Ticket
 ---------------
 
 Tickets can be created in the osTicket system by sending an HTTP POST to
-`api/tickets.xml` or `api/tickets.json` depending on the format of the
+`api/tickets.xml`, `api/tickets.email` or `api/tickets.json` depending on the format of the
 request content.
 
 ### Fields ######
@@ -32,7 +32,7 @@ request content.
     *   __name__:     *required* name of the file to be attached. Multiple files
                       with the same name are allowable
     *   __type__:     Mime type of the file. Default is `text/plain`
-    *   __encoding__: Set to `base64` if content is base64 encoded 
+    *   __encoding__: Set to `base64` if content is base64 encoded
 
 ### XML Payload Example ######
 
@@ -109,6 +109,59 @@ an object or array definition, and newlines are not allowed inside strings.
 
 [rfc 2397]:     http://www.ietf.org/rfc/rfc2397.txt     "Data URLs"
 
+### Email Payload Example ######
+
+* `POST /api/tickets.email`
+
+osTicket supports both remote (over http) and local piping. Please refer to the wiki on step-by-step instruction of setting up email piping.
+
+```email
+
+MIME-Version: 1.0
+Received: by 10.194.9.167 with HTTP; Thu, 7 Feb 2013 09:01:04 -0800 (PST)
+Date: Thu, 7 Feb 2013 11:01:04 -0600
+Delivered-To: support@osticket.com
+Message-ID: <CAL4KyrgKmpYxdX+6u3HyHZ3qN5K0mU2_sdfoVu6rT8cUNn+52w@osticket.com>
+Subject: Testing
+From: Peter Rotich <peter@osticket.com>
+To: support@osticket.com
+Content-Type: multipart/mixed; boundary=047d7bfcfaf263782204d52563a5
+
+--047d7bfcfaf263782204d52563a5
+Content-Type: multipart/alternative; boundary=047d7bfcfaf263781204d52563a3
+
+--047d7bfcfaf263781204d52563a3
+Content-Type: text/plain; charset=ISO-8859-1
+
+Testing testing.
+
+--
+Peter Rotich
+http://www.osticket.com
+
+--047d7bfcfaf263781204d52563a3
+Content-Type: text/html; charset=ISO-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+<div dir=3D"ltr">Testing testing.<br clear=3D"all"><div><br></div>-- <br>Pe=
+ter Rotich<br>
+<a href=3D"http://www.osticket.com" target=3D"_blank">http://www.osticket.=
+com</a>
+</div>
+
+--047d7bfcfaf263781204d52563a3--
+--047d7bfcfaf263782204d52563a5
+Content-Type: text/plain; charset=US-ASCII; name="file.txt"
+Content-Disposition: attachment; filename="file.txt"
+Content-Transfer-Encoding: base64
+X-Attachment-Id: f_hcw5kqf60
+
+Sm9lIGRhZGR5cyBjb250ZW50Cg==
+--047d7bfcfaf263782204d52563a5--
+```
+
+Local piping can utilize `api/pipe.php` without the neeed to setup an API key.
+
 ### Response ######
 
 If successful, the server will send `HTTP/201 Created`. Otherwise, it will
diff --git a/setup/setup.inc.php b/setup/setup.inc.php
index e0691939210ec57a797070d292f9099d1b6a321d..5339d9988cf81edb0bd5de920c2456ef583efcd9 100644
--- a/setup/setup.inc.php
+++ b/setup/setup.inc.php
@@ -15,7 +15,7 @@
 **********************************************************************/
 
 #This  version - changed on every release
-define('THIS_VERSION', '1.7-RC5');
+define('THIS_VERSION', '1.7-RC6');
 
 #inits - error reporting.
 $error_reporting = E_ALL & ~E_NOTICE;
@@ -24,7 +24,7 @@ if (defined('E_STRICT')) # 5.4.0
 if (defined('E_DEPRECATED')) # 5.3.0
     $error_reporting &= ~(E_DEPRECATED | E_USER_DEPRECATED);
 
-error_reporting($error_reporting); 
+error_reporting($error_reporting);
 ini_set('magic_quotes_gpc', 0);
 ini_set('session.use_trans_sid', 0);
 ini_set('session.cache_limiter', 'nocache');
diff --git a/tickets.php b/tickets.php
index d175d49b595e8d1676df0a69b6293d47d7c10c78..d1293db874536f9d2edf836b37838a019366ea79 100644
--- a/tickets.php
+++ b/tickets.php
@@ -40,12 +40,11 @@ if($_POST && is_object($ticket) && $ticket->getId()):
 
         if(!$errors) {
             //Everything checked out...do the magic.
-            if(($msgid=$ticket->postMessage(array('message'=>$_POST['message']), 'Web'))) {
-    
-                //Upload files
-                if($cfg->allowOnlineAttachments() && $_FILES['attachments'])
-                    $ticket->uploadFiles($_FILES['attachments'], $msgid, 'M');
+            $vars = array('message'=>$_POST['message']);
+            if($cfg->allowOnlineAttachments() && $_FILES['attachments'])
+                $vars['files'] = AttachmentFile::format($_FILES['attachments'], true);
 
+            if(($msgid=$ticket->postMessage($vars, 'Web'))) {
                 $msg='Message Posted Successfully';
             } else {
                 $errors['err']='Unable to post the message. Try again';