diff --git a/ajax.php b/ajax.php
new file mode 100644
index 0000000000000000000000000000000000000000..b40869292b81680ad136b9365793c616e0b0c3bb
--- /dev/null
+++ b/ajax.php
@@ -0,0 +1,34 @@
+<?php
+/*********************************************************************
+    ajax.php
+
+    Ajax utils for client interface.
+
+    Peter Rotich <peter@osticket.com>
+    Copyright (c)  2006-2012 osTicket
+    http://www.osticket.com
+
+    Released under the GNU General Public License WITHOUT ANY WARRANTY.
+    See LICENSE.TXT for details.
+
+    vim: expandtab sw=4 ts=4 sts=4:
+**********************************************************************/
+
+function clientLoginPage($msg='Unauthorized') {
+    Http::response(403,'Must login: '.Format::htmlchars($msg));
+    exit;
+}
+
+require('client.inc.php');
+
+if(!defined('INCLUDE_DIR'))	Http::response(500, 'Server configuration error');
+require_once INCLUDE_DIR.'/class.dispatcher.php';
+require_once INCLUDE_DIR.'/class.ajax.php';
+
+$dispatcher = patterns('',
+    url('^/config/', patterns('ajax.config.php:ConfigAjaxAPI',
+        url_get('^client', 'client')
+    ))
+);
+print $dispatcher->resolve($_SERVER['PATH_INFO']);
+?>
diff --git a/api/pipe.php b/api/pipe.php
index a35d2af2099ec60ae77edece8a1b2d0416f42773..d7d4b33be96644c484c846d47bf36179829ccdc2 100644
--- a/api/pipe.php
+++ b/api/pipe.php
@@ -100,8 +100,6 @@ if(preg_match ("[[#][0-9]{1,10}]",$var['subject'],$regs)) {
 $errors=array();
 $msgid=0;
 if(!$ticket){ //New tickets...
-    # Apply filters against the new ticket
-    $ef = new EmailFilter($var); $ef->apply($var);
     $ticket=Ticket::create($var,$errors,'email');
     if(!is_object($ticket) || $errors){
         api_exit(EX_DATAERR,'Ticket create Failed '.implode("\n",$errors)."\n\n");
@@ -118,14 +116,10 @@ if(!$ticket){ //New tickets...
     }
 }
 //Ticket created...save attachments if enabled.
-if($cfg->allowEmailAttachments()) {                   
-    if($attachments=$parser->getAttachments()){
-        //print_r($attachments);
-        foreach($attachments as $k=>$attachment){
-            if($attachment['filename'] && $cfg->canUploadFileType($attachment['filename'])) {
-                $ticket->saveAttachment(array('name' => $attachment['filename'], 'data' => $attachment['body']),$msgid,'M');
-            }
-        }
+if($cfg->allowEmailAttachments() && ($attachments=$parser->getAttachments())) {
+    foreach($attachments as $attachment) {
+        if($attachment['filename'] && $ost->isFileTypeAllowed($attachment['filename']))
+            $ticket->saveAttachment(array('name' => $attachment['filename'], 'data' => $attachment['body']), $msgid, 'M');
     }
 }
 api_exit(EX_SUCCESS);
diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css
index 88c4898a80b62ae5be6ede550948a3e217fd3a8a..b59f6af8955857d2008dff647e8e4d83d4924e72 100644
--- a/assets/default/css/theme.css
+++ b/assets/default/css/theme.css
@@ -106,10 +106,12 @@ table {
   border-spacing: 0;
 }
 
-td {
+th, td {
   vertical-align: top;
 }
 
+th { text-align: left; font-weight: normal; }
+
 h1, h2, h3, h4, h5, h6, form, fieldset {
   margin: 0;
   padding: 0;
@@ -516,17 +518,23 @@ body {
   display: block;
   float: left;
 }
+
+#ticketForm div input[type=file] {
+  border: 0;
+}
+
 #ticketForm div select, #clientLogin div select {
   display: block;
   float: left;
 }
-#ticketForm div textarea, #clientLogin div textarea {
+#ticketForm td textarea, #clientLogin div textarea {
   width: 600px;
 }
-#ticketForm div em, #clientLogin div em {
+
+#ticketForm td em, #clientLogin div em {
   color: #777;
 }
-#ticketForm div .captcha, #clientLogin div .captcha {
+#ticketForm td .captcha, #clientLogin div .captcha {
   width: 88px;
   height: 31px;
   background: #000;
@@ -534,20 +542,33 @@ body {
   float: left;
   margin-right: 20px;
 }
-#ticketForm div label.inline, #clientLogin div label.inline {
+#ticketForm td label.inline, #clientLogin div label.inline {
   width: auto;
   padding: 0 10px;
 }
-#ticketForm div label.required, #clientLogin div label.required {
+
+#ticketTable table tr th { 
+  width: 160px;
+  font-weight: normal;
+  text-align: left;
+}
+
+#ticketForm table th.required, #ticketForm table td.required, #clientLogin div label.required {
   font-weight: bold;
+  text-align: left;
 }
-#ticketForm div.captchaRow, #clientLogin div.captchaRow {
+
+
+
+#ticketForm tr.captchaRow, #clientLogin div.captchaRow {
   line-height: 31px;
 }
-#ticketForm div.captchaRow input, #clientLogin div.captchaRow input {
+
+.captchaRow td input, #clientLogin div.captchaRow input {
   position: relative;
   top: 6px;
 }
+
 #ticketForm div.error input, #clientLogin div.error input {
   border: 1px solid #a00;
 }
@@ -797,6 +818,14 @@ a.refresh {
     font-family:helvetica, arial, sans-serif;
 }
 
+
+.uploads {
+    display:inline-block;
+    padding-right:20px;
+}
+
+.uploads label { padding:3px; padding-right:10px; width: auto !important }
+
 .button:hover                          { background-color: #111; color: #fff; }
 .button:active                         { top: 1px; box-shadow:none; -moz-box-shadow:none; -webkit-box-shadow:none; }
 .button, .button:visited,
diff --git a/include/ajax.config.php b/include/ajax.config.php
index fc9fb2c3fc8622108054052828b7e6bb9a42dcd6..feb1eb4b0646f75762c554e73e55b7d7701b9680 100644
--- a/include/ajax.config.php
+++ b/include/ajax.config.php
@@ -19,12 +19,26 @@ if(!defined('INCLUDE_DIR')) die('!');
 class ConfigAjaxAPI extends AjaxController {
 
     //config info UI might need.
-    function scp_ui() {
-        global $thisstaff, $cfg;
+    function scp() {
+        global $cfg;
+
+        $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);
+    }
+
+    function client() {
+        global $cfg;
 
-        $config=array('ticket_lock_time'=>($cfg->getLockTime()*3600),
-                      'max_attachments'=>$cfg->getMaxFileUploads(),
-                      'max_file_size'=>$cfg->getMaxFileSize());
+        $config=array(
+                      'file_types'      => $cfg->getAllowedFileTypes(),
+                      'max_file_size'   => (int) $cfg->getMaxFileSize(),
+                      'max_file_uploads'=> (int) $cfg->getClientMaxFileUploads()
+                      );
 
         return $this->json_encode($config);
     }
diff --git a/include/class.ajax.php b/include/class.ajax.php
index 0240d91f83521393f31f73261562e1367b89e618..5bd26a67e13812966bd90164462c37083f52cd5f 100644
--- a/include/class.ajax.php
+++ b/include/class.ajax.php
@@ -26,11 +26,7 @@ require_once (INCLUDE_DIR.'class.api.php');
  */
 class AjaxController extends ApiController {
     function AjaxController() {
-        # Security checks first
-        # --> It is assumed that all AJAX calls will require a login. And
-        #     for now, since client logins are not yet supported, a staff
-        #     login will be required for AJAX calls.
-        $this->staffOnly();
+    
     }
     function staffOnly() {
         global $thisstaff;
diff --git a/include/class.config.php b/include/class.config.php
index e293908d61637c923ba1c9cb4120b210065b0cc2..2c9afb2448b276dc9364a36b3f16e5c62bb22f45 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -295,10 +295,15 @@ class Config {
         return $this->config['max_file_size'];
     }
 
-    function getMaxFileUploads() {
+    function getStaffMaxFileUploads() {
         return $this->config['max_staff_file_uploads'];
     }
 
+    function getClientMaxFileUploads() {
+        //TODO: change max_user_file_uploads to max_client_file_uploads
+        return $this->config['max_user_file_uploads'];
+    }
+
     function getLogLevel() {
         return $this->config['log_level'];
     }
@@ -509,6 +514,9 @@ class Config {
     
 
     /* Attachments */
+    function getAllowedFileTypes() {
+        return trim($this->config['allowed_filetypes']);
+    }
 
     function emailAttachments() {
         return ($this->config['email_attachments']);
@@ -530,22 +538,11 @@ class Config {
         return ($this->allowAttachments() && $this->config['allow_email_attachments']);
     }
 
+    /* Needed by upgrader on 1.6 and older releases upgrade - not not remove */
     function getUploadDir() {
         return $this->config['upload_dir'];
     }
     
-    //simply checking if destination dir is usable..nothing to do with permission to upload!
-    function canUploadFiles() {   
-        $dir=$this->config['upload_dir'];
-        return ($dir && is_writable($dir))?TRUE:FALSE;
-    }
-
-    function canUploadFileType($filename) {       
-        $ext = strtolower(preg_replace("/.*\.(.{3,4})$/", "$1", $filename));
-        $allowed=$this->config['allowed_filetypes']?array_map('trim',explode(',',strtolower($this->config['allowed_filetypes']))):null;
-        return ($ext && is_array($allowed) && (in_array(".$ext",$allowed) || in_array(".*",$allowed)))?TRUE:FALSE;
-    }
-
     function updateSettings($vars,&$errors) {
 
         if(!$vars || $errors)
@@ -736,10 +733,10 @@ class Config {
                 $maxfileuploads=DEFAULT_MAX_FILE_UPLOADS;
 
             if(!$vars['max_user_file_uploads'] || $vars['max_user_file_uploads']>$maxfileuploads)
-                $errors['max_user_file_uploads']='Invalid selection';
+                $errors['max_user_file_uploads']='Invalid selection. Must be less than '.$maxfileuploads;
 
             if(!$vars['max_staff_file_uploads'] || $vars['max_staff_file_uploads']>$maxfileuploads)
-                $errors['max_staff_file_uploads']='Invalid selection';
+                $errors['max_staff_file_uploads']='Invalid selection. Must be less than '.$maxfileuploads;
         }
 
         if($errors) return false;
diff --git a/include/class.faq.php b/include/class.faq.php
index 78b1a799e021f46ed4f44ba159f98568328eb4cd..2197bbac102604d334d2f59b71d7658a3b18b39b 100644
--- a/include/class.faq.php
+++ b/include/class.faq.php
@@ -19,11 +19,11 @@ class FAQ {
 
     var $id;
     var $ht;
+
     var $category;
+    var $attachments;
 
     function FAQ($id) {
-
-
         $this->id=0;
         $this->ht = array();
         $this->load($id);
@@ -44,6 +44,7 @@ class FAQ {
         $this->ht = db_fetch_array($res);
         $this->ht['id'] = $this->id = $this->ht['faq_id'];
         $this->category = null;
+        $this->attachments = array();
 
         return true;
     }
@@ -134,7 +135,7 @@ class FAQ {
 
         if($ids) {
             $topics = $this->getHelpTopicsIds();
-            foreach($ids as $k=>$id) {
+            foreach($ids as $id) {
                 if($topics && in_array($id,$topics)) continue;
                 $sql='INSERT IGNORE INTO '.FAQ_TOPIC_TABLE
                     .' SET faq_id='.db_input($this->getId())
@@ -158,6 +159,20 @@ class FAQ {
             return false;
 
         $this->updateTopics($vars['topics']);
+                    
+        //Delete removed attachments.
+        $keepers = $vars['files']?$vars['files']:array();
+        if(($attachments = $this->getAttachments())) {
+            foreach($attachments as $file) {
+                if($file['id'] && !in_array($file['id'], $keepers))
+                    $this->deleteAttachment($file['id']);
+            }
+        }
+
+        //Upload new attachments IF any.
+        if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments'])))
+            $this->uploadAttachments($files);
+
         $this->reload();
 
         return true;
@@ -261,10 +276,19 @@ class FAQ {
     /* ------------------> Static methods <--------------------- */
    
     function add($vars, &$errors) {
-        if(($id=self::create($vars, $errors)) && ($faq=self::lookup($id)))
+        if(!($id=self::create($vars, $errors)))
+            return false;
+
+        if(($faq=self::lookup($id))) {
             $faq->updateTopics($vars['topics']);
+               
+            if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments'])))
+                $faq->uploadAttachments($files);
 
-        return$faq;
+            $faq->reload();
+        }
+            
+        return $faq;
     }
 
     function create($vars, &$errors) {   
diff --git a/include/class.file.php b/include/class.file.php
index 891a8b5bfb851415c1ab3cbe804284ae5217f229..c27c9fdd07b5aa7477336c009af2fdb75605233c 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -141,7 +141,7 @@ class AttachmentFile {
     /* Function assumes the files types have been validated */
     function upload($file) {
         
-        if(!$file['name'] || !is_uploaded_file($file['tmp_name']))
+        if(!$file['name'] || $file['error'] || !is_uploaded_file($file['tmp_name']))
             return false;
 
         $info=array('type'=>$file['type'],
@@ -160,13 +160,6 @@ class AttachmentFile {
             $file['hash']=MD5(MD5($file['data']).time());
         if(!$file['size'])
             $file['size']=strlen($file['data']);
-
-
-        
-        //TODO: Do chunked INSERTs - 
-        if(($mps=db_get_variable('max_allowed_packet')) && $file['size']>($mps*0.7)) {
-            @db_set_variable('max_allowed_packet',$file['size']+$mps);
-        }
         
         $sql='INSERT INTO '.FILE_TABLE.' SET created=NOW() '
             .',type='.db_input($file['type'])
@@ -178,11 +171,15 @@ class AttachmentFile {
             return false;
 
         foreach (str_split($file['data'], 1024*100) as $chunk) {
-            if (!db_query('UPDATE '.FILE_TABLE.' SET filedata = CONCAT(filedata,'
-                    .db_input($chunk).') WHERE id='.db_input($id)))
-                # Remove partially uploaded file contents
+            $sql='UPDATE '.FILE_TABLE
+                .' SET filedata = CONCAT(filedata,'.db_input($chunk).')'
+                .' WHERE id='.db_input($id);
+            if(!db_query($sql)) {
+                db_query('DELETE FROM '.FILE_TABLE.' WHERE id='.db_input($id).' LIMIT 1');
                 return false;
+            }
         }
+
         return $id;
     }
 
diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php
index 248a6ba81b6f52d9397414c1a08db92e5b88336e..fb17d8a26ac719854a683ba778c287321ddefea9 100644
--- a/include/class.mailfetch.php
+++ b/include/class.mailfetch.php
@@ -323,11 +323,13 @@ class MailFetcher {
     }
 
     function saveAttachments($ticket,$mid,$part,$index=0) {
-        global $cfg;
+        global $ost;
 
         if($part && $part->ifdparameters && ($filename=$part->dparameters[0]->value)){ //attachment
             $index=$index?$index:1;
-            if($ticket && $cfg->canUploadFileType($filename) && $cfg->getMaxFileSize()>=$part->bytes) {
+            if($ticket 
+                    && $ost->isFileTypeAllowed($filename) 
+                    && $ost->getConfig()->getMaxFileSize()>=$part->bytes) {
                 //extract the attachments...and do the magic.
                 $data=$this->decode($part->encoding, imap_fetchbody($this->mbox,$mid,$index));
                 $ticket->saveAttachment(array('name'=>$filename, 'data'=>$data),$ticket->getLastMsgId(),'M');
diff --git a/include/class.osticket.php b/include/class.osticket.php
index 5f75d0fd1fe6ac28feedeea227ef3ad6dc3e7429..033ec0f82accf22a97b855b39c12c4166fe580af 100644
--- a/include/class.osticket.php
+++ b/include/class.osticket.php
@@ -109,6 +109,45 @@ class osTicket {
 
         return false;
     }
+    
+    function isFileTypeAllowed($file, $mimeType='') {
+       
+        if(!$file || !($allowedFileTypes=$this->getConfig()->getAllowedFileTypes()))
+            return false;
+
+        //Return true if all file types are allowed (.*)
+        if(trim($allowedFileTypes)=='.*') return true;
+
+        $allowed = array_map('trim', explode(',', strtolower($allowedFileTypes)));
+        $filename = is_array($file)?$file['name']:$file;
+
+        $ext = strtolower(preg_replace("/.*\.(.{3,4})$/", "$1", $filename));
+
+        //TODO: Check MIME type - file ext. shouldn't be solely trusted.
+
+        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) {
+       
+        $errors=0;
+        foreach($files as &$file) {
+            if(!$this->isFileTypeAllowed($file))
+                $file['error']='Invalid file type for '.$file['name'];
+            elseif($file['size']>$this->getConfig()->getMaxFileSize())
+                $file['error']=sprintf('File (%s) is too big. Maximum of %s allowed',
+                        $file['name'], Format::file_size($this->getConfig()->getMaxFileSize()));
+            elseif(!$file['error'] && !is_uploaded_file($file['tmp_name']))
+                $file['error']='Invalid or bad upload POST';
+
+            if($file['error']) $errors++;
+        }
+
+        return (!$errors);
+    }
 
     function addExtraHeader($header) {
         $this->headers[md5($header)] = $header;
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 42bb1737840d4c489f92ec128b918732d2a1ac77..071dda44f77a7ab4624c7408fa59ac85b737fb40 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -624,7 +624,7 @@ class Ticket{
             /* 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('(<i>%s</i>)',Format::file_size($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);
@@ -1372,7 +1372,7 @@ class Ticket{
     }
 
     /* public */ 
-    function postReply($vars, $files, $errors, $alert = true) {
+    function postReply($vars, $errors, $alert = true) {
         global $thisstaff,$cfg;
 
         if(!$thisstaff || !$thisstaff->isStaff() || !$cfg) return 0;
@@ -1380,7 +1380,7 @@ class Ticket{
         if(!$vars['msgId'])
             $errors['msgId'] ='Missing messageId - internal error';
         if(!$vars['response'])
-            $errors['response'] = 'Resonse message required';
+            $errors['response'] = 'Response message required';
 
         if($errors) return 0;
 
@@ -1401,20 +1401,17 @@ class Ticket{
             $this->setStatus($vars['reply_ticket_status']);
 
         /* We can NOT recover from attachment related failures at this point */
-        //upload files.
-        $attachments = $uploads = array();
-        //Web based upload..
-        if($files && is_array($files) && ($files=Format::files($files)))
-            $attachments=array_merge($attachments,$files);
+        $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']))
-            $attachments=array_merge($attachments,$vars['cannedattachments']);
-
-        
-        //Upload attachments -ids used on outgoing emails are returned.
-        if($attachments)
-            $uploads = $this->uploadAttachments($attachments, $respId,'R');
+        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();
@@ -1448,7 +1445,7 @@ class Ticket{
                 $body ="\n$tag\n\n".$body;
 
             //Set attachments if emailing.
-            $attachments =($cfg->emailAttachments() && $uploads)?$this->getAttachments($respId,'R'):array();
+            $attachments =($cfg->emailAttachments() && $attachments)?$this->getAttachments($respId,'R'):array();
             //TODO: setup  5 param (options... e.g mid trackable on replies)
             $email->send($this->getEmail(), $subj, $body, $attachments);
         }
@@ -1566,14 +1563,29 @@ class Ticket{
 
     //online based attached files.
     function uploadAttachments($files, $refid, $type) {
+        global $ost;
 
         $uploaded=array();
         foreach($files as $file) {
-            if(($fileId=is_numeric($file)?$file:AttachmentFile::upload($file)) && is_numeric($fileId))
-                if($this->saveAttachment($fileId, $refid, $type))
-                    $uploaded[]=$fileId;
-        }
+            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->postNote('File Upload Error', $error, false);
+               
+                $ost->logDebug('File Upload Error (Ticket #'.$this->getExtId().')', $error);
+            }
+            
+        }
+        
         return $uploaded;
     }
 
@@ -1964,16 +1976,15 @@ class Ticket{
             return null;
 
         /* -------------------- POST CREATE ------------------------ */
-        $dept = $ticket->getDept();
-     
+        
         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'); 
             //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['message'],$source,$vars['mid'],$vars['header'],true);
 
@@ -2008,29 +2019,28 @@ class Ticket{
             $autorespond=false;
         }
 
-        // If a canned-response is immediately queued for this ticket,
-        // disable the autoresponse
-        if ($vars['cannedResponseId'])
-            $autorespond=false;
-
-        /***** See if we need to send some alerts ****/
-
-        $ticket->onNewTicket($vars['message'], $autorespond, $alertstaff);
-
         if ($vars['cannedResponseId']
                 && ($canned = Canned::lookup($vars['cannedResponseId']))
                 && $canned->isEnabled()) {
             $files = array();
             foreach ($canned->getAttachments() as $file)
                 $files[] = $file['id'];
-            $ticket->postReply(array(
-                    'msgId'     => $msgid,
-                    'response'  =>
-                        $ticket->replaceTemplateVars($canned->getResponse()),
-                    'cannedattachments' => $files
-                ), null, $errors, true);
+            $ticket->postReply(
+                    array(
+                        'msgId'     => $msgid,
+                        'response'  =>
+                            $ticket->replaceTemplateVars($canned->getResponse()),
+                        'cannedattachments' => $files
+                    ),$errors, true);
+                    
+            // If a canned-response is immediately queued for this ticket,
+            // disable the autoresponse
+            $autorespond=false;
         }
 
+        /***** See if we need to send some alerts ****/
+        $ticket->onNewTicket($vars['message'], $autorespond, $alertstaff);
+
         /************ check if the user JUST reached the max. open tickets limit **********/
         if($cfg->getMaxOpenTickets()>0
                     && ($client=$ticket->getClient())
@@ -2046,7 +2056,7 @@ class Ticket{
         return $ticket;
     }
 
-    function open($vars, $files, &$errors) {
+    function open($vars, &$errors) {
         global $thisstaff,$cfg;
 
         if(!$thisstaff || !$thisstaff->canCreateTickets()) return false;
@@ -2068,7 +2078,7 @@ class Ticket{
         // post response - if any
         if($vars['response']) {
             $vars['response']=$ticket->replaceTemplateVars($vars['response']);
-            if(($respId=$ticket->postReply($vars,  $files, $errors, false))) {
+            if(($respId=$ticket->postReply($vars, $errors, false))) {
                 //Only state supported is closed on response
                 if(isset($vars['ticket_state']) && $thisstaff->canCloseTickets())
                     $ticket->setState($vars['ticket_state']);
diff --git a/include/client/header.inc.php b/include/client/header.inc.php
index 969e9e66b8d977256e8087fc8cba2dadebb8fc62..da763a7bbf7b1e5b9481b8d26a0dfca2c79a9997 100644
--- a/include/client/header.inc.php
+++ b/include/client/header.inc.php
@@ -12,8 +12,9 @@ header("Content-Type: text/html; charset=UTF-8\r\n");
     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
     <link rel="stylesheet" href="<?php echo ASSETS_PATH; ?>css/theme.css" media="screen">
     <link rel="stylesheet" href="<?php echo ASSETS_PATH; ?>css/print.css" media="print">
-    <script src="./js/jquery-1.7.2.min.js"></script>
-    <script src="./js/osticket.js"></script>
+    <script src="<?php echo ROOT_PATH; ?>js/jquery-1.7.2.min.js"></script>
+    <script src="<?php echo ROOT_PATH; ?>js/jquery.multifile.js"></script>
+    <script src="<?php echo ROOT_PATH; ?>js/osticket.js"></script>
 </head>
 <body>
     <div id="container">
diff --git a/include/client/open.inc.php b/include/client/open.inc.php
index b85c1e337eac2894c2246a94c623cea6717b42d2..42cf10c92d6eb3a45fdbf3775fc3cde7103d247b 100644
--- a/include/client/open.inc.php
+++ b/include/client/open.inc.php
@@ -13,99 +13,111 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):$info;
 <h1>Open a New Ticket</h1>
 <p>Please fill in the form below to open a new ticket.</p>
 <form id="ticketForm" method="post" action="open.php" enctype="multipart/form-data">
-    <?php csrf_token(); ?>
-    <input type="hidden" name="a" value="open">
-    <div>
-        <label for="name" class="required">Full Name:</label>
-        <?php
-        if($thisclient && $thisclient->isValid()) {
-            echo $thisclient->getName();
-        } else { ?>
-        <input id="name" type="text" name="name" size="30" value="<?php echo $info['name']; ?>">
-        <font class="error">*&nbsp;<?php echo $errors['name']; ?></font>
-        <?php
-        } ?>
-    </div>
-    <div>
-        <label for="email" class="required">Email Address:</label>
-        <?php
-        if($thisclient && $thisclient->isValid()) { 
-            echo $thisclient->getEmail();
-        } else { ?>
-        <input id="email" type="text" name="email" size="30" value="<?php echo $info['email']; ?>">
-        <font class="error">*&nbsp;<?php echo $errors['email']; ?></font>
-        <?php
-        } ?>
-    </div>
-    <div>
-        <label for="phone">Telephone:</label>
-        <input id="phone" type="text" name="phone" size="17" value="<?php echo $info['phone']; ?>">
-        <label for="ext" class="inline">Ext.:</label>
-        <input id="ext" type="text" name="phone_ext" size="3" value="<?php echo $info['phone_ext']; ?>">
-        <font class="error">&nbsp;<?php echo $errors['phone']; ?>&nbsp;&nbsp;<?php echo $errors['phone_ext']; ?></font>
-    </div>
-    <br>
-    <div>
-        <label for="topicId" class="required">Help Topic:</label>
-        <select id="topicId" name="topicId">
-            <option value="" selected="selected">&mdash; Select a Help Topics &mdash;</option>
+  <?php csrf_token(); ?>
+  <input type="hidden" name="a" value="open">
+  <table width="800" cellpadding="1" cellspacing="0" border="0">
+    <tr>
+        <th class="required" width="160">Full Name:</th>
+        <td>
             <?php
-            if($topics=Topic::getPublicHelpTopics()) {
-                foreach($topics as $id =>$name) {
-                    echo sprintf('<option value="%d" %s>%s</option>',
-                            $id, ($info['topicId']==$id)?'selected="selected"':'', $name);
-                }
+            if($thisclient && $thisclient->isValid()) {
+                echo $thisclient->getName();
             } else { ?>
-                <option value="0" >General Inquiry</option>
-            <?php } ?>
-        </select>
-        <font class="error">*&nbsp;<?php echo $errors['topicId']; ?></font>
-    </div>
-    <div>
-        <label for="subject" class="required">Subject:</label>
-        <input id="subject" type="text" name="subject" size="40" value="<?php echo $info['subject']; ?>">
-        <font class="error">*&nbsp;<?php echo $errors['subject']; ?></font>
-    </div>
-    <div>
-        <label for="msg" class="required">Message:</label>
-        <span id="msg">
-        <em>Please provide as much details as possible so we can best assist you.</em> <font class="error">*&nbsp;<?php echo $errors['message']; ?></font></span>
-    </div>
-    <div>
-        <label for="message" class="required">&nbsp;</label>
-        <textarea id="message" cols="60" rows="8" name="message"><?php echo $info['message']; ?></textarea>
-    </div>
+                <input id="name" type="text" name="name" size="30" value="<?php echo $info['name']; ?>">
+                <font class="error">*&nbsp;<?php echo $errors['name']; ?></font>
+            <?php
+            } ?>
+        </td>
+    </tr>
+    <tr>
+        <th class="required" width="160">Email Address:</th>
+        <td>
+            <?php
+            if($thisclient && $thisclient->isValid()) { 
+                echo $thisclient->getEmail();
+            } else { ?>
+                <input id="email" type="text" name="email" size="30" value="<?php echo $info['email']; ?>">
+                <font class="error">*&nbsp;<?php echo $errors['email']; ?></font>
+            <?php
+            } ?>
+        </td>
+    </tr>
+    <tr>
+        <th>Telephone:</th>
+        <td>
+
+            <input id="phone" type="text" name="phone" size="17" value="<?php echo $info['phone']; ?>">
+            <label for="ext" class="inline">Ext.:</label>
+            <input id="ext" type="text" name="phone_ext" size="3" value="<?php echo $info['phone_ext']; ?>">
+            <font class="error">&nbsp;<?php echo $errors['phone']; ?>&nbsp;&nbsp;<?php echo $errors['phone_ext']; ?></font>
+        </td>   
+    </tr>
+    <tr><td colspan=2>&nbsp;</td></tr>
+    <tr>
+        <td class="required">Help Topic:</td>
+        <td>
+            <select id="topicId" name="topicId">
+                <option value="" selected="selected">&mdash; Select a Help Topics &mdash;</option>
+                <?php
+                if($topics=Topic::getPublicHelpTopics()) {
+                    foreach($topics as $id =>$name) {
+                        echo sprintf('<option value="%d" %s>%s</option>',
+                                $id, ($info['topicId']==$id)?'selected="selected"':'', $name);
+                    }
+                } else { ?>
+                    <option value="0" >General Inquiry</option>
+                <?php
+                } ?>
+            </select>
+            <font class="error">*&nbsp;<?php echo $errors['topicId']; ?></font>
+        </td>
+    </tr>
+    <tr>
+        <td class="required">Subject:</td>
+        <td>
+            <input id="subject" type="text" name="subject" size="40" value="<?php echo $info['subject']; ?>">
+            <font class="error">*&nbsp;<?php echo $errors['subject']; ?></font>
+        </td>
+    </tr>
+    <tr>
+        <td class="required">Message:</td>
+        <td>
+            <div><em>Please provide as much details as possible so we can best assist you.</em> <font class="error">*&nbsp;<?php echo $errors['message']; ?></font></div>
+            <textarea id="message" cols="60" rows="8" name="message"><?php echo $info['message']; ?></textarea>
+        </td>
+    </tr>
+
     <?php if(($cfg->allowOnlineAttachments() && !$cfg->allowAttachmentsOnlogin())
             || ($cfg->allowAttachmentsOnlogin() && ($thisclient && $thisclient->isValid()))) { ?>
-     <div>
-        <label for="attachments">Attachments:</label>
-        <span id="uploads"></span>
-        <input type="file" class="multifile" name="attachments[]" id="attachments" size="30" value="" />
-        <font class="error">&nbsp;<?php echo $errors['attachments']; ?></font>
-    </div>                                                                
+    <tr>
+        <td>Attachments:</td>
+        <td>
+            <div class="uploads"></div><br>
+            <input type="file" class="multifile" name="attachments[]" id="attachments" size="30" value="" />
+            <font class="error">&nbsp;<?php echo $errors['attachments']; ?></font>
+        </td>
+    </tr>
+    <tr><td colspan=2>&nbsp;</td></tr>
     <?php } ?>
     <?php
     if($cfg->allowPriorityChange() && ($priorities=Priority::getPriorities())) { ?>
-    <div>
-        <label for="priority">Ticket Priority:</label>
-        <select id="priority" name="priorityId">
-            <?php
-                if(!$info['priorityId'])
-                    $info['priorityId'] = $cfg->getDefaultPriorityId(); //System default.
-                foreach($priorities as $id =>$name) {
-                    echo sprintf('<option value="%d" %s>%s</option>',
-                                    $id, ($info['priorityId']==$id)?'selected="selected"':'', $name);
+    <tr>
+        <td>Ticket Priority:</td>
+        <td>
+            <select id="priority" name="priorityId">
+                <?php
+                    if(!$info['priorityId'])
+                        $info['priorityId'] = $cfg->getDefaultPriorityId(); //System default.
+                    foreach($priorities as $id =>$name) {
+                        echo sprintf('<option value="%d" %s>%s</option>',
+                                        $id, ($info['priorityId']==$id)?'selected="selected"':'', $name);
                         
-                }
-            ?>
-
-                
-                
-        </select>
-        
-        <font class="error">&nbsp;<?php echo $errors['priorityId']; ?></font>
-        
-    </div>
+                    }
+                ?>
+            </select>
+            <font class="error">&nbsp;<?php echo $errors['priorityId']; ?></font>
+        </td>
+    </tr>
     <?php
     }
     ?>
@@ -114,20 +126,23 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):$info;
         if($_POST && $errors && !$errors['captcha'])
             $errors['captcha']='Please re-enter the text again';
         ?>
-    <br>
-    <div class="captchaRow">
-        <label for="captcha" class="required">CAPTCHA Text:</label>
-        <span class="captcha"><img src="captcha.php" border="0" align="left"></span>
-        <input id="captcha" type="text" name="captcha" size="6">
-        <em>Enter the text shown on the image.</em>
-        <font class="error">*&nbsp;<?php echo $errors['captcha']; ?></font>
-    </div>
+    <tr class="captchaRow">
+        <td class="required">CAPTCHA Text:</td>
+        <td>
+            <span class="captcha"><img src="captcha.php" border="0" align="left"></span>
+            &nbsp;&nbsp;
+            <input id="captcha" type="text" name="captcha" size="6">
+            <em>Enter the text shown on the image.</em>
+            <font class="error">*&nbsp;<?php echo $errors['captcha']; ?></font>
+        </td>
+    </tr>
     <?php
     } ?>
-    <br>
-    <p style="padding-left:150px;">
+    <tr><td colspan=2>&nbsp;</td></tr>
+  </table>
+  <p style="padding-left:150px;">
         <input type="submit" value="Create Ticket">
         <input type="reset" value="Reset">
         <input type="button" value="Cancel" onClick='window.location.href="index.php"'>
-    </p>
+  </p>
 </form>
diff --git a/include/client/view.inc.php b/include/client/view.inc.php
index 5aba461fc8f8a129138ed406d1319bb66d1ca9cb..ccea4c927cfdf31793ae325f79572a743fa35f26 100644
--- a/include/client/view.inc.php
+++ b/include/client/view.inc.php
@@ -122,7 +122,7 @@ if($ticket->getThreadCount() && ($thread=$ticket->getClientThread())) {
                 <div class="uploads">
                 </div>
                 <div class="file_input">
-                    <input type="file" name="attachments[]" size="30" value="" />
+                    <input class="multifile" type="file" name="attachments[]" size="30" value="" />
                 </div>
             </td>
         </tr>
diff --git a/include/staff/department.inc.php b/include/staff/department.inc.php
index 2c1ff86ca75f3b6467e44fa3b86fe98652a808e7..b900087331721347ee1b1c57dd37f43e274ed2d2 100644
--- a/include/staff/department.inc.php
+++ b/include/staff/department.inc.php
@@ -206,10 +206,10 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
         </tr>
         <tr>
             <th colspan="2">
-                <em><strong>Department Access</strong>: Check all groups allowed to access department.</em>
+                <em><strong>Department Access</strong>: Check all groups allowed to access this department.</em>
             </th>
         </tr>
-        <tr><td colspan=2><em>Primary department members and manager will always have access regarless of group selection or assignment.</em></td></tr>
+        <tr><td colspan=2><em>Department manager and primary members will always have access independent of group selection or assignment.</em></td></tr>
         <?php
          $sql='SELECT group_id, group_name, count(staff.staff_id) as members '
              .' FROM '.GROUP_TABLE.' grp '
diff --git a/include/staff/faq.inc.php b/include/staff/faq.inc.php
index 8882da2fa817cf1e5000d4700fab0cc2b9bb0a19..fb87156b97d5f33254efcce57b24b7cfef638c7d 100644
--- a/include/staff/faq.inc.php
+++ b/include/staff/faq.inc.php
@@ -23,6 +23,7 @@ if($faq){
 $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
 ?>
 <form action="faq.php?<?php echo $qstr; ?>" method="post" id="save" enctype="multipart/form-data">
+ <?php csrf_token(); ?>
  <input type="hidden" name="do" value="<?php echo $action; ?>">
  <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>">
  <input type="hidden" name="id" value="<?php echo $info['id']; ?>">
@@ -90,7 +91,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 <div><b>Attachments</b> (optional) <font class="error">&nbsp;<?php echo $errors['files']; ?></font></div>
                 <?php
                 if($faq && ($files=$faq->getAttachments())) {
-                    echo '<div id="faq_attachments"><span class="faded">Uncheck to delete the attachment on submit</span><br>';
+                    echo '<div class="faq_attachments"><span class="faded">Uncheck to delete the attachment on submit</span><br>';
                     foreach($files as $file) {
                         $hash=$file['hash'].md5($file['id'].session_id().$file['hash']);
                         echo sprintf('<label><input type="checkbox" name="files[]" id="f%d" value="%d" checked="checked">
@@ -99,14 +100,12 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                     }
                     echo '</div><br>';
                 }
-                //TODO: add a setting on admin panel
-                if(count($files)<5) {
                 ?>
-                <div>
-                    <input type="file" name="attachments[]" value=""/>
+                <div class="faded">Select files to upload.</div>
+                <div class="uploads"></div>
+                <div class="file_input">
+                    <input type="file" class="multifile" name="attachments[]" size="30" value="" />
                 </div>
-                <?}?>
-                <div class="faded">You can upload up to 5 attachments.</div>
             </td>
         </tr>
         <?php
diff --git a/include/staff/header.inc.php b/include/staff/header.inc.php
index 251de2b4f8ec4cdd8822668b806bde81b6a7e57e..1f4a3da546c99b645d81c73597cc1c47cc7927c6 100644
--- a/include/staff/header.inc.php
+++ b/include/staff/header.inc.php
@@ -12,6 +12,7 @@
     <![endif]-->
     <script type="text/javascript" src="../js/jquery-1.7.2.min.js"></script>
     <script type="text/javascript" src="../js/jquery-ui-1.8.18.custom.min.js"></script>
+    <script type="text/javascript" src="../js/jquery.multifile.js"></script>
     <script type="text/javascript" src="./js/tips.js"></script>
     <script type="text/javascript" src="./js/nicEdit.js"></script>
     <script type="text/javascript" src="./js/bootstrap-typeahead.js"></script>
diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php
index a61c7bfcbd2396bfe5e20d6bc0f2abb827d4b009..db3aca84018b1d0744a46363243a6f111f3bfdba 100644
--- a/include/staff/ticket-open.inc.php
+++ b/include/staff/ticket-open.inc.php
@@ -162,7 +162,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
             <?php
             if($cfg->allowAttachments()) { ?>
                 <br><em><b>Attachments:</b> Response required when files are attached.</em>
-                <div id="canned_attachments">
+                <div class="canned_attachments">
                     <?php
                     if($info['cannedattachments']) {
                         foreach($info['cannedattachments'] as $k=>$id) {
@@ -175,7 +175,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                     }
                     ?>
                 </div>
-                <div id="uploads"></div>
+                <div class="uploads"></div>
                 <div class="file_input">
                     <input type="file" class="multifile" name="attachments[]" size="30" value="" />
                 </div>
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index 633fe2f27d0eea17965c758b932c8650c7c9b768..a58206963acf4deb36fd48bf17d80c2ea63020aa 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -334,9 +334,9 @@ if(!$cfg->showNotesInline()) { ?>
                     <label for="attachment">Attachments:</label>
                 </td>
                 <td width="765" id="reply_form_attachments" class="attachments">
-                    <div id="canned_attachments">
+                    <div class="canned_attachments">
                     </div>
-                    <div id="uploads">
+                    <div class="uploads">
                     </div>
                     <div class="file_input">
                         <input type="file" class="multifile" name="attachments[]" size="30" value="" />
diff --git a/js/jquery.multifile.js b/js/jquery.multifile.js
new file mode 100644
index 0000000000000000000000000000000000000000..3ae6835d2f233f71073709e25560163d7550f733
--- /dev/null
+++ b/js/jquery.multifile.js
@@ -0,0 +1,178 @@
+/*********************************************************************
+    jquery.multifile.js
+
+    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.
+
+    NOTE:
+    * Files are not uploaded until the form is submitted
+    * Server side file type validation is a MUST
+    * Plugin doesn't take into account PHP related limitations e.g max uploads + max size.
+
+    Peter Rotich <peter@osticket.com>
+    Copyright (c) 2006-2012 osTicket
+    http://www.osticket.com
+
+    Credits:
+    The plugin borrows heavily from a plugin by Rocky Meza @ fusionbox
+    https://github.com/fusionbox/jquery-multifile
+
+    vim: expandtab sw=4 ts=4 sts=4:
+**********************************************************************/
+
+;(function($, global, undefined) {
+        
+    $.fn.multifile = function(options) {
+        var container = null;
+        var options = $.extend({}, $.fn.multifile.defaults, options);
+
+        options.allowedFileTypes = $.map(options.file_types.toLowerCase().split(','), $.trim);
+        options.inputTemplate = options.inputTemplate || $.fn.multifile.inputTemplate;
+
+        container = options.container || null;
+
+
+        return this.each(function() {
+
+            var settings = options;
+            var $container
+                
+            , addInput = function(event) {
+            
+                var $this = $(this)
+                , fObj = $(this).closest('form')
+                , new_input = $.fn.multifile.cloneInput($this)
+                , file = $.fn.multifile.getFileObject(this);
+
+                if(fObj.data('files') == undefined)
+                    fObj.data('files', 0);
+
+                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)) {
+                    $this.hide();
+                    
+                    settings
+                    .inputTemplate(file)
+                    .appendTo($container)
+                    .on('click', 'input',  bindRemoveInput($this, file));
+
+                    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);
+                }
+        
+            }
+      
+            , bindRemoveInput = function($input, file) {
+
+                return function(event) {
+
+                    event.preventDefault();
+           
+                    if(confirm('Are you sure you want to remove '+file.name+'?')) {
+                        var fObj = $(this).closest('form');
+
+                        fObj.data('files', fObj.data('files')-file.count);
+                        if(fObj.data('files')<settings.max_uploads && (fObj.data('files')+file.count)>=settings.max_uploads)
+                            $input.after($.fn.multifile.cloneInput($input).show());
+                        
+                        $input.remove();
+                        $(this).parent().remove();
+                    }
+
+                    return false;
+                };
+        
+            };
+    
+            if ( container ) {
+                if ( typeof container == 'string' ) 
+                    $container = $(container, $(this).closest('form'));
+                else
+                    $container = container;
+            } else {
+                $container = $('<div class="uploads" />');
+                $(this).after($container);
+            }
+
+            $(this).bind('change.multifile', addInput);
+  
+        });
+  };
+
+  $.fn.multifile.inputTemplate = function(file) {
+    return $('<label style="padding-right:5px;"><input type="checkbox" name="uploads[]" value="' + file.name + '" checked="checked"> ' + file.name + '</label>');
+  };
+
+  $.fn.multifile.checkFileTypes = function(file, allowedFileTypes) {
+     
+      //Wildcard.
+      if(allowedFileTypes[0]=='.*')
+          return true;
+
+      var filenames = $.map(file.name.toLowerCase().split(','), $.trim);
+      for (var i = 0, _len = filenames.length; i < _len; i++)
+          if(filenames[i] && $.inArray('.'+filenames[i].split('.').pop(), allowedFileTypes) == -1)
+              return false;
+
+      return true;
+  };
+
+  //Clone file input and clear the value without triggering a warning!
+  $.fn.multifile.cloneInput = function(input) {
+
+      var $clone = input.clone(true);
+                      
+      if ($.browser.msie) {
+          $clone.replaceWith(function () { return $(this).clone(true); });
+      } else {
+          $clone.val('');
+      }
+
+      return $clone;
+  }
+
+  //Get file object 
+  $.fn.multifile.getFileObject = function(input) {
+    var file = {};
+
+    file.count = 1; 
+    // check for HTML5 FileList support
+    if ( !!global.FileList ) {
+      if ( input.files.length == 1 )
+        file.name = input.files[0].name;
+      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.name += ', ' + input.files[i].name;
+
+        file.count = i;
+      }
+    } else {
+      file.name = input.value;
+    }
+
+    return file;
+  };
+
+  //Default options 
+  $.fn.multifile.defaults = { 
+                              max_uploads: 1,
+                              file_types: '.*'
+                            };
+})(jQuery, this);
diff --git a/js/osticket.js b/js/osticket.js
index a809e6f2f794a0534096c69b2a210428424d703d..4057b04b84a25e0cc7d2e249e17cac5efaa5c9e5 100644
--- a/js/osticket.js
+++ b/js/osticket.js
@@ -1 +1,55 @@
-//Nothing for now...
+/* 
+   osticket.js
+   Copyright (c) osTicket.com
+ */
+
+$(document).ready(function(){
+
+    $("input:not(.dp):visible:enabled:first").focus();
+    $('table.list tbody tr:odd').addClass('odd');
+
+    $("form#save :input").change(function() {
+        var fObj = $(this).closest('form');
+        if(!fObj.data('changed')){
+            fObj.data('changed', true);
+            $('input[type=submit]', fObj).css('color', 'red');
+            $(window).bind('beforeunload', function(e) {
+                return 'Are you sure you want to leave? Any changes or info you\'ve entered will be discarded!';
+             });
+        }
+       });
+
+    $("form#save :input[type=reset]").click(function() {
+        var fObj = $(this).closest('form');
+        if(fObj.data('changed')){
+            $('input[type=submit]', fObj).removeAttr('style');
+            $('label', fObj).removeAttr('style');
+            $('label', fObj).removeClass('strike');
+            fObj.data('changed', false);
+            $(window).unbind('beforeunload');
+        }
+       });
+
+    $('form#save').submit(function() {
+        $(window).unbind('beforeunload');
+        return true;
+       });
+
+    /* Get config settings from the backend */
+    var $config = null;
+    $.ajax({
+        url: "ajax.php/config/client",
+        dataType: 'json',
+        async: false,
+        success: function (config) {
+            $config = config;
+            }
+        });
+     
+    /* Multifile uploads */
+     $('.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:".*"
+       });
+});
diff --git a/open.php b/open.php
index 11d3a0a9d3397d64941886169a5fa2892ea663a1..7bdc8fc8ffccfee37516c5ec60c0e1d16119df75 100644
--- a/open.php
+++ b/open.php
@@ -32,6 +32,14 @@ if($_POST):
     //Ticket::create...checks for errors..
     if(($ticket=Ticket::create($_POST,$errors,SOURCE))){
         $msg='Support ticket request created';
+        //Upload attachments...         
+        if($cfg->allowOnlineAttachments()
+                && $_FILES['attachments']
+                && ($files=Format::files($_FILES['attachments']))) {
+            $ost->validateFileUploads($files); //Validator sets errors - if any.
+            $ticket->uploadAttachments($files, $ticket->getLastMsgId(), 'M');
+        }
+
         //Logged in...simply view the newly created ticket.
         if($thisclient && $thisclient->isValid()) {
             if(!$cfg->showRelatedTickets())
diff --git a/scp/ajax.php b/scp/ajax.php
index 8ffa515402f907b6525b1f74786bf6e1a0378c98..259413dab83034c23c52ca5e6017bf9b5d9b9719 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -28,7 +28,7 @@ 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? 
-if(!defined('INCLUDE_DIR'))	Http::response(500,'config error');
+if(!defined('INCLUDE_DIR'))	Http::response(500, 'Server configuration error');
 
 require_once INCLUDE_DIR.'/class.dispatcher.php';
 require_once INCLUDE_DIR.'/class.ajax.php';
@@ -43,7 +43,7 @@ $dispatcher = patterns('',
         url_get('^ticket_variables', 'ticket_variables')
     )),
     url('^/config/', patterns('ajax.config.php:ConfigAjaxAPI',
-        url_get('^ui', 'scp_ui')
+        url_get('^scp', 'scp')
     )),
     url('^/report/overview/', patterns('ajax.reports.php:OverviewReportAjaxAPI',
         # Send
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 899d9caa4726ab6009a89a8c3ef52196d425c8a9..3c7b83e12f3fc85001b1fd451c6fcc3e586e2752 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -30,7 +30,8 @@ a {
 
 .strike { text-decoration:line-through; color:red; }
 
-#canned_attachments label { padding:3px; padding-right:10px; }
+.canned_attachments label, .canned_attachments span .uploads label { padding:3px; padding-right:10px; }
+.canned_attachments label { padding-right:3px; }
 
 
 #breadcrumbs {
diff --git a/scp/faq.php b/scp/faq.php
index f668be9f233fe5f2678bebceda1f6fda94dfdd13..6e3d422a1669d8b09c02735d3115879e0fb3edbd 100644
--- a/scp/faq.php
+++ b/scp/faq.php
@@ -40,19 +40,7 @@ if($_POST):
             elseif($faq->update($_POST,$errors)) {
                 $msg='FAQ updated successfully';
                 $_REQUEST['a']=null; //Go back to view
-                //Delete removed attachments.
-                $keepers = $_POST['files']?$_POST['files']:array();
-                if(($attachments = $faq->getAttachments())) {
-                    foreach($attachments as $k=>$file) {
-                        if($file['id'] && !in_array($file['id'], $keepers)) {
-                            $faq->deleteAttachment($file['id']);
-                        }
-                    }
-                }
-                //Upload NEW attachments IF ANY - TODO: validate attachment types??
-                if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments'])))
-                    $faq->uploadAttachments($files);
-
+                $faq->reload();
             } elseif(!$errors['err'])
                 $errors['err'] = 'Unable to update FAQ. Try again!';     
             break;
diff --git a/scp/js/scp.js b/scp/js/scp.js
index c46c624d49ca97127b9bca3485c6dd313c53497b..8730685c0e2134d18d6a29e9f9f74cf66daaffa8 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -121,7 +121,7 @@ $(document).ready(function(){
 
 
     //Canned attachments.
-    $('#canned_attachments, #faq_attachments').delegate('input:checkbox', 'click', function(e) {
+    $('.canned_attachments, .faq_attachments').delegate('input:checkbox', 'click', function(e) {
         var elem = $(this);
         if(!$(this).is(':checked') && confirm("Are you sure you want to remove this attachment?")==true) {
             elem.parent().addClass('strike');
@@ -154,12 +154,13 @@ $(document).ready(function(){
                             $('#response',fObj).val(canned.response);
                     }
                     //Canned attachments.
-                    if(canned.files && $('#canned_attachments',fObj).length) {
+                    if(canned.files && $('.canned_attachments',fObj).length) {
                         $.each(canned.files,function(i, j) {
-                            if(!$('#canned_attachments #f'+j.id,fObj).length) {
-                                var file='<label><input type="checkbox" name="cannedattachments[]" value="' + j.id+'" id="f'+j.id+'" checked="checked">';
-                                    file+= '<a href="file.php?h=' + j.hash + j.key+ '">'+ j.name +'</a></label>';
-                                $('#canned_attachments', fObj).append(file);
+                            if(!$('.canned_attachments #f'+j.id,fObj).length) {
+                                var file='<span><label><input type="checkbox" name="cannedattachments[]" value="' + j.id+'" id="f'+j.id+'" checked="checked">';
+                                    file+= ' '+ j.name + '</label>';
+                                    file+= ' (<a href="file.php?h=' + j.hash + j.key+ '">view</a>) </span>';
+                                $('.canned_attachments', fObj).append(file);
                             }
 
                          });
@@ -203,17 +204,24 @@ $(document).ready(function(){
        });
 
     /* Get config settings from the backend */
-    $.get('ajax.php/config/ui.json',
-        function(config){
-            /*
-            if(config && config.max_attachments)
-                alert(config.max_attachments);
-            */
-        },
-        'json')
-        .error( function() {});
-    /* Datepicker */
+    var $config = null;
+    $.ajax({
+        url: "ajax.php/config/scp",
+        dataType: 'json',
+        async: false,
+        success: function (config) {
+            $config = config;
+            }
+        });
+     
+    /* Multifile uploads */
+     $('.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:".*"
+        });
 
+    /* Datepicker */
     $('.dp').datepicker({
         numberOfMonths: 2,
         showButtonPanel: true,
diff --git a/scp/tickets.php b/scp/tickets.php
index 0feacf86429846326f98430c4c465c38262295c4..66af215ea732a492b5f9e99cf5b0d99c16ebdb6c 100644
--- a/scp/tickets.php
+++ b/scp/tickets.php
@@ -57,7 +57,7 @@ if($_POST && !$errors):
 
             $wasOpen =($ticket->isOpen());
             //If no error...do the do.
-            if(!$errors && ($respId=$ticket->postReply($_POST,$_FILES['attachments'],$errors))) {
+            if(!$errors && ($respId=$ticket->postReply($_POST, $errors))) {
                 $msg='Reply posted successfully';
                 $ticket->reload();
                 if($ticket->isClosed() && $wasOpen)
@@ -360,7 +360,7 @@ 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, $_FILES['attachments'], $errors))) {
+                }elseif(($ticket=Ticket::open($_POST, $errors))) {
                     $msg='Ticket created successfully';
                     $_REQUEST['a']=null;
                     if(!$ticket->checkStaffAccess($thisstaff) || $ticket->isClosed())
diff --git a/secure.inc.php b/secure.inc.php
index 3096b3503036e8746a58cea56aa2e85f977b4896..45010979855f9f377e802b569a1d9edc8eb2af91 100644
--- a/secure.inc.php
+++ b/secure.inc.php
@@ -16,9 +16,18 @@
 if(!strcasecmp(basename($_SERVER['SCRIPT_NAME']),basename(__FILE__))) die('Kwaheri!');
 if(!file_exists('client.inc.php')) die('Fatal Error.');
 require_once('client.inc.php');
+
+//Client Login page: Ajax interface can pre-declare the function to trap logins.
+if(!function_exists('clientLoginPage')) {
+    function clientLoginPage($msg ='') {
+        require('./login.php');
+        exit;
+    }
+}
+
 //User must be logged in!
 if(!$thisclient || !$thisclient->getId() || !$thisclient->isValid()){
-    require('./login.php');
+    clientLoginPage();
     exit;
 }
 $thisclient->refreshSession();
diff --git a/tickets.php b/tickets.php
index 81b8ed68f153b551c4b063d2fa6860ea84070ac1..4ee69a8ef51f5b2b274df224fac4ed195330da5a 100644
--- a/tickets.php
+++ b/tickets.php
@@ -38,26 +38,15 @@ if($_POST && is_object($ticket) && $ticket->getId()):
         if(!$_POST['message'])
             $errors['message']='Message required';
 
-        //check attachment..if any is set
-        $files=($cfg->allowOnlineAttachments() && $_FILES['attachments'])?Format::files($_FILES['attachments']):array();
-        if($files) {
-
-            foreach($files as $file) {
-                if(!$file['name']) continue;
-
-                if(!$cfg->canUploadFileType($file['name']))
-                    $errors['attachment']='Invalid file type [ '.$file['name'].' ]';
-                elseif($file['size']>$cfg->getMaxFileSize())
-                    $errors['attachment']='File '.$file['name'].'is too big. Max '.$cfg->getMaxFileSize().' bytes allowed';
-            }
-        }
-                    
         if(!$errors) {
             //Everything checked out...do the magic.
             if(($msgid=$ticket->postMessage($_POST['message'],'Web'))) {
-                if($files && $cfg->allowOnlineAttachments())
-                    $ticket->uploadAttachments($files,$msgid,'M');
-
+                if($cfg->allowOnlineAttachments() 
+                        && $_FILES['attachments']
+                        && ($files=Format::files($_FILES['attachments']))) {
+                    $ost->validateFileUploads($files); //Validator sets errors - if any.
+                    $ticket->uploadAttachments($files, $msgid, 'M');
+                }
                 $msg='Message Posted Successfully';
             } else {
                 $errors['err']='Unable to post the message. Try again';
@@ -68,7 +57,7 @@ if($_POST && is_object($ticket) && $ticket->getId()):
         }
         break;
     default:
-        $errors['err']='Uknown action';
+        $errors['err']='Unknown action';
     }
     $ticket->reload();
 endif;