diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php
index 5253dfe818eae2e1d8c02757e8db647352cbb598..c7612ccbcec75bd2d7e99e6ee6e52014ebf8de89 100644
--- a/include/class.mailfetch.php
+++ b/include/class.mailfetch.php
@@ -400,11 +400,11 @@ class MailFetcher {
         
         $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 +421,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);
             }
         }
 
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.ticket.php b/include/class.ticket.php
index a7a16e4644ba357db5f619ec50760de71de60a20..4e9cff307deac882b5842ea23f08b8e629fe667b 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -64,7 +64,9 @@ class Ticket {
     var $team;  //Team obj
     var $topic; //Topic obj
     var $tlock; //TicketLock obj
-    
+
+    var $thread; //Thread obj.
+
     function Ticket($id){
         $this->id = 0;
         $this->load($id);
@@ -78,11 +80,8 @@ class Ticket {
         //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 +91,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';
 
@@ -143,7 +136,10 @@ class Ticket {
         $this->tlock = null;
         $this->stats = null;
         $this->topic = null;
-        
+        $this->thread = null;
+
+        $this->getThread();
+
         return true;
     }
         
@@ -261,13 +257,13 @@ class Ticket {
         return $this->duedate;
     }
 
-    function getSLADuedate() {
+    function getSLADueDate() {
         return $this->ht['sla_duedate'];
     }
 
     function getEstDueDate() {
 
-        //Real due date 
+        //Real due date
         if(($duedate=$this->getDueDate()))
             return $duedate;
 
@@ -534,12 +530,16 @@ class Ticket {
         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 +547,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();
+        return $this->getThreadEntries(array('M', 'R'));
     }
 
-    function getThreadWithNotes() {
-        return $this->getThread(true);
+    function getThreadEntry($id) {
+        return $this->getThread()->getEntry($id);
     }
-    
-    function getThreadWithoutNotes() {
-        return $this->getThread(false);
-    }
-
-    function getThread($includeNotes=false, $order='') {
 
-        $treadtypes=array('M', 'R'); // messages and responses.
-        if($includeNotes) //Include notes??
-            $treadtypes[] = 'N';
-
-        return $this->getThreadByType($treadtypes, $order);
+    function getThreadEntries($type, $order='') {
+        return $this->getThread()->getEntries($type, $order);
     }
-        
-    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 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) {
@@ -1168,10 +1089,10 @@ class Ticket {
                 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());
 
@@ -1397,46 +1318,34 @@ class Ticket {
     }
 
     //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']);
+            if(list($msg) = split($tag, $vars['message']))
+                $vars['message'] = $msg;
 
-        # XXX: Refuse auto-response messages? (via email) XXX: No - but kill our auto-responder.
+        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;
 
-        $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($message->getId());
 
-        $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 (isset($vars['mid']))
+            $message->saveEmailInfo($vars);
 
-        if(!$alerts) return $msgid; //Our work is done...
+        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,7 +1361,7 @@ 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();
@@ -1477,8 +1386,8 @@ class Ticket {
                 $sentlist[] = $staff->getEmail();
             }
         }
-        
-        return $msgid;
+
+        return $message;
     }
 
     function postCannedReply($canned, $msgId, $alert=true) {
@@ -1492,16 +1401,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 +1428,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;
 
-        $poster = $thisstaff?$thisstaff->getName():'SYSTEM (Canned Reply)';
+        if(!$vars['poster'] && $thisstaff)
+            $vars['poster'] = $thisstaff->getName();
 
-        $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(!$vars['staffId'] && $thisstaff)
+            $vars['staffId'] = $thisstaff->getId();
 
-        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,20 +1480,20 @@ 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!!
@@ -1659,42 +1545,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 +1569,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();
 
@@ -1733,7 +1597,7 @@ class Ticket {
             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;
@@ -1743,8 +1607,8 @@ class Ticket {
                 $sentlist[] = $staff->getEmail();
             }
         }
-        
-        return $id;
+
+        return $note;
     }
 
     //Print ticket... export the ticket thread as PDF.
@@ -1757,123 +1621,25 @@ class Ticket {
         exit;
     }
 
-    //online based attached files.
-    function uploadAttachments($files, $refid, $type, $checkFileTypes=false) {
-        global $ost;
-
-        $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);
+    function delete() {
 
-        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');
@@ -1959,9 +1725,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) {
@@ -2241,8 +2012,11 @@ class Ticket {
         }
 
         $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 +2034,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 +2046,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 +2057,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
@@ -2306,25 +2078,25 @@ class Ticket {
         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']);
@@ -2340,7 +2112,7 @@ class Ticket {
         }
 
         $ticket->reload();
-        
+
         if(!$cfg->notifyONNewStaffTicket() || !isset($vars['alertuser']))
             return $ticket; //No alerts.
 
@@ -2356,8 +2128,8 @@ class Ticket {
         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();
@@ -2372,7 +2144,7 @@ class Ticket {
             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);
         }
 
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/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/scp/tickets.php b/scp/tickets.php
index 9e436f200fe08f3348423d46f0118ba101fa0813..5f0c418adf2a4710b18e340f02393ec7ce6468eb 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;
         }
@@ -470,7 +488,7 @@ if($cfg->showAnsweredTickets()) {
                                'title'=>'Answered Tickets',
                                'href'=>'tickets.php?status=answered',
                                'iconclass'=>'answeredTickets'),
-                            ($_REQUEST['status']=='answered')); 
+                            ($_REQUEST['status']=='answered'));
     }
 }
 
@@ -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.';