diff --git a/api/.htaccess b/api/.htaccess
index e73e2eb3b9c3f1204223b4426274817c0200e279..f460420d6d2f55f362fb420788b6751afe38bbba 100644
--- a/api/.htaccess
+++ b/api/.htaccess
@@ -1,8 +1,11 @@
-RewriteEngine On
+<IfModule mod_rewrite.c>
 
-RewriteBase /api/
+RewriteEngine On
 
 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteCond %{REQUEST_FILENAME} !-d
+RewriteCond %{REQUEST_URI} (.*/api)
+
+RewriteRule ^(.*)$ %1/http.php/$1 [L]
 
-RewriteRule ^(.*)$ http.php/$1 [L]
+</IfModule>
diff --git a/api/api.inc.php b/api/api.inc.php
index 926a0e2a9ddb4b0464c5f1385e56665312339d8e..48836382022d6b8ddec422e0afc46ee074930567 100644
--- a/api/api.inc.php
+++ b/api/api.inc.php
@@ -42,7 +42,7 @@ function api_exit($code,$msg='') {
         //Error occured...
         $_SESSION['api']['errors']+=1;
         $_SESSION['api']['time']=time();
-        $ost->logWarning("API error - code #$code",$msg);
+        $ost->logWarning("API error - code #$code", $msg, ($_SESSION['api']['errors']>10));
         //echo "API Error:.$msg";
     }
     if($remotehost){
@@ -66,19 +66,20 @@ function api_exit($code,$msg='') {
 }
 
 //Remote hosts need authorization.
+$apikey = null;
 if($remotehost) {
-
-    $ip=$_SERVER['REMOTE_ADDR'];
-    $key=$_SERVER['HTTP_USER_AGENT']; //pulling all tricks.
-    //Upto 10 consecutive errors allowed...before a 5 minute timeout.
+    //Upto 10 consecutive errors allowed...before a 2 minute timeout.
     //One more error during timeout and timeout starts a new clock
-    if($_SESSION['api']['errors']>10 && (time()-$_SESSION['api']['time'])<=5*60) { // timeout!
-        api_exit(EX_NOPERM,"Remote host [$ip] in timeout - error #".$_SESSION['api']['errors']);
-    }
-    //Check API key & ip
-    if(!Validator::is_ip($ip) || !Api::validate($key,$ip)) { 
-        api_exit(EX_NOPERM,'Unknown remote host ['.$ip.'] or invalid API key ['.$key.']');
-    }
+    if($_SESSION['api']['errors']>10 && (time()-$_SESSION['api']['time'])<=2*60)  // timeout!
+        api_exit(EX_NOPERM, 'Remote host ['.$_SERVER['REMOTE_ADDR'].'] in timeout - error #'.$_SESSION['api']['errors']);
+        
+    if(!isset($_SERVER['HTTP_X_API_KEY']) || !isset($_SERVER['REMOTE_ADDR']))
+        api_exit(EX_NOPERM, 'API key required');
+    elseif(!($apikey=API::lookupByKey($_SERVER['HTTP_X_API_KEY'], $_SERVER['REMOTE_ADDR']))
+                || !$apikey->isActive()
+                || $apikey->getIPAddr()!=$_SERVER['REMOTE_ADDR'])
+        api_exit(EX_NOPERM, 'API key not found/active or source IP not authorized');
+    
     //At this point we know the remote host/IP is allowed.
     $_SESSION['api']['errors']=0; //clear errors for the session.
 }
diff --git a/api/pipe.php b/api/pipe.php
index 29dfcff1d1aa10a20386589927830ffed8211911..ff23cfa1b56c0f6fa75824f8106337c7440f6cb9 100644
--- a/api/pipe.php
+++ b/api/pipe.php
@@ -16,6 +16,7 @@
 **********************************************************************/
 @chdir(realpath(dirname(__FILE__)).'/'); //Change dir.
 ini_set('memory_limit', '256M'); //The concern here is having enough mem for emails with attachments.
+$apikey = null;
 require('api.inc.php');
 require_once(INCLUDE_DIR.'class.mailparse.php');
 require_once(INCLUDE_DIR.'class.email.php');
@@ -23,6 +24,9 @@ require_once(INCLUDE_DIR.'class.email.php');
 //Make sure piping is enabled!
 if(!$cfg->isEmailPipingEnabled())
     api_exit(EX_UNAVAILABLE,'Email piping not enabled - check MTA settings.');
+elseif($apikey && !$apikey->canCreateTickets()) //apikey is ONLY set on remote post - local post don't need a key (for now).
+    api_exit(EX_NOPERM, 'API key not authorized');
+
 //Get the input
 $data=isset($_SERVER['HTTP_HOST'])?file_get_contents('php://input'):file_get_contents('php://stdin');
 if(empty($data)){
@@ -77,8 +81,8 @@ $name=trim($from->personal,'"');
 if($from->comment && $from->comment[0])
     $name.=' ('.$from->comment[0].')';
 $subj=utf8_encode($parser->getSubject());
-if(!($body=Format::stripEmptyLines($parser->getBody())) && $subj)
-    $body=$subj;
+if(!($body=Format::stripEmptyLines($parser->getBody())))
+    $body=$subj?$subj:'(EMPTY)';
 
 $var['mid']=$parser->getMessageId();
 $var['email']=$from->mailbox.'@'.$from->host;
@@ -90,31 +94,38 @@ $var['header']=$parser->getHeader();
 $var['priorityId']=$cfg->useEmailPriority()?$parser->getPriority():0;
 
 $ticket=null;
-if(preg_match ("[[#][0-9]{1,10}]",$var['subject'],$regs)) {
+if(preg_match ("[[#][0-9]{1,10}]", $var['subject'], $regs)) {
     $extid=trim(preg_replace("/[^0-9]/", "", $regs[0]));
-    $ticket= new Ticket(Ticket::getIdByExtId($extid));
-    //Allow mismatched emails?? For now hell NO.
-    if(!is_object($ticket) || strcasecmp($ticket->getEmail(),$var['email']))
-        $ticket=null;
+    if(!($ticket=Ticket::lookupByExtId($extid, $var['email'])) || strcasecmp($ticket->getEmail(), $var['email']))
+       $ticket = null;
 }        
+
 $errors=array();
 $msgid=0;
-if(!$ticket) { //New tickets...
-    $ticket=Ticket::create($var,$errors,'email');
-    if(!is_object($ticket) || $errors) {
-        api_exit(EX_DATAERR,'Ticket create Failed '.implode("\n",$errors)."\n\n");
-    }
+if($ticket) {
+    //post message....postMessage does the cleanup.
+    if(!($msgid=$ticket->postMessage($var['message'], 'Email',$var['mid'],$var['header'])))
+        api_exit(EX_DATAERR, 'Unable to post message');
 
+} elseif(($ticket=Ticket::create($var, $errors, 'email'))) { // create new ticket.
     $msgid=$ticket->getLastMsgId();
+} else { // failure....
 
-} else {
-    //post message....postMessage does the cleanup.
-    if(!($msgid=$ticket->postMessage($var['message'], 'Email',$var['mid'],$var['header']))) {
-        api_exit(EX_DATAERR, 'Unable to post message');
+    // report success on hard rejection
+    if(isset($errors['errno']) && $errors['errno'] == 403)
+        api_exit(EX_SUCCESS);
+
+    // check if it's a bounce!
+    if($var['header'] && TicketFilter::isAutoBounce($var['header'])) {
+        $ost->logWarning('Bounced email', $var['message'], false);
+        api_exit(EX_SUCCESS); 
     }
+    
+    api_exit(EX_DATAERR, 'Ticket create Failed '.implode("\n",$errors)."\n\n");
 }
+
 //Ticket created...save attachments if enabled.
-if($cfg->allowEmailAttachments() && ($attachments=$parser->getAttachments())) {
+if($ticket && $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');
diff --git a/include/ajax.reports.php b/include/ajax.reports.php
index 7fbe19c89c81605725572e45433ef20ea374fdcf..bf86374055bd45b8e07a1860a157d77d3b53aa64 100644
--- a/include/ajax.reports.php
+++ b/include/ajax.reports.php
@@ -37,10 +37,15 @@ class OverviewReportAjaxAPI extends AjaxController {
     function getData() {
         global $thisstaff;
 
-        $start = $this->get('start', 'last month');
-        $stop = $this->get('stop', 'now');
-        if (substr($stop, 0, 1) == '+')
-            $stop = $start . $stop;
+        if(($start = $this->get('start', 'last month'))) {
+            $stop = $this->get('stop', 'now');
+            if (substr($stop, 0, 1) == '+')
+                $stop = $start . $stop;
+        } else {
+            $start = 'last month';
+            $stop = 'now';
+        }
+
         $start = 'FROM_UNIXTIME('.strtotime($start).')';
         $stop = 'FROM_UNIXTIME('.strtotime($stop).')';
 
@@ -72,9 +77,11 @@ class OverviewReportAjaxAPI extends AjaxController {
                 "filter" =>
                     ('T1.staff_id=S1.staff_id
                       AND 
-                      (T1.staff_id='.db_input($thisstaff->getDeptId())
+                      (T1.staff_id='.db_input($thisstaff->getId())
                         .(($depts=$thisstaff->getManagedDepartments())?
-                            (' OR T1.staff_id IN('.implode(',', db_input($depts)).')'):'')
+                            (' OR T1.dept_id IN('.implode(',', db_input($depts)).')'):'')
+                        .(($thisstaff->canViewStaffStats())?
+                            (' OR T1.dept_id IN('.implode(',', db_input($thisstaff->getDepts())).')'):'')
                      .')'
                      ) 
             )
@@ -166,10 +173,17 @@ class OverviewReportAjaxAPI extends AjaxController {
     }
 
     function getPlotData() {
-        $start = $this->get('start', 'last month');
-        $stop = $this->get('stop', 'now');
-        if (substr($stop, 0, 1) == '+')
-            $stop = $start . $stop;
+
+                
+        if(($start = $this->get('start', 'last month'))) {
+            $stop = $this->get('stop', 'now');
+            if (substr($stop, 0, 1) == '+')
+                $stop = $start . $stop;
+        } else {
+            $start = 'last month';
+            $stop = 'now';
+        }
+
         $start = strtotime($start);
         $stop = strtotime($stop);
 
diff --git a/include/api.ticket.php b/include/api.ticket.php
index 4fcae4b183925dfe32bb91c95275a986fc492ea6..7fd5ba713c8237aad483c4a44b08989cf9b17c3f 100644
--- a/include/api.ticket.php
+++ b/include/api.ticket.php
@@ -15,14 +15,16 @@ class TicketController extends ApiController {
             "attachments" => array("*" => 
                 array("name", "type", "data", "encoding")
             ), 
-            "message", "ip"
+            "message", "ip", "priorityId"
         );
         if ($format == "xml") return array("ticket" => $supported);
         else return $supported;
     }
 
     function create($format) {
-        $this->requireApiKey();
+
+        if(!($key=$this->getApiKey()) || !$key->canCreateTickets())
+            Http::response(401, 'API key not authorized');
 
         # Parse request body
         $data = $this->getRequest($format);
@@ -43,7 +45,7 @@ class TicketController extends ApiController {
                 if (!($info["data"] = base64_decode($info["data"], true)))
                     Http::response(400, sprintf(
                         "%s: Poorly encoded base64 data",
-                        $filename));
+                        $info['name']));
             }
             $info['size'] = strlen($info['data']);
         }
diff --git a/include/class.api.php b/include/class.api.php
index 1bd25463484a4e2f01a1d72050dde4e92d89d967..df8cfb0c8a9cb9f452b62de56c1e15181731d614 100644
--- a/include/class.api.php
+++ b/include/class.api.php
@@ -17,102 +17,115 @@ class API {
 
     var $id;
 
-    var $info;
+    var $ht;
 
-    function API($id){
-        $this->id=0;
+    function API($id) {
+        $this->id = 0;
         $this->load($id);
     }
 
-    function load($id) {
+    function load($id=0) {
+
+        if(!$id && !($id=$this->getId()))
+            return false;
 
         $sql='SELECT * FROM '.API_KEY_TABLE.' WHERE id='.db_input($id);
-        if(($res=db_query($sql)) && db_num_rows($res)) {
-            $info=db_fetch_array($res);
-            $this->id=$info['id'];
-            $this->info=$info;
-            return true;
-        }
-        return false;
+        if(!($res=db_query($sql)) || !db_num_rows($res))
+            return false;
+        
+        $this->ht = db_fetch_array($res);
+        $this->id = $this->ht['id'];
+        
+        return true;
     }
 
     function reload() {
-        return $this->load($this->getId());
+        return $this->load();
     }
 
-    function getId(){
+    function getId() {
         return $this->id;
     }
 
-    function getKey(){
-        return $this->info['apikey'];
+    function getKey() {
+        return $this->ht['apikey'];
     }
 
-    function getIPAddr(){
-        return $this->info['ipaddr'];
+    function getIPAddr() {
+        return $this->ht['ipaddr'];
     }
         
-    function getNotes(){
-        return $this->info['notes'];
+    function getNotes() {
+        return $this->ht['notes'];
     }
 
-    function isActive(){
-        return ($this->info['isactive']);
+    function getHashtable() {
+        return $this->ht;
     }
 
-    function update($vars,&$errors){
-        if(API::save($this->getId(),$vars,$errors)){
-            $this->reload();
-            return true;
-        }
+    function isActive() {
+        return ($this->ht['isactive']);
+    }
+
+    function canCreateTickets() {
+        return ($this->ht['can_create_tickets']);
+    }
+
+    function update($vars, &$errors) {
+
+        if(!API::save($this->getId(), $vars, $errors))
+            return false;
         
-        return false;
+        $this->reload();
+
+        return true;
     }
 
-    function delete(){
+    function delete() {
         $sql='DELETE FROM '.API_KEY_TABLE.' WHERE id='.db_input($this->getId()).' LIMIT 1';
         return (db_query($sql) && ($num=db_affected_rows()));
     }
 
     /** Static functions **/
-    function add($vars,&$errors){
-        return API::save(0,$vars,$errors);
+    function add($vars, &$errors) {
+        return API::save(0, $vars, $errors);
     }
 
-    function validate($key,$ip){
-
-        $sql='SELECT id FROM '.API_KEY_TABLE.' WHERE ipaddr='.db_input($ip).' AND apikey='.db_input($key);
-        return (($res=db_query($sql)) && db_num_rows($res));
+    function validate($key, $ip) {
+        return ($key && $ip && self::getIdByKey($key, $ip));
     }
 
-    function getKeyByIPAddr($ip){
+    function getIdByKey($key, $ip='') {
 
-        $sql='SELECT apikey FROM '.API_KEY_TABLE.' WHERE ipaddr='.db_input($ip);
+        $sql='SELECT id FROM '.API_KEY_TABLE.' WHERE apikey='.db_input($key);
+        if($ip)
+            $sql.=' AND ipaddr='.db_input($ip);
+        
         if(($res=db_query($sql)) && db_num_rows($res))
-            list($key)=db_fetch_row($res);
+            list($id) = db_fetch_row($res);
 
-        return $key;
+        return $id;
     }
 
-    function lookup($id){
-        return ($id && is_numeric($id) && ($k= new API($id)) && $k->getId()==$id)?$k:null;
+    function lookupByKey($key, $ip='') {
+        return self::lookup(self::getIdByKey($key, $ip));
     }
 
-    function save($id,$vars,&$errors){
+    function lookup($id) {
+        return ($id && is_numeric($id) && ($k= new API($id)) && $k->getId()==$id)?$k:null;
+    }
 
-        if(!$id) {
-            if(!$vars['ipaddr'] || !Validator::is_ip($vars['ipaddr']))
-                $errors['ipaddr']='Valid IP required';
-            elseif(API::getKeyByIPAddr($vars['ipaddr']))
-                $errors['ipaddr']='API key for the IP already exists';
-        }
+    function save($id, $vars, &$errors) {
 
+        if(!$id && (!$vars['ipaddr'] || !Validator::is_ip($vars['ipaddr'])))
+            $errors['ipaddr'] = 'Valid IP required';
+        
         if($errors) return false;
 
-
-        $sql=' updated=NOW() '.
-             ',isactive='.db_input($vars['isactive']).
-             ',notes='.db_input($vars['notes']);
+        $sql=' updated=NOW() '
+            .',isactive='.db_input($vars['isactive'])
+            .',can_create_tickets='.db_input($vars['can_create_tickets'])
+            .',notes='.db_input($vars['notes']);
 
         if($id) {
             $sql='UPDATE '.API_KEY_TABLE.' SET '.$sql.' WHERE id='.db_input($id);
@@ -120,14 +133,17 @@ class API {
                 return true;
 
             $errors['err']='Unable to update API key. Internal error occurred';
-        }else{
-            $sql='INSERT INTO '.API_KEY_TABLE.' SET '.$sql.',created=NOW() '.
-                 ',ipaddr='.db_input($vars['ipaddr']).
-                 ',apikey='.db_input(strtoupper(md5(time().$vars['ipaddr'].md5(Misc::randcode(16)))));
+
+        } else {
+            $sql='INSERT INTO '.API_KEY_TABLE.' SET '.$sql
+                .',created=NOW() '
+                .',ipaddr='.db_input($vars['ipaddr'])
+                .',apikey='.db_input(strtoupper(md5(time().$vars['ipaddr'].md5(Misc::randcode(16)))));
+
             if(db_query($sql) && ($id=db_insert_id()))
                 return $id;
 
-            $errors['err']='Unable to add API key. Internal error';
+            $errors['err']='Unable to add API key. Try again!';
         }
 
         return false;
@@ -141,16 +157,24 @@ class API {
  * API request.
  */
 class ApiController {
+
     function requireApiKey() {
         # Validate the API key -- required to be sent via the X-API-Key
         # header
-        if (!isset($_SERVER['HTTP_X_API_KEY']))
+        if (!isset($_SERVER['HTTP_X_API_KEY']) || !isset($_SERVER['REMOTE_ADDR']))
             Http::response(403, "API key required");
-        else if (!Api::validate($_SERVER['HTTP_X_API_KEY'],
-                $_SERVER['REMOTE_ADDR']))
-            Http::response(401,
-                "API key not found or source IP not authorized");
+        elseif (!($key=API::lookupByKey($_SERVER['HTTP_X_API_KEY'], $_SERVER['REMOTE_ADDR']))
+                || !$key->isActive() 
+                || $key->getIPAddr()!=$_SERVER['REMOTE_ADDR'])
+            Http::response(401, "API key not found/active or source IP not authorized");
+
+        return $key;
     }
+
+    function getApiKey() {
+        return $this->requireApiKey();
+    }
+
     /**
      * Retrieves the body of the API request and converts it to a common
      * hashtable. For JSON formats, this is mostly a noop, the conversion
@@ -184,7 +208,7 @@ class ApiController {
     function validate($data, $structure, $prefix="") {
         foreach ($data as $key=>$info) {
             if (is_array($structure) and is_array($info)) {
-                $search = isset($structure[$key]) ? $key : "*"; 
+                $search = (isset($structure[$key]) && !is_numeric($key)) ? $key : "*"; 
                 if (isset($structure[$search])) {
                     $this->validate($info, $structure[$search], "$prefix$key/");
                     continue;
@@ -219,16 +243,21 @@ class ApiXmlDataParser extends XmlDataParser {
             } else if ($key == "autorespond") {
                 $value = (bool)$value;
             } else if ($key == "attachments") {
-                foreach ($value as &$info) { 
-                    $info["data"] = $info[":text"]; 
-                    unset($info[":text"]);
+                if(!isset($value['file'][':text']))
+                    $value = $value['file'];
+
+                if($value && is_array($value)) {
+                    foreach ($value as &$info) { 
+                        $info["data"] = $info[":text"]; 
+                        unset($info[":text"]);
+                    }
+                    unset($info);
                 }
-                unset($info);
-            }
-            if (is_array($value)) {
+            } else if(is_array($value)) {
                 $value = $this->fixup($value);
             }
         }
+
         return $current;
     }
 }
@@ -243,7 +272,7 @@ class ApiJsonDataParser extends JsonDataParser {
             return $current;
         foreach ($current as $key=>&$value) {
             if ($key == "phone") {
-                list($value,$current["phone_ext"])
+                list($value, $current["phone_ext"])
                     = explode("X", strtoupper($value), 2); 
             } else if ($key == "alert") {
                 $value = (bool)$value;
@@ -255,7 +284,7 @@ class ApiJsonDataParser extends JsonDataParser {
                     # PHP5: fopen("data://$data[5:]");
                     if (substr($data, 0, 5) != "data:") {
                         $info = array(
-                            "data" => $data, 
+                            "data" => $data,
                             "type" => "text/plain",
                             "name" => key($info));
                     } else {
@@ -264,11 +293,17 @@ class ApiJsonDataParser extends JsonDataParser {
                         list($type, $extra) = explode(";", $meta);
                         $info = array(
                             "data" => $contents,
-                            "type" => $type,
+                            "type" => ($type) ? $type : "text/plain",
                             "name" => key($info));
                         if (substr($extra, -6) == "base64")
                             $info["encoding"] = "base64";
-                        # TODO: Handle 'charset' hint in $extra
+                        # Handle 'charset' hint in $extra, such as
+                        # data:text/plain;charset=iso-8859-1,Blah
+                        # Convert to utf-8 since it's the encoding scheme
+                        # for the database. Otherwise, assume utf-8
+                        list($param,$charset) = explode('=', $extra);
+                        if ($param == 'charset' && function_exists('iconv'))
+                            $contents = iconv($charset, "UTF-8", $contents);
                     }
                 }
                 unset($value);
diff --git a/include/class.config.php b/include/class.config.php
index 3aff085f77be0820d61c980a3445a889e7dc19d5..c7e9669742d26f9e45063e02d26d1b2a7737e16a 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -868,11 +868,11 @@ class Config {
              .',transfer_alert_active='.db_input($vars['transfer_alert_active'])
              .',transfer_alert_assigned='.db_input(isset($vars['transfer_alert_assigned'])?1:0)
              .',transfer_alert_dept_manager='.db_input(isset($vars['transfer_alert_dept_manager'])?1:0)
-             .',transfer_alert_dept_members='.db_input(isset($var['transfer_alert_dept_members'])?1:0)
+             .',transfer_alert_dept_members='.db_input(isset($vars['transfer_alert_dept_members'])?1:0)
              .',overdue_alert_active='.db_input($vars['overdue_alert_active'])
              .',overdue_alert_assigned='.db_input(isset($vars['overdue_alert_assigned'])?1:0)
              .',overdue_alert_dept_manager='.db_input(isset($vars['overdue_alert_dept_manager'])?1:0)
-             .',overdue_alert_dept_members='.db_input(isset($var['overdue_alert_dept_members'])?1:0)
+             .',overdue_alert_dept_members='.db_input(isset($vars['overdue_alert_dept_members'])?1:0)
              .',send_sys_errors='.db_input(isset($vars['send_sys_errors'])?1:0)
              .',send_sql_errors='.db_input(isset($vars['send_sql_errors'])?1:0)
              .',send_login_errors='.db_input(isset($vars['send_login_errors'])?1:0)
diff --git a/include/class.email.php b/include/class.email.php
index e6b10c3892ece68f9d0410a77116f67cf7de61dd..c38144c4b1281fabf18ffd90b7e4a3937ff4cd90 100644
--- a/include/class.email.php
+++ b/include/class.email.php
@@ -142,7 +142,6 @@ class Email {
 
     function send($to, $subject, $message, $attachments=null, $options=null) {
 
-
         $mailer = new Mailer($this);
         if($attachments)
             $mailer->addAttachments($attachments);
@@ -150,6 +149,16 @@ class Email {
         return $mailer->send($to, $subject, $message, $options);
     }
 
+    function sendAutoReply($to, $subject, $message, $attachments=null, $options=array()) {
+        $options+= array('autoreply' => true);
+        return $this->send($to, $subject, $message, $attachments, $options);
+    }
+
+    function sendAlert($to, $subject, $message, $attachments=null, $options=array()) {
+        $options+= array('bulk' => true);
+        return $this->send($to, $subject, $message, $attachments, $options);
+    }
+
     function update($vars,&$errors) {
         $vars=$vars;
         $vars['cpasswd']=$this->getPasswd(); //Current decrypted password.
diff --git a/include/class.file.php b/include/class.file.php
index 364e7f14ac570d3cc0c2bb2fa074bb05eef1c29a..da8ad76dc0ed9e9917596829fa7a6450f8176b2d 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -181,7 +181,7 @@ class AttachmentFile {
         $sql='INSERT INTO '.FILE_TABLE.' SET created=NOW() '
             .',type='.db_input($file['type'])
             .',size='.db_input($file['size'])
-            .',name='.db_input($file['name'])
+            .',name='.db_input(Format::file_name($file['name']))
             .',hash='.db_input($file['hash']);
 
         if (!(db_query($sql) && ($id=db_insert_id())))
diff --git a/include/class.filter.php b/include/class.filter.php
index 9c172edaabaf3f819df46e49e5fd2da33eb90d62..0af8330b8022393162450c803031b000a7471ab0 100644
--- a/include/class.filter.php
+++ b/include/class.filter.php
@@ -233,7 +233,9 @@ class Filter {
             'equal'     => array('strcmp', 0),
             'not_equal' => array('strcmp', null, 0),
             'contains'  => array('strpos', null, false),
-            'dn_contain'=> array('strpos', false)
+            'dn_contain'=> array('strpos', false),
+            'starts'    => array('strpos', 0),
+            'ends'      => array('endsWith', true)
         );
 
         $match = false;
@@ -309,7 +311,9 @@ class Filter {
             'equal'=>       'Equal',
             'not_equal'=>   'Not Equal',
             'contains'=>    'Contains',
-            'dn_contain'=>  'Does Not Contain'
+            'dn_contain'=>  'Does Not Contain',
+            'starts'=>      'Starts With',
+            'ends'=>        'Ends With'
         );
     }
 
@@ -366,8 +370,8 @@ class Filter {
 
     function save_rules($id,$vars,&$errors) {
 
-        $matches=array('name','email','subject','body','header');
-        $types=array('equal','not_equal','contains','dn_contain');
+        $matches = array_keys(self::getSupportedMatches());
+        $types = array_keys(self::getSupportedMatchTypes());
 
         $rules=array();
         for($i=1; $i<=25; $i++) { //Expecting no more than 25 rules...
@@ -866,30 +870,69 @@ class TicketFilter {
      *    http://msdn.microsoft.com/en-us/library/ee219609(v=exchg.80).aspx
      */
     /* static */ function isAutoResponse($headers) {
+
+        if($headers && !is_array($headers))
+            $headers = Mail_Parse::splitHeaders($headers);
+
         $auto_headers = array(
             'Auto-Submitted'    => 'AUTO-REPLIED',
             'Precedence'        => array('AUTO_REPLY', 'BULK', 'JUNK', 'LIST'),
             'Subject'           => array('OUT OF OFFICE', 'AUTO-REPLY:', 'AUTORESPONSE'),
             'X-Autoreply'       => 'YES',
-            'X-Auto-Response-Suppress' => 'OOF',
+            'X-Auto-Response-Suppress' => array('ALL', 'DR', 'RN', 'NRN', 'OOF', 'AutoReply'),
             'X-Autoresponse'    => '',
             'X-Auto-Reply-From' => ''
         );
+
         foreach ($auto_headers as $header=>$find) {
-            if ($value = strtoupper($headers[$header])) {
-                # Search text must be found at the beginning of the header
-                # value. This is especially import for something like the
-                # subject line, where something like an autoreponse may
-                # appear somewhere else in the value.
-                if (is_array($find)) {
-                    foreach ($find as $f)
-                        if (strpos($value, $f) === 0)
-                            return true;
-                } elseif (strpos($value, $find) === 0) {
-                    return true;
-                }
+            if(!isset($headers[$header])) continue;
+
+            $value = strtoupper($headers[$header]);
+            # Search text must be found at the beginning of the header
+            # value. This is especially import for something like the
+            # subject line, where something like an autoreponse may
+            # appear somewhere else in the value.
+
+            if (is_array($find)) {
+                foreach ($find as $f)
+                    if (strpos($value, $f) === 0)
+                        return true;
+            } elseif (strpos($value, $find) === 0) {
+                return true;
+            }
+        }
+
+        # Bounces also counts as auto-responses.
+        if(self::isAutoBounce($headers))
+            return true;
+
+        return false;
+    }
+
+    function isAutoBounce($headers) {
+
+        if($headers && !is_array($headers))
+            $headers = Mail_Parse::splitHeaders($headers);
+
+        $bounce_headers = array(
+            'From'          => array('<MAILER-DAEMON@MAILER-DAEMON>', 'MAILER-DAEMON', '<>'),
+            'Subject'       => array('DELIVERY FAILURE', 'DELIVERY STATUS', 'UNDELIVERABLE:'),
+        );
+
+        foreach ($bounce_headers as $header => $find) {
+            if(!isset($headers[$header])) continue;
+
+            $value = strtoupper($headers[$header]);
+
+            if (is_array($find)) {
+                foreach ($find as $f)
+                    if (strpos($value, $f) === 0)
+                        return true;
+            } elseif (strpos($value, $find) === 0) {
+                return true;
             }
         }
+
         return false;
     }
 
@@ -903,4 +946,20 @@ class TicketFilter {
         return $sources[strtolower($origin)];
     }
 }
+
+/**
+ * Function: endsWith
+ *
+ * Returns TRUE if the haystack ends with needle and FALSE otherwise.
+ * Thanks, http://stackoverflow.com/a/834355
+ */
+function endsWith($haystack, $needle)
+{
+    $length = strlen($needle);
+    if ($length == 0) {
+        return true;
+    }
+
+    return (substr($haystack, -$length) === $needle);
+}
 ?>
diff --git a/include/class.format.php b/include/class.format.php
index aaa6667d32c2fce14da2cb9f5e15387ec9608b0c..b510221e02b41a8da8b4bfd27278cfdb9e5d5e9d 100644
--- a/include/class.format.php
+++ b/include/class.format.php
@@ -31,10 +31,7 @@ class Format {
     }
 
     function file_name($filename) {
-
-        $search = array('/ß/','/ä/','/Ä/','/ö/','/Ö/','/ü/','/Ü/','([^[:alnum:]._])');
-        $replace = array('ss','ae','Ae','oe','Oe','ue','Ue','_');
-        return preg_replace($search,$replace,$filename);
+        return preg_replace('/\s+/', '_', $filename);
     }
 
     /* re-arrange $_FILES array for the sane */
diff --git a/include/class.group.php b/include/class.group.php
index b13082be9f24301e12bf298cac0d043f86b7f8d1..10a5eadafe9525f0c044c46d918924c00e2451cc 100644
--- a/include/class.group.php
+++ b/include/class.group.php
@@ -209,6 +209,8 @@ class Group {
             .', can_ban_emails='.db_input($vars['can_ban_emails'])
             .', can_manage_premade='.db_input($vars['can_manage_premade'])
             .', can_manage_faq='.db_input($vars['can_manage_faq'])
+            .', can_post_ticket_reply='.db_input($vars['can_post_ticket_reply'])
+            .', can_view_staff_stats='.db_input($vars['can_view_staff_stats'])
             .', notes='.db_input($vars['notes']);
             
         if($id) {
diff --git a/include/class.mailer.php b/include/class.mailer.php
index 2a965eb371cf3d3cf952e48be313f533bdd084a5..57242e61605b77f241c5ec09973d28a1518b4836 100644
--- a/include/class.mailer.php
+++ b/include/class.mailer.php
@@ -92,9 +92,9 @@ class Mailer {
         require_once (PEAR_DIR.'Mail/mime.php'); // PEAR Mail_Mime packge
 
         //do some cleanup
-        $to=preg_replace("/(\r\n|\r|\n)/s",'', trim($to));
-        $subject=stripslashes(preg_replace("/(\r\n|\r|\n)/s",'', trim($subject)));
-        $body = stripslashes(preg_replace("/(\r\n|\r)/s", "\n", trim($message)));
+        $to = preg_replace("/(\r\n|\r|\n)/s",'', trim($to));
+        $subject = preg_replace("/(\r\n|\r|\n)/s",'', trim($subject));
+        $body = preg_replace("/(\r\n|\r)/s", "\n", trim($message));
 
         /* Message ID - generated for each outgoing email */
         $messageId = sprintf('<%s%d-%s>', Misc::randCode(6), time(),
@@ -107,7 +107,20 @@ class Mailer {
                 'Date'=> date('D, d M Y H:i:s O'),
                 'Message-ID' => $messageId,
                 'X-Mailer' =>'osTicket Mailer'
-                );
+               );
+
+        //Set bulk/auto-response headers.
+        if($options && ($options['autoreply'] or $options['bulk'])) {
+            $headers+= array(
+                    'X-Autoreply' => 'yes',
+                    'X-Auto-Response-Suppress' => 'ALL, AutoReply',
+                    'Auto-Submitted' => 'auto-replied');
+
+            if($options['bulk']) 
+                $headers+= array('Precedence' => 'bulk');
+            else
+                $headers+= array('Precedence' => 'auto_reply');
+        }
 
         $mime = new Mail_mime();
         $mime->setTXTBody($body);
diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php
index aa43ce89e960231bdf2e25bf0f622a24d2b44741..c567819cbdb27b6e4fe5910865b4a36c0c01faa9 100644
--- a/include/class.mailfetch.php
+++ b/include/class.mailfetch.php
@@ -196,23 +196,25 @@ class MailFetcher {
     //Convert text to desired encoding..defaults to utf8
     function mime_encode($text, $charset=null, $enc='utf-8') { //Thank in part to afterburner  
         
-        if(function_exists('iconv') and $text) {
+        if(function_exists('iconv') and ($charset or function_exists('mb_detect_encoding'))) {
             if($charset)
                 return iconv($charset, $enc.'//IGNORE', $text);
             elseif(function_exists('mb_detect_encoding'))
                 return iconv(mb_detect_encoding($text, $this->encodings), $enc, $text);
+        } elseif(function_exists('iconv_mime_decode')) {
+            return iconv_mime_decode($text, 0, $enc);
         }
 
         return utf8_encode($text);
     }
     
-    //Generic decoder - mirrors imap_utf8
+    //Generic decoder - resuting text is utf8 encoded -> mirrors imap_utf8
     function mime_decode($text) {
         
         $str = '';
         $parts = imap_mime_header_decode($text);
         foreach ($parts as $part)
-            $str.= $part->text;
+            $str.= $this->mime_encode($part->text, ($part->charset=='default'?'ASCII':$part->charset), 'utf-8');
         
         return $str?$str:imap_utf8($text);
     }
@@ -310,15 +312,31 @@ class MailFetcher {
         if($part && !$part->parts) {
             //Check if the part is an attachment.
             $filename = '';
-            if($part->ifdisposition && in_array(strtolower($part->disposition), array('attachment', 'inline')))
+            if($part->ifdisposition && in_array(strtolower($part->disposition), array('attachment', 'inline'))) {
                 $filename = $part->dparameters[0]->value;
-            elseif($part->ifparameters && $part->type == 5) //inline image without disposition.
+                //Some inline attachments have multiple parameters.
+                if(count($part->dparameters)>1) {
+                    foreach($part->dparameters as $dparameter) {
+                        if(strcasecmp($dparameter->attribute, 'FILENAME')) continue;
+                        $filename = $dparameter->value;
+                        break;
+                    }
+                }
+            } elseif($part->ifparameters && $part->type == 5) { //inline image without disposition.
                 $filename = $part->parameters[0]->value;
+                if(count($part->parameters)>1) {
+                    foreach($part->parameters as $parameter) {
+                        if(strcasecmp($parameter->attribute, 'FILENAME')) continue;
+                        $filename = $parameter->value;
+                        break;
+                    }
+                }
+            }
 
             if($filename) {
                 return array(
                         array(
-                            'name'  => $filename,
+                            'name'  => $this->mime_decode($filename),
                             'mime'  => $this->getMimeType($part),
                             'encoding' => $part->encoding,
                             'index' => ($index?$index:1)
@@ -378,7 +396,7 @@ class MailFetcher {
 	    //Is the email address banned?
         if($mailinfo['email'] && TicketFilter::isBanned($mailinfo['email'])) {
 	        //We need to let admin know...
-            $ost->logWarning('Ticket denied', 'Banned email - '.$mailinfo['email']);
+            $ost->logWarning('Ticket denied', 'Banned email - '.$mailinfo['email'], false);
 	        return true; //Report success (moved or delete)
         }
 
@@ -417,6 +435,16 @@ class MailFetcher {
         } elseif (($ticket=Ticket::create($var, $errors, 'Email'))) {
             $msgid = $ticket->getLastMsgId();
         } else {
+            //Report success if the email was absolutely rejected.
+            if(isset($errors['errno']) && $errors['errno'] == 403)
+                return true;
+
+            # check if it's a bounce!
+            if($var['header'] && TicketFilter::isAutoBounce($var['header'])) {
+                $ost->logWarning('Bounced email', $var['message'], false);
+                return true;
+            }
+
             //TODO: Log error..
             return null;
         }
diff --git a/include/class.osticket.php b/include/class.osticket.php
index 86d206f212d6450db9755ac4f5c77926ff2a53c8..fd300184a96b985b1c1b1022817b2b11e85fc4aa 100644
--- a/include/class.osticket.php
+++ b/include/class.osticket.php
@@ -26,7 +26,16 @@ define('LOG_WARN',LOG_WARNING);
 class osTicket {
 
     var $loglevel=array(1=>'Error','Warning','Debug');
+    
+    //Page errors.
     var $errors;
+
+    //System 
+    var $system;
+
+
+
+
     var $warning;
     var $message;
 
@@ -136,14 +145,19 @@ class osTicket {
        
         $errors=0;
         foreach($files as &$file) {
-            if(!$this->isFileTypeAllowed($file))
-                $file['error']='Invalid file type for '.$file['name'];
+            //skip no file upload "error" - why PHP calls it an error is beyond me.
+            if($file['error'] && $file['error']==UPLOAD_ERR_NO_FILE) continue;
+
+            if($file['error']) //PHP defined error!
+                $file['error'] = 'File upload error #'.$file['error'];
+            elseif(!$file['tmp_name'] || !is_uploaded_file($file['tmp_name']))
+                $file['error'] = 'Invalid or bad upload POST';
+            elseif(!$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['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++;
         }
 
@@ -182,18 +196,15 @@ class osTicket {
     }
 
     function setErrors($errors) {
-        if(!is_array($errors))
-            return  $this->setError($errors);
-
         $this->errors = $errors;
     }
 
     function getError() {
-        return $this->errors['err'];
+        return $this->system['err'];
     }
 
     function setError($error) {
-        $this->errors['err'] = $error;
+        $this->system['error'] = $error;
     }
 
     function clearError() {
@@ -201,11 +212,11 @@ class osTicket {
     }
 
     function getWarning() {
-        return $this->warning;
+        return $this->system['warning'];
     }
 
-    function setWarning($warn) {
-        $this->warning = $warn;
+    function setWarning($warning) {
+        $this->system['warning'] = $warning;
     }
 
     function clearWarning() {
@@ -213,16 +224,16 @@ class osTicket {
     }
 
 
-    function getMessage() {
-        return $this->message;
+    function getNotice() {
+        return $this->system['notice'];
     }
 
-    function setMessage($msg) {
-        $this->message = $msg;
+    function setNotice($notice) {
+        $this->system['notice'] = $notice;
     }
 
-    function clearMessage() {
-        $this->setMessage('');
+    function clearNotice() {
+        $this->setNotice('');
     }
 
 
@@ -242,7 +253,7 @@ class osTicket {
             $email=$this->getConfig()->getDefaultEmail(); //will take the default email.
 
         if($email) {
-            $email->send($to, $subject, $message);
+            $email->sendAlert($to, $subject, $message);
         } else {//no luck - try the system mail.
             Email::sendmail($to, $subject, $message, sprintf('"osTicket Alerts"<%s>',$to));
         }
diff --git a/include/class.staff.php b/include/class.staff.php
index 9c6078c7f148e3a01612c05cac9c796422871269..102e79ecda4ae642dc7a777f75143b3148078a22 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -302,7 +302,7 @@ class Staff {
     function canEditTickets() {
         return ($this->ht['can_edit_tickets']);
     }
-    
+
     function canDeleteTickets() {
         return ($this->ht['can_delete_tickets']);
     }
@@ -311,6 +311,14 @@ class Staff {
         return ($this->ht['can_close_tickets']);
     }
 
+    function canPostReply() {
+        return ($this->ht['can_post_ticket_reply']);
+    }
+
+    function canViewStaffStats() {
+        return ($this->ht['can_view_staff_stats']);
+    }
+
     function canAssignTickets() {
         return ($this->ht['can_assign_tickets']);
     }
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 50e8173611715153187190c253ea98aec2a77759..5a3fd83f3883c432b17bf89d4ac08d6b4ee95de6 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -867,8 +867,7 @@ class Ticket {
             if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
                 $msg['body'] ="\n$tag\n\n".$msg['body'];
             
-            //TODO: add auto flags....be nice to mail servers and sysadmins!!
-            $email->send($this->getEmail(), $msg['subj'], $msg['body']);
+            $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body']);
         }
         
         if(!($email=$cfg->getAlertEmail()))
@@ -885,7 +884,7 @@ class Ticket {
             //Alert admin??
             if($cfg->alertAdminONNewTicket()) {
                 $alert = str_replace('%{recipient}', 'Admin', $msg['body']);
-                $email->send($cfg->getAdminEmail(), $msg['subj'], $alert);
+                $email->sendAlert($cfg->getAdminEmail(), $msg['subj'], $alert);
                 $sentlist[]=$cfg->getAdminEmail();
             }
               
@@ -901,7 +900,7 @@ class Ticket {
             foreach( $recipients as $k=>$staff){
                 if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue;
                 $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']);
-                $email->send($staff->getEmail(), $msg['subj'], $alert);
+                $email->sendAlert($staff->getEmail(), $msg['subj'], $alert);
                 $sentlist[] = $staff->getEmail();
             }
            
@@ -934,7 +933,7 @@ class Ticket {
             $msg = $this->replaceVars($msg, 
                         array('signature' => ($dept && $dept->isPublic())?$dept->getSignature():''));
             
-            $email->send($this->getEmail(), $msg['subj'], $msg['body']);
+            $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body']);
         }
 
         $client= $this->getClient();
@@ -976,7 +975,7 @@ class Ticket {
             $autorespond=$dept->autoRespONNewMessage();
 
 
-        if(!$autorespond && !$cfg->autoRespONNewMessage()) return;  //no autoresp or alerts.
+        if(!$autorespond || !$cfg->autoRespONNewMessage()) return;  //no autoresp or alerts.
 
         $this->reload();
 
@@ -997,7 +996,7 @@ class Ticket {
             if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator()))
                 $msg['body'] ="\n$tag\n\n".$msg['body'];
         
-            $email->send($this->getEmail(), $msg['subj'], $msg['body']);
+            $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body']);
         }
     }
 
@@ -1056,7 +1055,7 @@ class Ticket {
             foreach( $recipients as $k=>$staff) {
                 if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue;
                 $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']);
-                $email->send($staff->getEmail(), $msg['subj'], $alert);
+                $email->sendAlert($staff->getEmail(), $msg['subj'], $alert);
                 $sentlist[] = $staff->getEmail();
             }
         }
@@ -1109,7 +1108,7 @@ class Ticket {
             foreach( $recipients as $k=>$staff){
                 if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue;
                 $alert = str_replace("%{recipient}", $staff->getFirstName(), $msg['body']);
-                $email->send($staff->getEmail(), $msg['subj'], $alert);
+                $email->sendAlert($staff->getEmail(), $msg['subj'], $alert);
                 $sentlist[] = $staff->getEmail();
             }
 
@@ -1294,7 +1293,7 @@ class Ticket {
             foreach( $recipients as $k=>$staff){
                 if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue;
                 $alert = str_replace('%{recipient}',$staff->getFirstName(), $msg['body']);
-                $email->send($staff->getEmail(), $msg['subj'], $alert);
+                $email->sendAlert($staff->getEmail(), $msg['subj'], $alert);
                 $sentlist[] = $staff->getEmail();
             }
          }
@@ -1451,9 +1450,9 @@ class Ticket {
                 
             $sentlist=array(); //I know it sucks...but..it works.
             foreach( $recipients as $k=>$staff){
-                if(!$staff || !$staff->getEmail() || !$staff->isAvailable() && in_array($staff->getEmail(),$sentlist)) continue;
+                if(!$staff || !$staff->getEmail() || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
                 $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']);
-                $email->send($staff->getEmail(), $msg['subj'], $alert);
+                $email->sendAlert($staff->getEmail(), $msg['subj'], $alert);
                 $sentlist[] = $staff->getEmail();
             }
         }
@@ -1503,7 +1502,7 @@ class Ticket {
                 $msg['body'] ="\n$tag\n\n".$msg['body'];
 
             $attachments =($cfg->emailAttachments() && $files)?$this->getAttachments($respId, 'R'):array();
-            $email->send($this->getEmail(), $msg['subj'], $msg['body'], $attachments);
+            $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body'], $attachments);
         }
 
         return $respId;
@@ -1717,7 +1716,7 @@ class Ticket {
                 if(!$staff || !is_object($staff) || !$staff->getEmail() || !$staff->isAvailable()) continue;
                 if(in_array($staff->getEmail(),$sentlist) || ($staffId && $staffId==$staff->getId())) continue; 
                 $alert = str_replace('%{recipient}',$staff->getFirstName(), $msg['body']);
-                $email->send($staff->getEmail(), $msg['subj'], $alert, $attachments);
+                $email->sendAlert($staff->getEmail(), $msg['subj'], $alert, $attachments);
                 $sentlist[] = $staff->getEmail();
             }
         }
@@ -2019,6 +2018,7 @@ class Ticket {
             //Make sure the email address is not banned
             if(TicketFilter::isBanned($vars['email'])) {
                 $errors['err']='Ticket denied. Error #403';
+                $errors['errno'] = 403;
                 $ost->logWarning('Ticket denied', 'Banned email - '.$vars['email']);
                 return 0;
             }
@@ -2044,6 +2044,7 @@ class Ticket {
         if($ticket_filter 
                 && ($filter=$ticket_filter->shouldReject())) {
             $errors['err']='Ticket denied. Error #403';
+            $errors['errno'] = 403;
             $ost->logWarning('Ticket denied', 
                     sprintf('Ticket rejected ( %s) by filter "%s"', 
                         $vars['email'], $filter->getName()));
diff --git a/include/class.topic.php b/include/class.topic.php
index 900ff76168363c47e9b4763fee6cf2f00334a0fd..ee579e45b2d74e292ff9fd118a5547e32aa2b553 100644
--- a/include/class.topic.php
+++ b/include/class.topic.php
@@ -165,10 +165,13 @@ class Topic {
         return self::getHelpTopics(true);
     }
 
-    function getIdByName($topic) {
-        $sql='SELECT topic_id FROM '.TOPIC_TABLE.' WHERE topic='.db_input($topic);
+    function getIdByName($name, $pid=0) {
+
+        $sql='SELECT topic_id FROM '.TOPIC_TABLE
+            .' WHERE topic='.db_input($name)
+            .' AND topic_pid='.db_input($pid);
         if(($res=db_query($sql)) && db_num_rows($res))
-            list($id)=db_fetch_row($res);
+            list($id) = db_fetch_row($res);
 
         return $id;
     }
@@ -188,7 +191,7 @@ class Topic {
             $errors['topic']='Help topic required';
         elseif(strlen($vars['topic'])<5)
             $errors['topic']='Topic is too short. 5 chars minimum';
-        elseif(($tid=self::getIdByName($vars['topic'])) && $tid!=$id)
+        elseif(($tid=self::getIdByName($vars['topic'], $vars['pid'])) && $tid!=$id)
             $errors['topic']='Topic already exists';
 
         if(!$vars['dept_id'])
diff --git a/include/class.upgrader.php b/include/class.upgrader.php
index 012f88d1160bea1fb09d2104129d19e8fccebd3e..e437fcad7acd8d9b69045eb65655fa0f4ad835a5 100644
--- a/include/class.upgrader.php
+++ b/include/class.upgrader.php
@@ -67,7 +67,7 @@ class Upgrader extends SetupWizard {
 
         $subject = 'Upgrader Error';
         if($email) {
-            $email->send($thisstaff->getEmail(), $subject, $error);
+            $email->sendAlert($thisstaff->getEmail(), $subject, $error);
         } else {//no luck - try the system mail.
             Mailer::sendmail($thisstaff->getEmail(), $subject, $error, sprintf('"osTicket Alerts"<%s>', $thisstaff->getEmail()));
         }
diff --git a/include/class.variable.php b/include/class.variable.php
index f83ba61be9063d385943b4fe4f4fdc751a58f422..7d49ce592855e3d65bb5d41165bcbad6b5346bc9 100644
--- a/include/class.variable.php
+++ b/include/class.variable.php
@@ -98,7 +98,7 @@ class VariableReplacer {
         if(!($vars=$this->_parse($input)))
             return $input;
 
-        return preg_replace($this->_delimit(array_keys($vars)), array_values($vars), $input);
+        return str_replace(array_keys($vars), array_values($vars), $input);
     }
 
     function _resolveVar($var) {
@@ -134,14 +134,5 @@ class VariableReplacer {
 
         return $vars;
     }
-
-    //Helper function - will be replaced by a lambda function (PHP 5.3+)
-    function _delimit($val, $d='/') {
-
-        if($val && is_array($val))
-            return array_map(array($this, '_delimit'), $val);
-
-        return $d.$val.$d;
-    }
 }
 ?>
diff --git a/include/class.xml.php b/include/class.xml.php
index 854f182372c94f57338b8b45a94d155f69f5bd33..56baf4fbccaf65985921e081afd8e5f475c95c07 100644
--- a/include/class.xml.php
+++ b/include/class.xml.php
@@ -77,10 +77,14 @@ class XmlDataParser {
         $this->content = array_pop($this->stack);
         $i = 1;
         if (array_key_exists($name, $this->content)) {
-            while (array_key_exists("$name$i", $this->content)) $i++;
-            $name = "$name$i";
-        }
-        $this->content[$name] = $prev;
+            if(!isset($this->content[$name][0])) {
+                $current = $this->content[$name];
+                unset($this->content[$name]);
+                $this->content[$name][0] = $current;
+            }
+            $this->content[$name][] = $prev;
+        } else
+            $this->content[$name] = $prev;
     }
 
     function content($parser, $data) {
diff --git a/include/client/open.inc.php b/include/client/open.inc.php
index 2fd076100388888765970d1a561dc3c5235a8b90..275e856dea07f8726f06b3fa6641392e459bb2e4 100644
--- a/include/client/open.inc.php
+++ b/include/client/open.inc.php
@@ -57,7 +57,7 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):$info;
         <td class="required">Help Topic:</td>
         <td>
             <select id="topicId" name="topicId">
-                <option value="" selected="selected">&mdash; Select a Help Topics &mdash;</option>
+                <option value="" selected="selected">&mdash; Select a Help Topic &mdash;</option>
                 <?php
                 if($topics=Topic::getPublicHelpTopics()) {
                     foreach($topics as $id =>$name) {
@@ -82,7 +82,7 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):$info;
     <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>
+            <div><em>Please provide as much detail 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>
diff --git a/include/staff/apikey.inc.php b/include/staff/apikey.inc.php
index ff5592b46fd57b374bb0a2d0825f99d3156bceef..6e2ffe6e6d8fdc4d41134726e6853162f3bfe0da 100644
--- a/include/staff/apikey.inc.php
+++ b/include/staff/apikey.inc.php
@@ -6,9 +6,7 @@ if($api && $_REQUEST['a']!='add'){
     $title='Update API Key';
     $action='update';
     $submit_text='Save Changes';
-    $info['id']=$api->getId();
-    $info['isactive']=$api->isActive()?1:0;
-    $info['notes']=$api->getNotes();
+    $info=$api->getHashtable();
     $qstr.='&id='.$api->getId();
 }else {
     $title='Add New API Key';
@@ -30,13 +28,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
         <tr>
             <th colspan="2">
                 <h4><?php echo $title; ?></h4>
-                <em>API Key is autogenerated and unique per IP address.</em>
+                <em>API Key is auto-generated. Delete and re-add to change the key.</em>
             </th>
         </tr>
     </thead>
     <tbody>
         <tr>
-            <td width="180" class="required">
+            <td width="150" class="required">
                 Status:
             </td>
             <td>
@@ -47,7 +45,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
         </tr>
         <?php if($api){ ?>
         <tr>
-            <td width="180">
+            <td width="150">
                 IP Address:
             </td>
             <td>
@@ -55,14 +53,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
             </td>
         </tr>
         <tr>
-            <td width="180">
+            <td width="150">
                 API Key:
             </td>
-            <td><?php echo $api->getKey(); ?> &nbsp;&nbsp;<em>(Delete and re-add to change the key)</em></td>
+            <td><?php echo $api->getKey(); ?> &nbsp;</td>
         </tr>
         <?php }else{ ?>
         <tr>
-            <td width="180" class="required">
+            <td width="150" class="required">
                IP Address:
             </td>
             <td>
@@ -73,16 +71,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
         <?php } ?>
         <tr>
             <th colspan="2">
-                <em><strong>Enabled Services:</strong>: Check applicable API services.</em>
+                <em><strong>Enabled Services:</strong>: Check applicable API services. All active keys can make cron call.</em>
             </th>
         </tr>
         <tr>
-            <td width="180">
-                Email piping:
-            </td>
-            <td>
-                <input type="checkbox" name="email_piping" value="1" checked="checked" disabled="disabled" >
-                    <strong>Enable</strong> remote email piping.
+            <td colspan=2 style="padding-left:5px">
+                <input type="checkbox" name="can_create_tickets" value="1" <?php echo $info['can_create_tickets']?'checked="checked"':''; ?> >
+                Can Create Tickets. <em>(XML/JSON/PIPE)</em>
             </td>
         </tr>
         <tr>
diff --git a/include/staff/apikeys.inc.php b/include/staff/apikeys.inc.php
index b085329343d101fe69de9b4628ed13ace6d6586b..90b4e31eef10af37b71ba2ebef6edffc9dc63d7b 100644
--- a/include/staff/apikeys.inc.php
+++ b/include/staff/apikeys.inc.php
@@ -5,7 +5,7 @@ $qstr='';
 $sql='SELECT * FROM '.API_KEY_TABLE.' WHERE 1';
 $sortOptions=array('key'=>'apikey','status'=>'isactive','ip'=>'ipaddr','date'=>'created','created'=>'created','updated'=>'updated');
 $orderWays=array('DESC'=>'DESC','ASC'=>'ASC');
-$sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'date';
+$sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'key';
 //Sorting options...
 if($sort && $sortOptions[$sort]) {
     $order_column =$sortOptions[$sort];
@@ -54,10 +54,10 @@ else
     <thead>
         <tr>
             <th width="7">&nbsp;</th>        
-            <th width="150" nowrap><a  <?php echo $date_sort; ?>href="apikeys.php?<?php echo $qstr; ?>&sort=date">Date Added</a></th>
             <th width="320"><a <?php echo $key_sort; ?> href="apikeys.php?<?php echo $qstr; ?>&sort=key">API Key</a></th>
-            <th width="100"><a  <?php echo $status_sort; ?> href="apikeys.php?<?php echo $qstr; ?>&sort=status">Status</a></th>
             <th width="120"><a  <?php echo $ip_sort; ?> href="apikeys.php?<?php echo $qstr; ?>&sort=ip">IP Addr.</a></th>
+            <th width="100"><a  <?php echo $status_sort; ?> href="apikeys.php?<?php echo $qstr; ?>&sort=status">Status</a></th>
+            <th width="150" nowrap><a  <?php echo $date_sort; ?>href="apikeys.php?<?php echo $qstr; ?>&sort=date">Date Added</a></th>
             <th width="150" nowrap><a  <?php echo $updated_sort; ?>href="apikeys.php?<?php echo $qstr; ?>&sort=updated">Last Updated</a></th>
         </tr>
     </thead>
@@ -75,10 +75,10 @@ else
                 <td width=7px>
                   <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $row['id']; ?>" 
                             <?php echo $sel?'checked="checked"':''; ?>> </td>
-                <td>&nbsp;<?php echo Format::db_date($row['created']); ?></td>
                 <td>&nbsp;<a href="apikeys.php?id=<?php echo $row['id']; ?>"><?php echo Format::htmlchars($row['apikey']); ?></a></td>
-                <td><?php echo $row['isactive']?'Active':'<b>Disabled</b>'; ?></td>
                 <td><?php echo $row['ipaddr']; ?></td>
+                <td><?php echo $row['isactive']?'Active':'<b>Disabled</b>'; ?></td>
+                <td>&nbsp;<?php echo Format::db_date($row['created']); ?></td>
                 <td>&nbsp;<?php echo Format::db_datetime($row['updated']); ?></td>
             </tr>
             <?php
diff --git a/include/staff/footer.inc.php b/include/staff/footer.inc.php
index d789c14474ef63caf5c06f61d60cdfb6df881bcc..90373479f3b1270bc0375826953a92c6657db403 100644
--- a/include/staff/footer.inc.php
+++ b/include/staff/footer.inc.php
@@ -13,5 +13,9 @@ if(is_object($thisstaff) && $thisstaff->isStaff()) { ?>
 } ?>
 </div>
 <div id="overlay"></div>
+<div id="loading">
+    <h4>Please Wait!</h4>
+    <p>Please wait... it will take a second!</p>
+</div>
 </body>
 </html>
diff --git a/include/staff/group.inc.php b/include/staff/group.inc.php
index f2935d72e56237ce35dbc701f563fdeaf475a829..bfcc2a596a54850211a71ff72ea95cb698ecdb57 100644
--- a/include/staff/group.inc.php
+++ b/include/staff/group.inc.php
@@ -76,6 +76,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 &nbsp;&nbsp;<i>Ability to edit tickets.</i>
             </td>
         </tr>
+        <tr><td>Can <b>Post Reply</b></td>
+            <td>
+                <input type="radio" name="can_post_ticket_reply"  value="1"   <?php echo $info['can_post_ticket_reply']?'checked="checked"':''; ?> />Yes
+                &nbsp;&nbsp;
+                <input type="radio" name="can_post_ticket_reply"  value="0"   <?php echo !$info['can_post_ticket_reply']?'checked="checked"':''; ?> />No
+                &nbsp;&nbsp;<i>Ability to post a ticket reply.</i>
+            </td>
+        </tr>
         <tr><td>Can <b>Close</b> Tickets</td>
             <td>
                 <input type="radio" name="can_close_tickets"  value="1" <?php echo $info['can_close_tickets']?'checked="checked"':''; ?> />Yes
@@ -105,7 +113,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 <input type="radio" name="can_delete_tickets"  value="1"   <?php echo $info['can_delete_tickets']?'checked="checked"':''; ?> />Yes
                 &nbsp;&nbsp;
                 <input type="radio" name="can_delete_tickets"  value="0"   <?php echo !$info['can_delete_tickets']?'checked="checked"':''; ?> />No
-                &nbsp;&nbsp;<i>Deleted tickets can't be recovered!</i>
+                &nbsp;&nbsp;<i>Ability to delete tickets (Deleted tickets can't be recovered!)</i>
             </td>
         </tr>
         <tr><td>Can Ban Emails</td>
@@ -132,6 +140,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 &nbsp;&nbsp;<i>Ability to add/update/disable/delete knowledgebase categories and FAQs.</i>
             </td>
         </tr>
+        <tr><td>Can View Staff Stats.</td>
+            <td>
+                <input type="radio" name="can_view_staff_stats"  value="1" <?php echo $info['can_view_staff_stats']?'checked="checked"':''; ?> />Yes
+                &nbsp;&nbsp;
+                <input type="radio" name="can_view_staff_stats"  value="0" <?php echo !$info['can_view_staff_stats']?'checked="checked"':''; ?> />No
+                &nbsp;&nbsp;<i>Ability to view stats of other staff members in allowed departments.</i>
+            </td>
+        </tr>
         <tr>
             <th colspan="2">
                 <em><strong>Department Access</strong>: Check all departments the group members are allowed to access.&nbsp;&nbsp;&nbsp;<a id="selectAll" href="#deptckb">Select All</a>&nbsp;&nbsp;<a id="selectNone" href="#deptckb">Select None</a>&nbsp;&nbsp;</em>
diff --git a/include/staff/header.inc.php b/include/staff/header.inc.php
index a349cb3c6ecb662f2d82f64086fc4984e295c263..8475f328606a9e70892a97daef6ebb0e96aa7352 100644
--- a/include/staff/header.inc.php
+++ b/include/staff/header.inc.php
@@ -31,6 +31,14 @@
 </head>
 <body>
 <div id="container">
+    <?php
+    if($ost->getError())
+        echo sprintf('<div id="error_bar">%s</div>', $ost->getError());
+    elseif($ost->getWarning())
+        echo sprintf('<div id="warning_bar">%s</div>', $ost->getWarning());
+    elseif($ost->getNotice())
+        echo sprintf('<div id="notice_bar">%s</div>', $ost->getNotice());
+    ?>
     <div id="header">
         <a href="index.php" id="logo">osTicket - Customer Support System</a>
         <p id="info">Howdy, <strong><?php echo $thisstaff->getUserName(); ?></strong>
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index c2cb0bd1067684f7a012aa4f1a44cc73dc8ee904..b14a4654af003d0c5dd6b85635d8d91a63048940 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -348,7 +348,11 @@ if(!$cfg->showNotesInline()) { ?>
 
 <div id="response_options">
     <ul>
+        <?php
+        if($thisstaff->canPostReply()) { ?>
         <li><a id="reply_tab" href="#reply">Post Reply</a></li>
+        <?php
+        } ?>
         <li><a id="note_tab" href="#note">Post Internal Note</a></li>
         <?php
         if($thisstaff->canTransferTickets()) { ?>
@@ -361,12 +365,12 @@ if(!$cfg->showNotesInline()) { ?>
         <?php
         } ?>
     </ul>
-
+    <?php
+    if($thisstaff->canPostReply()) { ?>
     <form id="reply" action="tickets.php?id=<?php echo $ticket->getId(); ?>#reply" name="reply" method="post" enctype="multipart/form-data">
         <?php csrf_token(); ?>
         <input type="hidden" name="id" value="<?php echo $ticket->getId(); ?>">
         <input type="hidden" name="msgId" value="<?php echo $msgId; ?>">
-        <input type="hidden" name="locktime" value="<?php echo $cfg->getLockTime(); ?>">
         <input type="hidden" name="a" value="reply">
         <span class="error"></span>
         <table border="0" cellspacing="0" cellpadding="3">
@@ -485,9 +489,12 @@ if(!$cfg->showNotesInline()) { ?>
             <input class="btn_sm" type="reset" value="Reset">
         </p>
     </form>
+    <?php
+    } ?>
     <form id="note" action="tickets.php?id=<?php echo $ticket->getId(); ?>#note" name="note" method="post" enctype="multipart/form-data">
         <?php csrf_token(); ?>
         <input type="hidden" name="id" value="<?php echo $ticket->getId(); ?>">
+        <input type="hidden" name="locktime" value="<?php echo $cfg->getLockTime(); ?>">
         <input type="hidden" name="a" value="postnote">
         <table border="0" cellspacing="0" cellpadding="3">
             <?php 
@@ -507,7 +514,7 @@ if(!$cfg->showNotesInline()) { ?>
                         <span class="error">*&nbsp;<?php echo $errors['note']; ?></span></div>
                     <textarea name="note" id="internal_note" cols="80" rows="9" wrap="soft"><?php echo $info['note']; ?></textarea><br>
                     <div>
-                        <span class="faded">Note title - sumarry of the note (optional)</span>&nbsp;
+                        <span class="faded">Note title - summarry of the note (optional)</span>&nbsp;
                         <span class="error"&nbsp;<?php echo $errors['title']; ?></span>
                     </div>
                     <input type="text" name="title" id="title" size="60" value="<?php echo $info['title']; ?>" >
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index 534a358bfcc01bbb771340f78db2b5fcd7924cbd..d6fab690cdcd76cc9a8c7dd9e590a6c60231a6b4 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -190,35 +190,40 @@ $sortOptions=array('date'=>'ticket.created','ID'=>'ticketID','pri'=>'priority_ur
 $orderWays=array('DESC'=>'DESC','ASC'=>'ASC');
 
 //Sorting options...
+$queue = isset($_REQUEST['status'])?strtolower($_REQUEST['status']):$status;
 $order_by=$order=null;
 if($_REQUEST['sort'] && $sortOptions[$_REQUEST['sort']])
     $order_by =$sortOptions[$_REQUEST['sort']];
-elseif(!strcasecmp($status, 'open') && !$showanswered && $sortOptions[$_SESSION['tickets']['sort']]) {
-    $_REQUEST['sort'] = $_SESSION['tickets']['sort'];
-    $order_by = $sortOptions[$_SESSION['tickets']['sort']];
-    $order = $_SESSION['tickets']['order'];
+elseif($sortOptions[$_SESSION[$queue.'_tickets']['sort']]) {
+    $_REQUEST['sort'] = $_SESSION[$queue.'_tickets']['sort'];
+    $_REQUEST['order'] = $_SESSION[$queue.'_tickets']['order'];
+
+    $order_by = $sortOptions[$_SESSION[$queue.'_tickets']['sort']];
+    $order = $_SESSION[$queue.'_tickets']['order'];
 }
 
 if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])])
     $order=$orderWays[strtoupper($_REQUEST['order'])];
 
 //Save sort order for sticky sorting.
-if(!strcasecmp($status, 'open') && $_REQUEST['sort']) {
-    $_SESSION['tickets']['sort'] = $_REQUEST['sort'];
-    $_SESSION['tickets']['order'] = $_REQUEST['order'];
+if($_REQUEST['sort'] && $queue) {
+    $_SESSION[$queue.'_tickets']['sort'] = $_REQUEST['sort'];
+    $_SESSION[$queue.'_tickets']['order'] = $_REQUEST['order'];
 }
 
-if(!$order_by && $showanswered) {
-    $order_by='ticket.lastresponse, ticket.created'; //No priority sorting for answered tickets.
-}elseif(!$order_by && !strcasecmp($status,'closed')){
-    $order_by='ticket.closed, ticket.created'; //No priority sorting for closed tickets.
+//Set default sort by columns.
+if(!$order_by ) {
+    if($showanswered) 
+        $order_by='ticket.lastresponse, ticket.created'; //No priority sorting for answered tickets.
+    elseif(!strcasecmp($status,'closed'))
+        $order_by='ticket.closed, ticket.created'; //No priority sorting for closed tickets.
+    else
+        $order_by='priority_urgency ASC, effective_date, ticket.created';
 }
 
-$order_by =$order_by?$order_by:'priority_urgency, effective_date, ticket.created';
-$order=$order?$order:'ASC';
-
-if($order_by && strpos($order_by,','))
-    $order_by=str_replace(','," $order,",$order_by);
+$order=$order?$order:'DESC';
+if($order_by && strpos($order_by,',') && $order)
+    $order_by=preg_replace('/(?<!ASC|DESC),/', " $order,", $order_by);
 
 $sort=$_REQUEST['sort']?strtolower($_REQUEST['sort']):'urgency'; //Urgency is not on display table.
 $x=$sort.'_sort';
@@ -302,7 +307,7 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting..
  <a class="refresh" href="<?php echo $_SERVER['REQUEST_URI']; ?>">Refresh</a>
  <input type="hidden" name="a" value="mass_process" >
  <input type="hidden" name="do" id="action" value="" >
- <input type="hidden" name="status" value="<?php echo $status; ?>" >
+ <input type="hidden" name="status" value="<?php echo $_REQUEST['status']; ?>" >
  <table class="list" border="0" cellspacing="1" cellpadding="2" width="940">
     <caption><?php echo $showing; ?>&nbsp;&nbsp;&nbsp;<?php echo $results_type; ?></caption>
     <thead>
@@ -468,6 +473,11 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting..
                     <input class="button" type="submit" name="close" value="Close">
                     <?php
                     break;
+                case 'overdue':
+                    ?>
+                    <input class="button" type="submit" name="close" value="Close">
+                    <?php
+                    break;
                 default: //search??
                     ?>
                     <input class="button" type="submit" name="close" value="Close" >
diff --git a/include/upgrader/sql/00ff231f-9f3b454c.patch.sql b/include/upgrader/sql/00ff231f-9f3b454c.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..7c3e3bd3c681e1e75f07cb87fe5496c5116da859
--- /dev/null
+++ b/include/upgrader/sql/00ff231f-9f3b454c.patch.sql
@@ -0,0 +1,38 @@
+/**
+ * @version v1.7 RC4
+ * @signature 9f3b454c06dfd5ee96003eae5182ac13
+ *
+ *  - Supports starts- and ends-with in ticket filter rules
+ *  - Fix assigned template variable
+ *  - Allow nested templates to have duplicate names
+ *  - New permission settings for API key  & groups
+ */
+
+ALTER TABLE  `%TABLE_PREFIX%filter_rule` CHANGE  `how`  `how` ENUM(  'equal',
+    'not_equal',  'contains',  'dn_contain',  'starts',  'ends' );
+
+-- templates -> %message
+UPDATE `%TABLE_PREFIX%email_template`
+    SET `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%message', '%{comments}');
+
+-- API Access.
+ALTER TABLE  `%TABLE_PREFIX%api_key`
+    CHANGE  `isactive`  `isactive` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT  '1',
+    ADD  `can_create_tickets` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT  '1' AFTER  `apikey`,
+    DROP INDEX  `ipaddr`,
+    ADD INDEX  `ipaddr` (  `ipaddr` );
+
+-- Help topics 
+ALTER TABLE  `%TABLE_PREFIX%help_topic` 
+    DROP INDEX  `topic` ,
+    ADD UNIQUE  `topic` (  `topic` ,  `topic_pid` );
+
+
+-- group settings.
+ALTER TABLE  `%TABLE_PREFIX%groups` 
+    ADD  `can_post_ticket_reply` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT  '1' AFTER  `can_transfer_tickets` ,
+    ADD  `can_view_staff_stats` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT  '0' AFTER  `can_post_ticket_reply`;
+
+-- update schema signature.
+UPDATE `%TABLE_PREFIX%config`
+    SET `schema_signature`='9f3b454c06dfd5ee96003eae5182ac13';
diff --git a/include/upgrader/upgrade.inc.php b/include/upgrader/upgrade.inc.php
index 7c8a8aae47e692d9337a51c46a46f2114c4a2848..fae6947d94849c6cfbd4a7df849df708e470f1de 100644
--- a/include/upgrader/upgrade.inc.php
+++ b/include/upgrader/upgrade.inc.php
@@ -32,7 +32,7 @@ $action=$upgrader->getNextAction();
             <p>3. We can help, feel free to <a href="http://osticket.com/support/" target="_blank">contact us </a> for professional help.</p>
     </div>
     <div class="clear"></div>
-    <div id="loading">
+    <div id="upgrading">
         <h4><?php echo $action; ?></h4>
         Please wait... while we upgrade your osTicket installation!
         <div id="msg" style="font-weight: bold;padding-top:10px;">Smile!</div>
diff --git a/l.php b/l.php
index 5e605c73cb3d48c32409c8ff89f727199bcbbba3..286a17299cd1e51850b3760ed4e6d264244f7974 100644
--- a/l.php
+++ b/l.php
@@ -21,7 +21,7 @@ if (!$url || !Validator::is_url($url)) exit('Invalid url');
 <html>
 <head>
     <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
-    <meta http-equiv="refresh" content="0;<?php echo $url; ?>"/>
+    <meta http-equiv="refresh" content="0;URL=<?php echo $url; ?>"/>
 </head>
 <body/>
 </html>
diff --git a/main.inc.php b/main.inc.php
index 4a775b233bb00f45331b891e5f6fe1d4f202268d..43aeca4d617c5d5eec310df9899611073ddf9250 100644
--- a/main.inc.php
+++ b/main.inc.php
@@ -62,8 +62,8 @@
     /*############## Do NOT monkey with anything else beyond this point UNLESS you really know what you are doing ##############*/
 
     #Current version && schema signature (Changes from version to version)
-    define('THIS_VERSION','1.7-RC3'); //Shown on admin panel
-    define('SCHEMA_SIGNATURE','00ff231f2ade8797a0e7f2a7fccd52f4'); //MD5 signature of the db schema. (used to trigger upgrades)
+    define('THIS_VERSION','1.7-RC4'); //Shown on admin panel
+    define('SCHEMA_SIGNATURE','9f3b454c06dfd5ee96003eae5182ac13'); //MD5 signature of the db schema. (used to trigger upgrades)
     #load config info
     $configfile='';
     if(file_exists(ROOT_DIR.'ostconfig.php')) //Old installs prior to v 1.6 RC5
diff --git a/scp/admin.inc.php b/scp/admin.inc.php
index 8a41c54db7f690b3caec9ee7728345bba1570000..66fca5d976bb3b9c4a76eed99b71657ab28f0d64 100644
--- a/scp/admin.inc.php
+++ b/scp/admin.inc.php
@@ -25,6 +25,7 @@ define('OSTADMININC',TRUE); //checked by admin include files
 define('ADMINPAGE',TRUE);   //Used by the header to swap menus.
 
 //Some security related warnings - bitch until fixed!!! :)
+$sysnotice= '';
 if($ost->isUpgradePending()) {
     $errors['err']=$sysnotice='System upgrade is pending <a href="upgrade.php">Upgrade Now</a>';
     if(!in_array(basename($_SERVER['SCRIPT_NAME']), array('upgrade.php', 'logs.php'))) {
@@ -57,6 +58,9 @@ if($ost->isUpgradePending()) {
         $sysnotice='Please consider turning off register globals if possible';
 }
 
+//System notice displayed as a warning (if any).
+$ost->setWarning($sysnotice);
+
 //Admin navigation - overwrites what was set in staff.inc.php
 $nav = new AdminNav($thisstaff);
 
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 59964d4e61561ba78bc704e3e6ddb2d1a73af964..04bd90d604e5a6265b159e49279792db58fbe4ef 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -43,12 +43,18 @@ a {
     color: #555;
 }
 
-#msg_notice { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #0a0; background: url('../images/icons/ok.png?1300763726') 10px 50% no-repeat #e0ffe0; }
+#msg_notice { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #0a0; background: url('../images/icons/ok.png') 10px 50% no-repeat #e0ffe0; }
 
-#msg_warning { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #f26522; background: url('../images/icons/alert.png?1307823786') 10px 50% no-repeat #ffffdd; }
+#msg_warning { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #f26522; background: url('../images/icons/alert.png') 10px 50% no-repeat #ffffdd; }
 
 #msg_error { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #a00; background: url('../images/icons/error.png') 10px 50% no-repeat #fff0f0; }
 
+#notice_bar { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; border: 1px solid #0a0; background: url('../images/icons/ok.png') 10px 50% no-repeat #e0ffe0; }
+
+#warning_bar { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; border: 1px solid #f26522; background: url('../images/icons/alert.png') 10px 50% no-repeat #ffffdd; }
+
+#error_bar { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; border: 1px solid #a00; background: url('../images/icons/error.png') 10px 50% no-repeat #fff0f0; }
+
 
 #container {
     width:960px;
@@ -311,6 +317,7 @@ a.Icon:hover {
 .Icon.webTicket { background:url(../images/icons/ticket_source_web.gif) 0 0 no-repeat; }
 .Icon.emailTicket { background:url(../images/icons/ticket_source_email.gif) 0 0 no-repeat; }
 .Icon.phoneTicket { background:url(../images/icons/ticket_source_phone.gif) 0 0 no-repeat; }
+.Icon.apiTicket { background:url(../images/icons/ticket_source_other.gif) 0 0 no-repeat; }
 .Icon.otherTicket { background:url(../images/icons/ticket_source_other.gif) 0 0 no-repeat; }
 .Icon.overdueTicket { background:url(../images/icons/overdue_ticket.gif) 0 0 no-repeat; }
 .Icon.assignedTicket { background:url(../images/icons/assigned_ticket.gif) 0 0 no-repeat; }
@@ -1372,8 +1379,28 @@ ul.progress li.no small {color:red;}
 #bar.error { background: #ffd; text-align: center; color: #a00; font-weight: bold; }
 
 /* Overlay */
-#overlay { display: none; position: fixed; background: #000; z-index: 1000; }
+#overlay { 
+    display: none; 
+    position: fixed; 
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: #000;
+    z-index: 1000;
+    -webkit-transform: translate3d(0,0,0); 
+}
+
+#loading, #upgrading {
+    border:1px solid #2a67ac;
+    padding: 10px 10px 10px 60px; 
+    width: 300px; 
+    height: 100px; 
+    background: rgb( 255, 255, 255) url('../images/FhHRx-Spinner.gif') 10px 50% no-repeat;
+    position: fixed; 
+    display: none; 
+    z-index: 3000; 
+}
 
-#loading { padding: 10px 10px 10px 60px; width: 300px; height: 100px; background: url('../images/ajax-loader.gif?1312925608') 10px 50% no-repeat white; position: fixed; display: none; z-index: 3000; }
-#loading h4 { margin: 3px 0 0 0; padding: 0; color: #d80; }
+#loading h4, #upgrading h4 { margin: 3px 0 0 0; padding: 0; color: #d80; }
 
diff --git a/scp/emailtest.php b/scp/emailtest.php
index 0ca1d7308cc8c62f0f9852a57f45d4e8802e805a..b39e5c7edaf82004217ad39f6c9bf8fe6b99e995 100644
--- a/scp/emailtest.php
+++ b/scp/emailtest.php
@@ -47,7 +47,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
 $nav->setTabActive('emails');
 require(STAFFINC_DIR.'header.inc.php');
 ?>
-<form action="emailtest.php" method="post" id="emailtest">
+<form action="emailtest.php" method="post" id="save">
  <?php csrf_token(); ?>
  <input type="hidden" name="do" value="<?php echo $action; ?>">
  <h2>Test Outgoing Email</h2>
diff --git a/scp/images/FhHRx-Spinner.gif b/scp/images/FhHRx-Spinner.gif
new file mode 100644
index 0000000000000000000000000000000000000000..574b1d4a161707c6c7f0ba9d2bc531bead4a3028
Binary files /dev/null and b/scp/images/FhHRx-Spinner.gif differ
diff --git a/scp/js/dashboard.inc.js b/scp/js/dashboard.inc.js
index 0f9e31a54dccdff96388d7304b0187f2d8dc9fad..ba19235f12ec5198cb2c074bc083f37b1344ed4d 100644
--- a/scp/js/dashboard.inc.js
+++ b/scp/js/dashboard.inc.js
@@ -9,9 +9,7 @@
         $.ajax({
             method:     'GET',
             url:        'ajax.php/report/overview/graph',
-            data:       ((this.start && this.start.value) ? {
-                'start': this.start.value,
-                'stop': this.period.value} : {}),
+            data:       $(this).serialize(),
             dataType:   'json',
             success:    function(json) {
                 var times = [],
@@ -136,6 +134,7 @@
             start = this.start.value || 'last month';
             stop = this.period.value || 'now';
         }
+
         var group = current_tab.attr('table-group');
         $.ajax({
             method:     'GET',
@@ -250,7 +249,7 @@
 
                 // ------------------------> Export <-----------------------
                 $('<a>').attr({'href':'ajax.php/report/overview/table/export?group='
-                        +group}).append('Export')
+                        +group+'&start='+start+'&stop='+stop}).append('Export')
                     .appendTo($('<li>')
                     .appendTo(p));
 
@@ -259,6 +258,10 @@
         });
         return false;
     }
-    $(refresh);
-    $(function() { $('#timeframe-form').submit(refresh); });
+   
+    $(function() { 
+        $('#timeframe-form').submit(refresh);
+        //Trigger submit now...init.
+        $('#timeframe-form').submit(); 
+        });
 })(window.jQuery);
diff --git a/scp/js/scp.js b/scp/js/scp.js
index 71d0e69a823e0869f38ec3e9a5562bec296a7baa..cf32e42d8b638ff1b465cb23eb9a196e72be31ba 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -103,6 +103,14 @@ $(document).ready(function(){
         return false;
      });
 
+    $(window).scroll(function () {
+         
+        $('.dialog').css({
+            top  : (($(this).height() /5)+$(this).scrollTop()),
+            left : ($(this).width() / 2 - 300)
+         });
+     });
+
     if($.browser.msie) {
         $('.inactive').mouseenter(function() {
             var elem = $(this);
@@ -137,9 +145,9 @@ $(document).ready(function(){
         }
     });
 
-    $('form#save').submit(function() {
+    $('form#save, form:has(table.list)').submit(function() {
         $(window).unbind('beforeunload');
-
+        $('#overlay, #loading').show();
         return true;
      });
 
@@ -316,9 +324,7 @@ $(document).ready(function(){
     $('#overlay').css({
         opacity : 0.3,
         top     : 0,
-        left    : 0,
-        width   : $(window).width(),
-        height  : $(window).height()
+        left    : 0
     });
        
     //Dialog
@@ -341,6 +347,12 @@ $(document).ready(function(){
         left : ($(window).width() / 2 - 300)
     });
 
+    /* loading ... */    
+    $("#loading").css({
+        top  : ($(window).height() / 3),
+        left : ($(window).width() / 2 - 160)
+        });
+
     $('#go-advanced').click(function(e) {
         e.preventDefault();
         $('#result-count').html('');
diff --git a/scp/js/ticket.js b/scp/js/ticket.js
index 65ee84aad0882649f8aec44daf447b45d38e64d9..8cb792d1c403661063ea564c3bff3b8938bc9d50 100644
--- a/scp/js/ticket.js
+++ b/scp/js/ticket.js
@@ -94,7 +94,7 @@ var autoLock = {
     Init: function(config) {
 
         //make sure we are on ticket view page & locking is enabled!
-        var fObj=$('form#reply');
+        var fObj=$('form#note');
         if(!fObj 
                 || !$(':input[name=id]',fObj).length 
                 || !$(':input[name=locktime]',fObj).length
diff --git a/scp/js/upgrader.js b/scp/js/upgrader.js
index 09bde7766c6f39e9f95e0b50ccf3c0efc3bbb2fb..1631ac5cd5a7a156498f44e6f13020dae875dde2 100644
--- a/scp/js/upgrader.js
+++ b/scp/js/upgrader.js
@@ -8,7 +8,7 @@ jQuery(function($) {
         height  : $(window).height()
         });
 
-    $("#loading").css({
+    $("#upgrading").css({
         top  : ($(window).height() / 3),
         left : ($(window).width() / 2 - 160)
         });
@@ -17,7 +17,7 @@ jQuery(function($) {
         e.preventDefault();
         var form = $(this);
         $('input[type=submit]', this).attr('disabled', 'disabled');
-        $('#overlay, #loading').show();
+        $('#overlay, #upgrading').show();
         doTasks('upgrade.php',form.serialize());
 
         return false;
diff --git a/scp/l.php b/scp/l.php
index 93fff3a24894612017f53ff0bb0a119b656f8b9d..dec8c0a6a52dd62c0d02f3edebfeeeaa79c0c8fa 100644
--- a/scp/l.php
+++ b/scp/l.php
@@ -21,7 +21,7 @@ if (!$url || !Validator::is_url($url)) exit('Invalid url');
 <html>
 <head>
     <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
-    <meta http-equiv="refresh" content="0;<?php echo $url; ?>"/>
+    <meta http-equiv="refresh" content="0; URL=<?php echo $url; ?>"/>
 </head>
 <body/>
 </html>
diff --git a/scp/staff.inc.php b/scp/staff.inc.php
index 8553a8bac1fef78482532de7789723af43e6c20d..8a91e4774803886deed8abce8e62bab01ffaff87 100644
--- a/scp/staff.inc.php
+++ b/scp/staff.inc.php
@@ -119,10 +119,11 @@ $nav = new StaffNav($thisstaff);
 if($thisstaff->forcePasswdChange() && !$exempt) {
     # XXX: Call staffLoginPage() for AJAX and API requests _not_ to honor
     #      the request
+    $sysnotice = 'Password change required to continue';
     require('profile.php'); //profile.php must request this file as require_once to avoid problems.
     exit;
 }
-
+$ost->setWarning($sysnotice);
 $ost->setPageTitle('osTicket :: Staff Control Panel');
 
 ?>
diff --git a/scp/tickets.php b/scp/tickets.php
index 47395d592c2da9cb505daec7d037fb1349711dfa..17384ae50b627ab56bb1b8e9fe5c0df01034e062 100644
--- a/scp/tickets.php
+++ b/scp/tickets.php
@@ -42,18 +42,23 @@ if($_POST && !$errors):
         $statusKeys=array('open'=>'Open','Reopen'=>'Open','Close'=>'Closed');
         switch(strtolower($_POST['a'])):
         case 'reply':
+            if(!$thisstaff->canPostReply())
+                $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!';
+                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.';
+                //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.
@@ -341,7 +346,7 @@ if($_POST && !$errors):
                                 foreach($_POST['tids'] as $k=>$v) {
                                     if(($t=Ticket::lookup($v)) && $t->isClosed() && @$t->reopen()) {
                                         $i++;
-                                        $t->logNote('Ticket Reopened', $note);
+                                        $t->logNote('Ticket Reopened', $note, $thisstaff);
                                     }
                                 }
 
@@ -361,7 +366,7 @@ if($_POST && !$errors):
                                 foreach($_POST['tids'] as $k=>$v) {
                                     if(($t=Ticket::lookup($v)) && $t->isOpen() && @$t->close()) { 
                                         $i++;
-                                        $t->logNote('Ticket Closed', $note);
+                                        $t->logNote('Ticket Closed', $note, $thisstaff);
                                     }
                                 }
 
@@ -380,7 +385,7 @@ if($_POST && !$errors):
                             foreach($_POST['tids'] as $k=>$v) {
                                 if(($t=Ticket::lookup($v)) && !$t->isOverdue() && $t->markOverdue()) {
                                     $i++;
-                                    $t->logNote('Ticket Marked Overdue', $note);
+                                    $t->logNote('Ticket Marked Overdue', $note, $thisstaff);
                                 }
                             }
 
@@ -470,8 +475,8 @@ if($cfg->showAnsweredTickets()) {
 }
 
 if($stats['assigned']) {
-    if(!$sysnotice && $stats['assigned']>10)
-        $sysnotice=$stats['assigned'].' assigned to you!';
+    if(!$ost->getWarning() && $stats['assigned']>10)
+        $ost->setWarning($stats['assigned'].' tickets assigned to you! Do something about it!');
 
     $nav->addSubMenu(array('desc'=>'My Tickets ('.$stats['assigned'].')',
                            'title'=>'Assigned Tickets',
diff --git a/setup/doc/api.md b/setup/doc/api.md
new file mode 100644
index 0000000000000000000000000000000000000000..7509616aa2406b842f56cacd7a6c0693b23415ca
--- /dev/null
+++ b/setup/doc/api.md
@@ -0,0 +1,29 @@
+osTicket API
+============
+
+The osTicket API is implemented as (somewhat) simple XML or JSON over HTTP.
+For now, only ticket creation is supported, but eventually, all resources
+inside osTicket will be accessible and modifiable via the API.
+
+Authentication
+--------------
+
+Authentication via the API is done via API keys configured inside the
+osTicket admin panel. API keys are created and tied to a source IP address,
+which will be checked against the source IP of requests to the HTTP API.
+
+API keys can be created and managed via the admin panel. Navigate to Manage
+-> API keys. Use *Add New API Key* to create a new API key. Currently, no
+special configuration is required to allow the API key to be used for the
+HTTP API. All API keys are valid for the HTTP API.
+
+Wrappers
+--------
+
+Currently, there are no wrappers for the API. If you've written one and
+would like it on the list, submit a pull request to add your wrapper.
+
+Resources
+---------
+
+- [Tickets](api/tickets.md)
diff --git a/setup/doc/api/tickets.md b/setup/doc/api/tickets.md
new file mode 100644
index 0000000000000000000000000000000000000000..fc79b1aead833a9f4cf9fd3a214635dcba9e52fc
--- /dev/null
+++ b/setup/doc/api/tickets.md
@@ -0,0 +1,128 @@
+Tickets
+=======
+The API supports ticket creation via the HTTP API (as well as via email,
+etc.). Currently, the API support creation of tickets only -- so no
+modifications and deletions of existing tickets is possible via the API for
+now.
+
+Create a Ticket
+---------------
+
+Tickets can be created in the osTicket system by sending an HTTP POST to
+`api/tickets.xml` or `api/tickets.json` depending on the format of the
+request content.
+
+### Fields ######
+
+*   __email__:   *required* Email address of the submitter
+*   __name__:    *required* Name of the submitter
+*   __subject__: *required* Subject of the ticket
+*   __message__: *required* Initial message for the ticket thread
+*   __alert__:       If unset, disable alerts to staff. Default is `true`
+*   __autorespond__: If unset, disable autoresponses. Default is `true`
+*   __ip__:          IP address of the submitter
+*   __phone__:       Phone number of the submitter
+*   __phone_ext__:   Phone number extension -- can also be embedded in *phone*
+*   __priorityId__:  Priority *id* for the new ticket to assume
+*   __source__:      Source of the ticket, default is `API`
+*   __topicId__:     Help topic *id* associated with the ticket
+*   __attachments__: An array of files to attach to the initial message.
+                     Each attachment must have some content and also the
+                     following fields:
+    *   __name__:     *required* name of the file to be attached. Multiple files
+                      with the same name are allowable
+    *   __type__:     Mime type of the file. Default is `text/plain`
+    *   __encoding__: Set to `base64` if content is base64 encoded 
+
+### XML Payload Example ######
+
+* `POST /api/tickets.xml`
+
+The XML data format is extremely lax. Content can be either sent as an
+attribute or a named element if it has no sub-content.
+
+In the example below, the simple element could also be replaced as
+attributes on the root `<ticket>` element; however, if a `CDATA` element is
+necessary to hold special content, or difficult content such as double
+quotes is to be embedded, simple sub-elements are also supported.
+
+Notice that the phone extension can be sent as the `@ext` attribute of the
+`phone` sub-element.
+
+``` xml
+<?xml version="1.0" encoding="UTF-8"?>
+<ticket alert="true" autorespond="true" source="API">
+    <name>Angry User</name>
+    <email>api@osticket.com</email>
+    <subject>Testing API</subject>
+    <phone ext="123">318-555-8634</phone>
+    <message><![CDATA[Message content here]]></message>
+    <attachments>
+        <file name="file.txt" type="text/plain"><![CDATA[
+            File content is here and is automatically trimmed
+        ]]></file>
+        <file name="image.gif" type="image/gif" encoding="base64">
+            R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwy
+            GHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ
+            +5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/TPg7JpJHxyendzWTBfX0cxOnK
+            PjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5iJ90m6
+            mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3u
+            Nna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7
+        </file>
+    </attachments>
+    <ip>123.211.233.122</ip>
+</ticket>
+```
+
+### JSON Payload Example ###
+
+* `POST /api/tickets.json`
+
+Attachment data for the JSON content uses the [RFC 2397][] data URL format.
+As described above, the content-type and base64 encoding hints are optional.
+Furthermore, a character set can be optionally declared for each attachment
+and will be automatically converted to UTF-8 for database storage.
+
+Notice that the phone number extension can be embedded in the `phone` value
+denoted with a capital `X`
+
+Do also note that the JSON format forbids a comma after the last element in
+an object or array definition, and newlines are not allowed inside strings.
+
+``` json
+{
+    "alert": true,
+    "autorespond": true,
+    "source": "API",
+    "name": "Angry User",
+    "email": "api@osticket.com",
+    "phone": "3185558634X123",
+    "subject": "Testing API",
+    "ip": "123.211.233.122",
+    "message": "MESSAGE HERE",
+    "attachments": [
+        {"file.txt": "data:text/plain;charset=utf-8,content"},
+        {"image.png": "data:image/png;base64,R0lGODdhMAA..."},
+    ]
+}
+```
+
+[rfc 2397]:     http://www.ietf.org/rfc/rfc2397.txt     "Data URLs"
+
+### Response ######
+
+If successful, the server will send `HTTP/201 Created`. Otherwise, it will
+send an appropriate HTTP status with the content being the error
+description. Most likely offenders are
+
+* Required field not included
+* Data type mismatch (text send for numeric field)
+* Incorrectly encoded base64 data
+* Unsupported field sent
+* Incorrectly formatted content (bad JSON or XML)
+
+Upon success, the content of the response will be the external ticket id of
+the newly-created ticket.
+
+    Status: 201 Created
+    123456
diff --git a/setup/inc/class.installer.php b/setup/inc/class.installer.php
index 2d284ecedca1e48b25a2fdfd25ad912ec449912a..f355fbe04cf8a55aaa50bac9e9672131b3c3354a 100644
--- a/setup/inc/class.installer.php
+++ b/setup/inc/class.installer.php
@@ -89,6 +89,13 @@ class Installer extends SetupWizard {
                 $this->errors['db']='Unable to create the database.';
             } elseif(!db_select_database($vars['dbname'])) {
                 $this->errors['dbname']='Unable to select the database';
+            } else {
+                //Abort if we have another installation (or table) with same prefix.
+                $sql = 'SELECT * FROM `'.$vars['prefix'].'config` LIMIT 1';
+                if(mysql_query($sql)) {
+                    $this->errors['err'] = 'We have a problem - another installation with same table prefix exists!';
+                    $this->errors['prefix'] = 'Prefix already in-use';
+                }
             }
         }
 
diff --git a/setup/inc/file-perm.inc.php b/setup/inc/file-perm.inc.php
index 7f93a302c266cabeb4a466e3f3951e14b508c90e..6a158082ddecadbb7552f52f43503cf29f7ac3bb 100644
--- a/setup/inc/file-perm.inc.php
+++ b/setup/inc/file-perm.inc.php
@@ -9,9 +9,9 @@ if(!defined('SETUPINC')) die('Kwaheri!');
              </p>
             </div>
             <h3>Solution: <font color="red"><?php echo $errors['err']; ?></font></h3>
-            Please follow the instructions below to give read and write access to the web server.
+            Please follow the instructions below to give read and write access to the web server user.
             <ul>
-                <li><b>CLI</b>:<br><i>chmod 0777  include/ost-config.php</i></li>
+                <li><b>CLI</b>:<br><i>chmod 0666  include/ost-config.php</i></li>
                 <li><b>FTP</b>:<br>Using WS_FTP this would be right hand clicking on the fil, selecting chmod, and then giving all permissions to the file.</li>
                 <li><b>Cpanel</b>:<br>Click on the file, select change permission, and then giving all permissions to the file.</li>
             </ul>
diff --git a/setup/inc/file-unclean.inc.php b/setup/inc/file-unclean.inc.php
index 309fad10bc150d3beac23c937df1ebd448d42238..3c194b60ac2f078ecaef85cb3811ac9ed0741a6d 100644
--- a/setup/inc/file-unclean.inc.php
+++ b/setup/inc/file-unclean.inc.php
@@ -4,7 +4,7 @@ if(!defined('SETUPINC')) die('Kwaheri!');
     <div id="main">
             <h1 style="color:#FF7700;">osTicket is already installed?</h1>
             <div id="intro">
-             <p>Configuration file already changed - which could mean osTicket is already installed or the config file is currupted. If you are trying to upgrade osTicket, then go to <a href="../scp/" >Admin Panel</a>.</p>
+             <p>Configuration file already changed - which could mean osTicket is already installed or the config file is currupted. If you are trying to upgrade osTicket, then go to <a href="../scp/admin.php" >Admin Panel</a>.</p>
 
              <p>If you believe this is in error, please try replacing the config file with a unchanged template copy and try again or get technical help.</p>
              <p>Refer to the <a target="_blank" href="http://osticket.com/wiki/Installation">Installation Guide</a> on the wiki for more information.</p>
diff --git a/setup/inc/install.inc.php b/setup/inc/install.inc.php
index 7f050baef479582a393d41e452b0adb3e42b4b8b..cb5f2d82bfd15b2fcb9e95087b443844d0f3a02a 100644
--- a/setup/inc/install.inc.php
+++ b/setup/inc/install.inc.php
@@ -16,13 +16,13 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):array('prefix'=>'ost_','dbho
                 </div>
                 <div class="row">
                     <label>Helpdesk Name:</label>
-                    <input type="text" name="name" size="30" tabindex="1" value="<?php echo $info['name']; ?>">
+                    <input type="text" name="name" size="45" tabindex="1" value="<?php echo $info['name']; ?>">
                     <a class="tip" href="#t1">?</a>
                     <font class="error"><?php echo $errors['name']; ?></font>
                 </div>
                 <div class="row">
                     <label>Default Email:</label>
-                    <input type="text" name="email" size="30" tabindex="2" value="<?php echo $info['email']; ?>">
+                    <input type="text" name="email" size="45" tabindex="2" value="<?php echo $info['email']; ?>">
                     <a class="tip" href="#t2">?</a>
                     <font class="error"><?php echo $errors['email']; ?></font>
                 </div>
@@ -31,37 +31,37 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):array('prefix'=>'ost_','dbho
                 <span class="subhead">Your primary administrator account - you can add more users later.</span>
                 <div class="row">
                     <label>First Name:</label>
-                    <input type="text" name="fname" size="30" tabindex="3" value="<?php echo $info['fname']; ?>">
+                    <input type="text" name="fname" size="45" tabindex="3" value="<?php echo $info['fname']; ?>">
                     <a class="tip" href="#t3">?</a>
                     <font class="error"><?php echo $errors['fname']; ?></font>
                 </div>
                 <div class="row">
                     <label>Last Name:</label>
-                    <input type="text" name="lname" size="30" tabindex="4" value="<?php echo $info['lname']; ?>">
+                    <input type="text" name="lname" size="45" tabindex="4" value="<?php echo $info['lname']; ?>">
                     <a class="tip" href="#t4">?</a>
                     <font class="error"><?php echo $errors['lname']; ?></font>
                 </div>
                 <div class="row">
                     <label>Email Address:</label>
-                    <input type="text" name="admin_email" size="30" tabindex="5" value="<?php echo $info['admin_email']; ?>">
+                    <input type="text" name="admin_email" size="45" tabindex="5" value="<?php echo $info['admin_email']; ?>">
                     <a class="tip" href="#t5">?</a>
                     <font class="error"><?php echo $errors['admin_email']; ?></font>
                 </div>
                 <div class="row">
                     <label>Username:</label>
-                    <input type="text" name="username" size="30" tabindex="6" value="<?php echo $info['username']; ?>" autocomplete="off">
+                    <input type="text" name="username" size="45" tabindex="6" value="<?php echo $info['username']; ?>" autocomplete="off">
                     <a class="tip" href="#t6">?</a>
                     <font class="error"><?php echo $errors['username']; ?></font>
                 </div>
                 <div class="row">
                     <label> Password:</label>
-                    <input type="password" name="passwd" size="30" tabindex="7" value="<?php echo $info['passwd']; ?>" autocomplete="off">
+                    <input type="password" name="passwd" size="45" tabindex="7" value="<?php echo $info['passwd']; ?>" autocomplete="off">
                     <a class="tip" href="#t7">?</a>
                     <font class="error"><?php echo $errors['passwd']; ?></font>
                 </div>
                 <div class="row">
                     <label>Retype Password:</label>
-                    <input type="password" name="passwd2" size="30" tabindex="8" value="<?php echo $info['passwd2']; ?>">
+                    <input type="password" name="passwd2" size="45" tabindex="8" value="<?php echo $info['passwd2']; ?>">
                     <a class="tip" href="#t8">?</a>
                     <font class="error"><?php echo $errors['passwd2']; ?></font>
                 </div>
@@ -70,31 +70,31 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):array('prefix'=>'ost_','dbho
                 <span class="subhead">Database connection information <font class="error"><?php echo $errors['db']; ?></font></span>
                 <div class="row">
                     <label>MySQL Table Prefix:</label>
-                    <input type="text" name="prefix" size="30" tabindex="9" value="<?php echo $info['prefix']; ?>">
+                    <input type="text" name="prefix" size="45" tabindex="9" value="<?php echo $info['prefix']; ?>">
                     <a class="tip" href="#t9">?</a>
                     <font class="error"><?php echo $errors['prefix']; ?></font>
                 </div>
                 <div class="row">
                     <label>MySQL Hostname:</label>
-                    <input type="text" name="dbhost" size="30" tabindex="10" value="<?php echo $info['dbhost']; ?>">
+                    <input type="text" name="dbhost" size="45" tabindex="10" value="<?php echo $info['dbhost']; ?>">
                     <a class="tip" href="#t10">?</a>
                     <font class="error"><?php echo $errors['dbhost']; ?></font>
                 </div>
                 <div class="row">
                     <label>MySQL Database:</label>
-                    <input type="text" name="dbname" size="30" tabindex="11" value="<?php echo $info['dbname']; ?>">
+                    <input type="text" name="dbname" size="45" tabindex="11" value="<?php echo $info['dbname']; ?>">
                     <a class="tip" href="#t11">?</a>
                     <font class="error"><?php echo $errors['dbname']; ?></font>
                 </div>
                 <div class="row">
                     <label>MySQL Username:</label>
-                    <input type="text" name="dbuser" size="30" tabindex="12" value="<?php echo $info['dbuser']; ?>">
+                    <input type="text" name="dbuser" size="45" tabindex="12" value="<?php echo $info['dbuser']; ?>">
                     <a class="tip" href="#t12">?</a>
                     <font class="error"><?php echo $errors['dbuser']; ?></font>
                 </div>
                 <div class="row">
                     <label>MySQL Password:</label>
-                    <input type="password" name="dbpass" size="30" tabindex="13" value="<?php echo $info['dbpass']; ?>">
+                    <input type="password" name="dbpass" size="45" tabindex="13" value="<?php echo $info['dbpass']; ?>">
                     <a class="tip" href="#t13">?</a>
                     <font class="error"><?php echo $errors['dbpass']; ?></font>
                 </div>
diff --git a/setup/inc/sql/osTicket-mysql.sql b/setup/inc/sql/osTicket-mysql.sql
index 5d2f4ceefdb5d912ef5cf7b84f9b1e3a91508d75..004b9934092948798fc84adbe2e987af6006cd2b 100644
--- a/setup/inc/sql/osTicket-mysql.sql
+++ b/setup/inc/sql/osTicket-mysql.sql
@@ -5,11 +5,12 @@ CREATE TABLE `%TABLE_PREFIX%api_key` (
   `isactive` tinyint(1) NOT NULL default '1',
   `ipaddr` varchar(64) NOT NULL,
   `apikey` varchar(255) NOT NULL,
+  `can_create_tickets` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT  '1',
   `notes` text,
   `updated` datetime NOT NULL,
   `created` datetime NOT NULL,
   PRIMARY KEY  (`id`),
-  UNIQUE KEY `ipaddr` (`ipaddr`),
+  KEY `ipaddr` (`ipaddr`),
   UNIQUE KEY `apikey` (`apikey`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
 
@@ -133,7 +134,7 @@ CREATE TABLE `%TABLE_PREFIX%config` (
   `assigned_alert_team_members` tinyint(1) unsigned NOT NULL default '0',
   `auto_assign_reopened_tickets` tinyint(1) unsigned NOT NULL default '1',
   `show_related_tickets` tinyint(1) unsigned NOT NULL default '1',
-  `show_assigned_tickets` tinyint(1) unsigned NOT NULL default '0',
+  `show_assigned_tickets` tinyint(1) unsigned NOT NULL default '1',
   `show_answered_tickets` tinyint(1) unsigned NOT NULL default '0',
   `show_notes_inline` tinyint(1) unsigned NOT NULL default '1',
   `hide_staff_name` tinyint(1) unsigned NOT NULL default '0',
@@ -265,7 +266,7 @@ CREATE TABLE `%TABLE_PREFIX%filter_rule` (
   `id` int(11) unsigned NOT NULL auto_increment,
   `filter_id` int(10) unsigned NOT NULL default '0',
   `what` enum('name','email','subject','body','header') NOT NULL,
-  `how` enum('equal','not_equal','contains','dn_contain') NOT NULL,
+  `how` enum('equal','not_equal','contains','dn_contain','starts','ends') NOT NULL,
   `val` varchar(255) NOT NULL,
   `isactive` tinyint(1) unsigned NOT NULL DEFAULT '1',
   `notes` tinytext NOT NULL,
@@ -355,6 +356,7 @@ CREATE TABLE `%TABLE_PREFIX%groups` (
   `group_name` varchar(50) NOT NULL default '',
   `can_create_tickets` tinyint(1) unsigned NOT NULL default '1',
   `can_edit_tickets` tinyint(1) unsigned NOT NULL default '1',
+  `can_post_ticket_reply` tinyint( 1 ) unsigned NOT NULL DEFAULT  '1',
   `can_delete_tickets` tinyint(1) unsigned NOT NULL default '0',
   `can_close_tickets` tinyint(1) unsigned NOT NULL default '1',
   `can_assign_tickets` tinyint(1) unsigned NOT NULL default '1',
@@ -362,6 +364,7 @@ CREATE TABLE `%TABLE_PREFIX%groups` (
   `can_ban_emails` tinyint(1) unsigned NOT NULL default '0',
   `can_manage_premade` tinyint(1) unsigned NOT NULL default '0',
   `can_manage_faq` tinyint(1) unsigned NOT NULL default '0',
+  `can_view_staff_stats` tinyint( 1 ) unsigned NOT NULL DEFAULT  '0',
   `notes` text,
   `created` datetime NOT NULL,
   `updated` datetime NOT NULL,
@@ -402,7 +405,7 @@ CREATE TABLE `%TABLE_PREFIX%help_topic` (
   `created` datetime NOT NULL,
   `updated` datetime NOT NULL,
   PRIMARY KEY  (`topic_id`),
-  UNIQUE KEY `topic` (`topic`),
+  UNIQUE KEY `topic` ( `topic` ,  `topic_pid` ),
   KEY `topic_pid` (`topic_pid`),
   KEY `priority_id` (`priority_id`),
   KEY `dept_id` (`dept_id`),
diff --git a/setup/inc/sql/osTicket-mysql.sql.md5 b/setup/inc/sql/osTicket-mysql.sql.md5
index 4aa4254e6af1e5496dc39cf89726de1a607bab14..49a6e93dd4e013257c9abf0f1117e12241ee381c 100644
--- a/setup/inc/sql/osTicket-mysql.sql.md5
+++ b/setup/inc/sql/osTicket-mysql.sql.md5
@@ -1 +1 @@
-00ff231f2ade8797a0e7f2a7fccd52f4
+9f3b454c06dfd5ee96003eae5182ac13
diff --git a/setup/install.php b/setup/install.php
index d019598e55de7099c9dbb517b4a9aa1663886ad0..130983a692155869064ae08b8ad47e88f4a6b2f2 100644
--- a/setup/install.php
+++ b/setup/install.php
@@ -80,7 +80,6 @@ if($_POST && $_POST['s']) {
     $_SESSION['ost_installer']['s']='done';
 }
 
-
 switch(strtolower($_SESSION['ost_installer']['s'])) {
     case 'config':
     case 'install':
@@ -105,7 +104,16 @@ switch(strtolower($_SESSION['ost_installer']['s'])) {
             $inc='install-prereq.inc.php';
         break;
     default:
-         $inc='install-prereq.inc.php';
+        //Fail IF any of the old config files exists.
+        if(file_exists(INCLUDE_DIR.'settings.php') 
+                || file_exists(ROOT_DIR.'ostconfig.php')
+                || (file_exists(OSTICKET_CONFIGFILE) 
+                    && preg_match("/define\('OSTINSTALLED',TRUE\)\;/i", 
+                        file_get_contents(OSTICKET_CONFIGFILE)))
+                )
+            $inc='file-unclean.inc.php';
+        else
+            $inc='install-prereq.inc.php';
 }
 
 require(INC_DIR.'header.inc.php');
diff --git a/setup/setup.inc.php b/setup/setup.inc.php
index a79c93c7e55ed3aab9a220ea54f1d558258e3fc6..8eeb792feebca0c57bcf8d6c7e8ee24e98474985 100644
--- a/setup/setup.inc.php
+++ b/setup/setup.inc.php
@@ -15,7 +15,7 @@
 **********************************************************************/
 
 #This  version - changed on every release
-define('THIS_VERSION', '1.7-RC3');
+define('THIS_VERSION', '1.7-RC4');
 
 #inits - error reporting.
 $error_reporting = E_ALL & ~E_NOTICE;
diff --git a/setup/upgrade.php b/setup/upgrade.php
new file mode 100644
index 0000000000000000000000000000000000000000..2dfc5b475031ca9cf727962648d7c83db381ede5
--- /dev/null
+++ b/setup/upgrade.php
@@ -0,0 +1,4 @@
+<?php
+/* Simply redirect to Admin Panel - new upgrade home */
+header('Location: ../scp/upgrade.php');
+?>