diff --git a/bootstrap.php b/bootstrap.php
index d726e8c0452c7ee2141a02d6065e01b36f308285..a82796ebd2d7ecba1eb453884502f10a193ded53 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -177,7 +177,7 @@ class Bootstrap {
         require(INCLUDE_DIR.'class.log.php');
         require(INCLUDE_DIR.'class.crypto.php');
         require(INCLUDE_DIR.'class.timezone.php');
-        require(INCLUDE_DIR.'class.signal.php');
+        require_once(INCLUDE_DIR.'class.signal.php');
         require(INCLUDE_DIR.'class.nav.php');
         require(INCLUDE_DIR.'class.page.php');
         require_once(INCLUDE_DIR.'class.format.php'); //format helpers
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index e6c0fdb36a4aa6268f89feb437c53e713d2ce350..f52a4341205f6c0e66e12ffff07ebff15d14767f 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -103,12 +103,10 @@ class TicketsAjaxAPI extends AjaxController {
         global $thisstaff, $cfg;
 
         $result=array();
-        $select = 'SELECT DISTINCT ticket.ticket_id';
+        $select = 'SELECT ticket.ticket_id';
         $from = ' FROM '.TICKET_TABLE.' ticket ';
-        $where = ' WHERE 1 ';
-
         //Access control.
-        $where.=' AND ( ticket.staff_id='.db_input($thisstaff->getId());
+        $where = ' WHERE ( ticket.staff_id='.db_input($thisstaff->getId());
 
         if(($teams=$thisstaff->getTeams()) && count(array_filter($teams)))
             $where.=' OR ticket.team_id IN('.implode(',', db_input(array_filter($teams))).')';
@@ -179,57 +177,65 @@ class TicketsAjaxAPI extends AjaxController {
             $where.=' AND ticket.created<=FROM_UNIXTIME('.$endTime.')';
 
         //Query
+        $joins = array();
         if($req['query']) {
             $queryterm=db_real_escape($req['query'], false);
 
-            $from.=' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON (ticket.ticket_id=thread.ticket_id )'
-                .' LEFT JOIN '.FORM_ENTRY_TABLE.' tentry ON (tentry.object_id = ticket.ticket_id
-                   AND tentry.object_type="T")
-                   LEFT JOIN '.FORM_ANSWER_TABLE.' tans ON (tans.entry_id = tentry.id
-                   AND tans.value_id IS NULL)
-                   LEFT JOIN '.FORM_ENTRY_TABLE.' uentry ON (uentry.object_id = ticket.user_id
+            // Setup sets of joins and queries
+            $joins[] = array(
+                'from' =>
+                    'LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON (ticket.ticket_id=thread.ticket_id )',
+                'where' => "thread.title LIKE '%$queryterm%' OR thread.body LIKE '%$queryterm%'"
+            );
+            $joins[] = array(
+                'from' =>
+                    'LEFT JOIN '.FORM_ENTRY_TABLE.' tentry ON (tentry.object_id = ticket.ticket_id AND tentry.object_type="T")
+                    LEFT JOIN '.FORM_ANSWER_TABLE.' tans ON (tans.entry_id = tentry.id AND tans.value_id IS NULL)',
+                'where' => "tans.value LIKE '%$queryterm%'"
+            );
+            $joins[] = array(
+                'from' =>
+                   'LEFT JOIN '.FORM_ENTRY_TABLE.' uentry ON (uentry.object_id = ticket.user_id
                    AND uentry.object_type="U")
                    LEFT JOIN '.FORM_ANSWER_TABLE.' uans ON (uans.entry_id = uentry.id
                    AND uans.value_id IS NULL)
                    LEFT JOIN '.USER_TABLE.' user ON (ticket.user_id = user.id)
-                   LEFT JOIN '.USER_EMAIL_TABLE.' uemail ON (user.id = uemail.user_id)';
-
-            $where.=" AND (  uemail.address LIKE '%$queryterm%'"
-                       ." OR user.name LIKE '%$queryterm%'"
-                       ." OR tans.value LIKE '%$queryterm%'"
-                       ." OR uans.value LIKE '%$queryterm%'"
-                       ." OR thread.title LIKE '%$queryterm%'"
-                       ." OR thread.body LIKE '%$queryterm%'"
-                       .' )';
+                   LEFT JOIN '.USER_EMAIL_TABLE.' uemail ON (user.id = uemail.user_id)',
+                'where' =>
+                    "uemail.address LIKE '%$queryterm%' OR user.name LIKE '%$queryterm%' OR uans.value LIKE '%$queryterm%'",
+            );
         }
 
         // Dynamic fields
-        $dynfields='(SELECT entry.object_id, %s '.
-             'FROM '.FORM_ANSWER_TABLE.' ans '.
-             'JOIN '.FORM_ENTRY_TABLE.' entry ON entry.id=ans.entry_id '.
-             'JOIN '.FORM_FIELD_TABLE.' field ON field.id=ans.field_id '.
-             'WHERE entry.object_type="T" GROUP BY entry.object_id)';
-        $vals = array();
+        $cdata_search = false;
         foreach (TicketForm::getInstance()->getFields() as $f) {
             if (isset($req[$f->getFormName()])
                     && ($val = $req[$f->getFormName()])) {
-                $id = $f->get('id');
-                $vals[] = "MAX(IF(field.id = '$id', ans.value_id, NULL)) as `f_{$id}_id`";
-                $vals[] = "MAX(IF(field.id = '$id', ans.value, NULL)) as `f_$id`";
-                $where .= " AND (dyn.`f_{$id}_id` = ".db_input($val)
-                    . " OR dyn.`f_$id` LIKE '%".db_real_escape($val)."%')";
+                $name = $f->get('name') ? $f->get('name') : 'field_'.$f->get('id');
+                $cwhere = "cdata.`$name` LIKE '%".db_real_escape($val)."%'";
+                if ($f->getImpl()->hasIdValue() && is_numeric($val))
+                    $cwhere .= " OR cdata.`{$name}_id` = ".db_input($val);
+                $where .= ' AND ('.$cwhere.')';
+                $cdata_search = true;
             }
         }
-        if ($vals)
-            $from .= ' LEFT JOIN '.sprintf($dynfields, implode(',', $vals))
-                ." dyn ON (dyn.object_id = ticket.ticket_id)";
+        if ($cdata_search)
+            $from .= 'LEFT JOIN '.TABLE_PREFIX.'ticket__cdata '
+                    ." cdata ON (cdata.ticket_id = ticket.ticket_id)";
+
+        $sections = array();
+        foreach ($joins as $j) {
+            $sections[] = "$select $from {$j['from']} $where AND ({$j['where']})";
+        }
+        if (!$joins)
+            $sections[] = "$select $from $where";
 
-        $sql="$select $from $where";
+        $sql=implode(' union ', $sections);
         $res = db_query($sql);
 
         $tickets = array();
-        while (list($tickets[]) = db_fetch_row($res));
-        $tickets = array_filter($tickets);
+        while ($row = db_fetch_row($res))
+            $tickets[] = $row[0];
 
         return $tickets;
     }
diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index 52e3109356db760e6a9d412c7782b5697b9891ec..f164138994cc1846eec67baccc16b99f1fe49c97 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -18,6 +18,7 @@
 **********************************************************************/
 require_once(INCLUDE_DIR . 'class.orm.php');
 require_once(INCLUDE_DIR . 'class.forms.php');
+require_once(INCLUDE_DIR . 'class.signal.php');
 
 /**
  * Form template, used for designing the custom form and for entering custom
@@ -130,6 +131,7 @@ class DynamicForm extends VerySimpleModel {
             foreach ($ht['fields'] as $f) {
                 $f = DynamicFormField::create($f);
                 $f->form_id = $inst->id;
+                $f->setForm($inst);
                 $f->save();
             }
         }
@@ -196,6 +198,89 @@ class TicketForm extends DynamicForm {
         static::$instance = $o[0]->instanciate();
         return static::$instance;
     }
+
+    // Materialized View for Ticket custom data (MySQL FlexViews would be
+    // nice)
+    //
+    // @see http://code.google.com/p/flexviews/
+    static function getDynamicDataViewFields() {
+        $fields = array();
+        foreach (self::getInstance()->getFields() as $f) {
+            $impl = $f->getImpl();
+            if (!$impl->hasData() || $impl->isPresentationOnly())
+                continue;
+
+            $name = ($f->get('name')) ? $f->get('name')
+                : 'field_'.$f->get('id');
+
+            $fields[] = sprintf(
+                'MAX(IF(field.name=\'%1$s\',ans.value,NULL)) as `%1$s`',
+                $name);
+            if ($impl->hasIdValue()) {
+                $fields[] = sprintf(
+                    'MAX(IF(field.name=\'%1$s\',ans.value_id,NULL)) as `%1$s_id`',
+                    $name);
+            }
+        }
+        return $fields;
+    }
+
+    static function ensureDynamicDataView() {
+        $sql = 'SHOW TABLES LIKE \''.TABLE_PREFIX.'ticket__cdata\'';
+        if (!db_num_rows(db_query($sql)))
+            return static::buildDynamicDataView();
+    }
+
+    static function buildDynamicDataView() {
+        // create  table __cdata (primary key (ticket_id)) as select
+        // entry.object_id as ticket_id, MAX(IF(field.name = 'subject',
+        // ans.value, NULL)) as `subject`,MAX(IF(field.name = 'priority',
+        // ans.value, NULL)) as `priority_desc`,MAX(IF(field.name =
+        // 'priority', ans.value_id, NULL)) as `priority_id`
+        // FROM ost_form_entry entry LEFT JOIN ost_form_entry_values ans ON
+        // ans.entry_id = entry.id LEFT JOIN ost_form_field field ON
+        // field.id=ans.field_id
+        // where entry.object_type='T' group by entry.object_id;
+        $fields = static::getDynamicDataViewFields();
+        $sql = 'CREATE TABLE `'.TABLE_PREFIX.'ticket__cdata` (PRIMARY KEY (ticket_id)) AS
+            SELECT entry.`object_id` AS ticket_id, '.implode(',', $fields)
+         .' FROM ost_form_entry entry
+            JOIN ost_form_entry_values ans ON ans.entry_id = entry.id
+            JOIN ost_form_field field ON field.id=ans.field_id
+            WHERE entry.object_type=\'T\' GROUP BY entry.object_id';
+        db_query($sql);
+    }
+
+    static function dropDynamicDataView() {
+        db_query('DROP TABLE IF EXISTS `'.TABLE_PREFIX.'ticket__cdata`');
+    }
+
+    static function updateDynamicDataView($answer, $data) {
+        // TODO: Detect $data['dirty'] for value and value_id
+        // We're chiefly concerned with Ticket form answers
+        if (!($e = $answer->getEntry()) || $e->get('object_type') != 'T')
+            return;
+
+        // If the `name` column is in the dirty list, we would be renaming a
+        // column. Delete the view instead.
+        if (isset($data['dirty']) && isset($data['dirty']['name']))
+            return self::dropDynamicDataView();
+
+        // $record = array();
+        // $record[$f] = $answer->value'
+        // TicketFormData::objects()->filter(array('ticket_id'=>$a))
+        //      ->merge($record);
+        $f = $answer->getField();
+        $name = $f->get('name') ? $f->get('name') : 'field_'.$f->get('id');
+        $ids = $f->hasIdValue();
+        $fields = sprintf('`%s`=', $name) . db_input($answer->get('value'));
+        if ($f->hasIdValue())
+            $fields .= sprintf(',`%s_id`=', $name) . db_input($answer->getIdValue());
+        $sql = 'INSERT INTO `'.TABLE_PREFIX.'ticket__cdata` SET '.$fields
+            .', `ticket_id`='.db_input($answer->getEntry()->get('object_id'))
+            .' ON DUPLICATE KEY UPDATE '.$fields;
+        db_query($sql);
+    }
 }
 // Add fields from the standard ticket form to the ticket filterable fields
 Filter::addSupportedMatches('Custom Fields', function() {
@@ -207,6 +292,23 @@ Filter::addSupportedMatches('Custom Fields', function() {
     }
     return $matches;
 });
+// Manage materialized view on custom data updates
+Signal::connect('model.created',
+    array('TicketForm', 'updateDynamicDataView'),
+    'DynamicFormEntryAnswer');
+Signal::connect('model.updated',
+    array('TicketForm', 'updateDynamicDataView'),
+    'DynamicFormEntryAnswer');
+// Recreate the dynamic view after new or removed fields to the ticket
+// details form
+Signal::connect('model.created',
+    array('TicketForm', 'dropDynamicDataView'),
+    'DynamicFormField',
+    function($o) { return $o->getForm()->get('type') == 'T'; });
+Signal::connect('model.deleted',
+    array('TicketForm', 'dropDynamicDataView'),
+    'DynamicFormField',
+    function($o) { return $o->getForm()->get('type') == 'T'; });
 
 require_once(INCLUDE_DIR . "class.json.php");
 
@@ -555,6 +657,7 @@ class DynamicFormEntry extends VerySimpleModel {
                 array('field_id'=>$f->get('id')));
             $a->field = $f;
             $a->field->setAnswer($a);
+            $a->entry = $inst;
             $inst->_values[] = $a;
         }
         return $inst;
diff --git a/include/class.forms.php b/include/class.forms.php
index b5a7802408378eac68b0d41efc302b7af944ef75..9d0bdc87e460396ee901a708b6e1ff31948fb767 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -440,6 +440,14 @@ class FormField {
         return $this->presentation_only;
     }
 
+    /**
+     * Indicates if the field places data in the `value_id` column. This
+     * is currently used by the materialized view system
+     */
+    function hasIdValue() {
+        return false;
+    }
+
     function getConfigurationForm() {
         if (!$this->_cform) {
             $type = static::getFieldType($this->get('type'));
@@ -806,6 +814,10 @@ class PriorityField extends ChoiceField {
         return $widget;
     }
 
+    function hasIdValue() {
+        return true;
+    }
+
     function getChoices() {
         $this->ht['default'] = 0;
 
@@ -1140,6 +1152,7 @@ class ThreadEntryWidget extends Widget {
         <input type="file" class="multifile" name="attachments[]" id="attachments" size="30" value="" />
         </div>
         <font class="error">&nbsp;<?php echo $errors['attachments']; ?></font>
+        </div>
         <hr/>
         <?php
         }
diff --git a/include/class.http.php b/include/class.http.php
index 3aea32b6e93029e541bf0d0c9fd28bef15b20dc3..ef917845d76a1f943e022167304eb7a2b0d131c5 100644
--- a/include/class.http.php
+++ b/include/class.http.php
@@ -43,9 +43,14 @@ class Http {
         exit;
     }
 
-	function redirect($url,$delay=0,$msg='') {
+    function redirect($url,$delay=0,$msg='') {
 
-        if(strstr($_SERVER['SERVER_SOFTWARE'], 'IIS')){
+        $iis = strpos($_SERVER['SERVER_SOFTWARE'], 'IIS') !== false;
+        @list($name, $version) = explode('/', $_SERVER['SERVER_SOFTWARE']);
+        // Legacy code for older versions of IIS that would not emit the
+        // correct HTTP status and headers when using the `Location`
+        // header alone
+        if ($iis && version_compare($version, '7.0', '<')) {
             header("Refresh: $delay; URL=$url");
         }else{
             header("Location: $url");
diff --git a/include/class.mailer.php b/include/class.mailer.php
index a57f8d7be1ac5b7e15f79fd3d8430cbfd2d58819..21a7d157d68a28191b88e798dade0633d3c15725 100644
--- a/include/class.mailer.php
+++ b/include/class.mailer.php
@@ -139,12 +139,11 @@ class Mailer {
 
         $mime = new Mail_mime();
 
+        // If the message is not explicitly declared to be a text message,
+        // then assume that it needs html processing to create a valid text
+        // body
         $isHtml = true;
-        // Ensure that the 'text' option / hint is not set to true and that
-        // the message appears to be HTML -- that is, the first
-        // non-whitespace char is a '<' character
-        if (!(isset($options['text']) && $options['text'])
-                && (!$cfg || $cfg->isHtmlThreadEnabled())) {
+        if (!(isset($options['text']) && $options['text'])) {
             // Make sure nothing unsafe has creeped into the message
             $message = Format::safe_html($message); //XXX??
             $mime->setTXTBody(Format::html2text($message, 90, false));
diff --git a/include/class.orm.php b/include/class.orm.php
index 38d3482daf79c91fddeb9ef0f9b81240d4a4ed42..dee287cea77adc73d323db573e79a83151a998de 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -147,7 +147,10 @@ class VerySimpleModel {
         foreach ($pk as $p)
             $filter[] = $p.' = '.db_input($this->get($p));
         $sql .= ' WHERE '.implode(' AND ', $filter).' LIMIT 1';
-        return db_query($sql) && db_affected_rows() == 1;
+        if (!db_query($sql) || db_affected_rows() != 1)
+            throw new Exception(db_error());
+        Signal::send('model.deleted', $this);
+        return true;
     }
 
     function save($refetch=false) {
@@ -183,6 +186,11 @@ class VerySimpleModel {
             $this->__new__ = false;
             // Setup lists again
             $this->__setupForeignLists();
+            Signal::send('model.created', $this);
+        }
+        else {
+            $data = array('dirty' => $this->dirty);
+            Signal::send('model.updated', $this, $data);
         }
         # Refetch row from database
         # XXX: Too much voodoo
diff --git a/include/class.signal.php b/include/class.signal.php
index f931acebc50c8beaf020859279c2ba0cba56f1de..928c15c4d2392ae767a0c465b6730d859f3dc85f 100644
--- a/include/class.signal.php
+++ b/include/class.signal.php
@@ -93,7 +93,7 @@ class Signal {
             list($s, $callable, $check) = $sub;
             if ($s && !is_a($object, $s))
                 continue;
-            elseif ($check && !call_user_func($check, $data))
+            elseif ($check && !call_user_func($check, $object, $data))
                 continue;
             call_user_func($callable, $object, $data);
         }
diff --git a/include/class.thread.php b/include/class.thread.php
index 3ebdc83a6729e9130134c256040d246b589512bb..5e6c88ea7a9c9ccdb6a4074d0c550729136662f0 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -746,7 +746,7 @@ Class ThreadEntry {
         $subject = $mailinfo['subject'];
         $match = array();
         if ($subject && $mailinfo['email']
-                && preg_match("/\[#([0-9]{1,10})\]/", $subject, $match)
+                && preg_match("/#[\p{L}-]+?([0-9]{1,10})/u", $subject, $match)
                 && ($tid = Ticket::getIdByExtId((int)$match[1], $mailinfo['email']))
                 )
             // Return last message for the thread
diff --git a/include/class.ticket.php b/include/class.ticket.php
index bb07895ae17cb779e64ccad3a0c71aa8452350ca..97d83f293029e7c916afafabe510b414431fe5c7 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -1486,7 +1486,6 @@ class Ticket {
                 && ($tpl = $dept->getTemplate())
                 && ($msg = $tpl->getNewMessageAlertMsgTemplate())) {
 
-            $attachments = $message->getAttachments();
             $msg = $this->replaceVars($msg->asArray(), $variables);
 
             //Build list of recipients and fire the alerts.
@@ -1508,7 +1507,7 @@ class Ticket {
             foreach( $recipients as $k=>$staff) {
                 if(!$staff || !$staff->getEmail() || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue;
                 $alert = $this->replaceVars($msg, array('recipient' => $staff));
-                $email->sendAlert($staff->getEmail(), $alert['subj'], $alert['body'], $attachments, $options);
+                $email->sendAlert($staff->getEmail(), $alert['subj'], $alert['body'], null, $options);
                 $sentlist[] = $staff->getEmail();
             }
         }
@@ -1557,8 +1556,6 @@ class Ticket {
             else
                 $signature='';
 
-            $attachments =($cfg->emailAttachments() && $files)?$response->getAttachments():array();
-
             $msg = $this->replaceVars($msg->asArray(),
                 array('response' => $response, 'signature' => $signature));
 
@@ -1718,8 +1715,6 @@ class Ticket {
                 && ($tpl = $dept->getTemplate())
                 && ($msg=$tpl->getNoteAlertMsgTemplate())) {
 
-            $attachments = $note->getAttachments();
-
             $msg = $this->replaceVars($msg->asArray(),
                 array('note' => $note));
 
@@ -1738,7 +1733,6 @@ class Ticket {
             if($cfg->alertDeptManagerONNewNote() && $dept && $dept->getManagerId())
                 $recipients[]=$dept->getManager();
 
-            $attachments = $note->getAttachments();
             $options = array(
                 'inreplyto'=>$note->getEmailMessageId(),
                 'references'=>$note->getEmailReferences());
@@ -1750,7 +1744,7 @@ class Ticket {
                         || $note->getStaffId() == $staff->getId())  //No need to alert the poster!
                     continue;
                 $alert = $this->replaceVars($msg, array('recipient' => $staff));
-                $email->sendAlert($staff->getEmail(), $alert['subj'], $alert['body'], $attachments, $options);
+                $email->sendAlert($staff->getEmail(), $alert['subj'], $alert['body'], null, $options);
                 $sentlist[] = $staff->getEmail();
             }
         }
diff --git a/include/client/header.inc.php b/include/client/header.inc.php
index 7266060fbf2f50160e54b1a275f3b4e90fafc87e..9f20098b4b1b4fcfc2d891973717c42876296fde 100644
--- a/include/client/header.inc.php
+++ b/include/client/header.inc.php
@@ -3,6 +3,7 @@ $title=($cfg && is_object($cfg) && $cfg->getTitle())?$cfg->getTitle():'osTicket
 header("Content-Type: text/html; charset=UTF-8\r\n");
 ?>
 <!DOCTYPE html>
+<html>
 <head>
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
diff --git a/include/mysqli.php b/include/mysqli.php
index 34fa57479b64ebcda7e1ac381f4c70a6069773aa..7245d9ec20eeab75197500a8b9f5d8f06af8e567 100644
--- a/include/mysqli.php
+++ b/include/mysqli.php
@@ -39,18 +39,24 @@ function db_connect($host, $user, $passwd, $options = array()) {
         return NULL;
 
     $port = ini_get("mysqli.default_port");
+    $socket = ini_get("mysqli.default_socket");
     if (strpos($host, ':') !== false) {
-        list($host, $port) = explode(':', $host);
+        list($host, $portspec) = explode(':', $host);
         // PHP may not honor the port number if connecting to 'localhost'
-        if (!strcasecmp($host, 'localhost'))
-            // XXX: Looks like PHP gethostbyname() is IPv4 only
-            $host = gethostbyname($host);
-        $port = (int) $port;
+        if ($portspec && is_numeric($portspec)) {
+            if (!strcasecmp($host, 'localhost'))
+                // XXX: Looks like PHP gethostbyname() is IPv4 only
+                $host = gethostbyname($host);
+            $port = (int) $portspec;
+        }
+        elseif ($portspec) {
+            $socket = $portspec;
+        }
     }
 
     // Connect
     $start = microtime(true);
-    if (!@$__db->real_connect($host, $user, $passwd, null, $port))
+    if (!@$__db->real_connect($host, $user, $passwd, null, $port, $socket))
         return NULL;
 
     //Select the database, if any.
diff --git a/include/staff/header.inc.php b/include/staff/header.inc.php
index ded810d8996c70fed985b75c690abdb5f9e8974b..d2a3e43b82277bb13eb38718e06bfd84340457f9 100644
--- a/include/staff/header.inc.php
+++ b/include/staff/header.inc.php
@@ -2,6 +2,7 @@
 <html>
 <head>
     <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
     <meta http-equiv="cache-control" content="no-cache" />
     <meta http-equiv="pragma" content="no-cache" />
     <title><?php echo ($ost && ($title=$ost->getPageTitle()))?$title:'osTicket :: Staff Control Panel'; ?></title>
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index e463e515d88a0afa7c7e013f57adf9f411d0dc9a..1415ce76d059cb8617c26f7f66456e51ba9bf4b9 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -129,6 +129,9 @@ if($search):
             if (count($tickets))
                 $qwhere .= ' AND ticket.ticket_id IN ('.
                     implode(',',db_input($tickets)).')';
+            else
+                // No hits -- there should be an empty list of results
+                $qwhere .= ' AND false';
         }
    }
 
@@ -192,10 +195,7 @@ $$x=' class="'.strtolower($order).'" ';
 if($_GET['limit'])
     $qstr.='&limit='.urlencode($_GET['limit']);
 
-$qselect ='SELECT DISTINCT ticket.ticket_id,lock_id,ticketID,ticket.dept_id,ticket.staff_id,ticket.team_id '
-    .',MAX(IF(field.name = \'subject\', ans.value, NULL)) as `subject`'
-    .',MAX(IF(field.name = \'priority\', ans.value, NULL)) as `priority_desc`'
-    .',MAX(IF(field.name = \'priority\', ans.value_id, NULL)) as `priority_id`'
+$qselect ='SELECT ticket.ticket_id,lock_id,ticketID,ticket.dept_id,ticket.staff_id,ticket.team_id '
     .' ,user.name'
     .' ,email.address as email, dept_name '
          .' ,ticket.status,ticket.source,isoverdue,isanswered,ticket.created ';
@@ -203,18 +203,13 @@ $qselect ='SELECT DISTINCT ticket.ticket_id,lock_id,ticketID,ticket.dept_id,tick
 $qfrom=' FROM '.TICKET_TABLE.' ticket '.
        ' LEFT JOIN '.USER_TABLE.' user ON user.id = ticket.user_id'.
        ' LEFT JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id'.
-       ' LEFT JOIN '.DEPT_TABLE.' dept ON ticket.dept_id=dept.dept_id '.
-       ' LEFT JOIN '.FORM_ENTRY_TABLE.' entry ON entry.object_type=\'T\'
-             and entry.object_id=ticket.ticket_id'.
-       ' LEFT JOIN '.FORM_ANSWER_TABLE.' ans ON ans.entry_id = entry.id'.
-       ' LEFT JOIN '.FORM_FIELD_TABLE.' field ON field.id=ans.field_id';
+       ' LEFT JOIN '.DEPT_TABLE.' dept ON ticket.dept_id=dept.dept_id ';
 
 $sjoin='';
 if($search && $deep_search) {
     $sjoin=' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON (ticket.ticket_id=thread.ticket_id )';
 }
 
-$qgroup=' GROUP BY ticket.ticket_id';
 //get ticket count based on the query so far..
 $total=db_count("SELECT count(DISTINCT ticket.ticket_id) $qfrom $sjoin $qwhere");
 //pagenate
@@ -224,23 +219,23 @@ $pageNav=new Pagenate($total,$page,$pagelimit);
 $pageNav->setURL('tickets.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']));
 
 //ADD attachment,priorities, lock and other crap
-$qselect.=' ,count(attach.attach_id) as attachments '
-         .' ,count(DISTINCT thread.id) as thread_count '
-         .' ,IF(ticket.duedate IS NULL,IF(sla.id IS NULL, NULL, DATE_ADD(ticket.created, INTERVAL sla.grace_period HOUR)), ticket.duedate) as duedate '
+$qselect.=' ,IF(ticket.duedate IS NULL,IF(sla.id IS NULL, NULL, DATE_ADD(ticket.created, INTERVAL sla.grace_period HOUR)), ticket.duedate) as duedate '
          .' ,CAST(GREATEST(IFNULL(ticket.lastmessage, 0), IFNULL(ticket.reopened, 0), ticket.created) as datetime) as effective_date '
          .' ,CONCAT_WS(" ", staff.firstname, staff.lastname) as staff, team.name as team '
          .' ,IF(staff.staff_id IS NULL,team.name,CONCAT_WS(" ", staff.lastname, staff.firstname)) as assigned '
-         .' ,IF(ptopic.topic_pid IS NULL, topic.topic, CONCAT_WS(" / ", ptopic.topic, topic.topic)) as helptopic ';
+         .' ,IF(ptopic.topic_pid IS NULL, topic.topic, CONCAT_WS(" / ", ptopic.topic, topic.topic)) as helptopic '
+         .' ,cdata.priority_id, cdata.subject';
 
 $qfrom.=' LEFT JOIN '.TICKET_LOCK_TABLE.' tlock ON (ticket.ticket_id=tlock.ticket_id AND tlock.expire>NOW()
                AND tlock.staff_id!='.db_input($thisstaff->getId()).') '
-       .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach ON (ticket.ticket_id=attach.ticket_id) '
-       .' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON ( ticket.ticket_id=thread.ticket_id) '
        .' LEFT JOIN '.STAFF_TABLE.' staff ON (ticket.staff_id=staff.staff_id) '
        .' LEFT JOIN '.TEAM_TABLE.' team ON (ticket.team_id=team.team_id) '
        .' LEFT JOIN '.SLA_TABLE.' sla ON (ticket.sla_id=sla.id AND sla.isactive=1) '
        .' LEFT JOIN '.TOPIC_TABLE.' topic ON (ticket.topic_id=topic.topic_id) '
-       .' LEFT JOIN '.TOPIC_TABLE.' ptopic ON (ptopic.topic_id=topic.topic_pid) ';
+       .' LEFT JOIN '.TOPIC_TABLE.' ptopic ON (ptopic.topic_id=topic.topic_pid) '
+       .' LEFT JOIN '.TABLE_PREFIX.'ticket__cdata cdata ON (cdata.ticket_id = ticket.ticket_id) ';
+
+TicketForm::ensureDynamicDataView();
 
 // Fetch priority information
 $res = db_query('select * from '.PRIORITY_TABLE);
@@ -248,7 +243,7 @@ $prios = array();
 while ($row = db_fetch_array($res))
     $prios[$row['priority_id']] = $row;
 
-$query="$qselect $qfrom $qwhere $qgroup ORDER BY $order_by $order LIMIT ".$pageNav->getStart().",".$pageNav->getLimit();
+$query="$qselect $qfrom $qwhere ORDER BY $order_by $order LIMIT ".$pageNav->getStart().",".$pageNav->getLimit();
 //echo $query;
 $hash = md5($query);
 $_SESSION['search_'.$hash] = $query;
@@ -262,6 +257,27 @@ if($search)
 
 $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting..
 
+// Fetch the results
+$results = array();
+while ($row = db_fetch_array($res)) {
+    $results[$row['ticket_id']] = $row;
+}
+
+// Fetch attachment and thread entry counts
+if ($results) {
+    $counts_sql = 'SELECT ticket.ticket_id, count(attach.attach_id) as attachments,
+        count(DISTINCT thread.id) as thread_count
+        FROM '.TICKET_TABLE.' ticket
+        LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach ON (ticket.ticket_id=attach.ticket_id) '
+     .' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON ( ticket.ticket_id=thread.ticket_id) '
+     .' WHERE ticket.ticket_id IN ('.implode(',', db_input(array_keys($results))).')
+        GROUP BY ticket.ticket_id';
+    $ids_res = db_query($counts_sql);
+    while ($row = db_fetch_array($ids_res)) {
+        $results[$row['ticket_id']] += $row;
+    }
+}
+
 //YOU BREAK IT YOU FIX IT.
 ?>
 <!-- SEARCH FORM START -->
@@ -345,9 +361,9 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting..
         <?php
         $class = "row1";
         $total=0;
-        if($res && ($num=db_num_rows($res))):
+        if($res && ($num=count($results))):
             $ids=($errors && $_POST['tids'] && is_array($_POST['tids']))?$_POST['tids']:null;
-            while ($row = db_fetch_array($res)) {
+            foreach ($results as $row) {
                 $tag=$row['staff_id']?'assigned':'openticket';
                 $flag=null;
                 if($row['lock_id'])
diff --git a/include/upgrader/streams/core/c00511c7-7be60a84.patch.sql b/include/upgrader/streams/core/c00511c7-7be60a84.patch.sql
index e565227249787174009fddfb6e7f06ed82f01d52..97c2bf97b45d6e5fa1368e8011d05e07d48c545e 100644
--- a/include/upgrader/streams/core/c00511c7-7be60a84.patch.sql
+++ b/include/upgrader/streams/core/c00511c7-7be60a84.patch.sql
@@ -224,7 +224,7 @@ CREATE TABLE `%TABLE_PREFIX%email_filter_rule` (
 
 -- SYSTEM BAN LIST was the first filter created, with ID of '1'
 INSERT INTO `%TABLE_PREFIX%email_filter_rule` (`filter_id`, `what`, `how`, `val`)
-    SELECT LAST_INSERT_ID(), 'email', 'equals', email FROM `%TABLE_PREFIX%email_banlist`;
+    SELECT LAST_INSERT_ID(), 'email', 'equal', email FROM `%TABLE_PREFIX%email_banlist`;
 
 -- Create table session
 DROP TABLE IF EXISTS `%TABLE_PREFIX%session`;
diff --git a/js/redactor-osticket.js b/js/redactor-osticket.js
index 0fed815da1831b9425a22d6f2702766e1ca81362..337d525a5d1cbc5a85601313cf2e941b242d00d4 100644
--- a/js/redactor-osticket.js
+++ b/js/redactor-osticket.js
@@ -140,7 +140,12 @@ $(function() {
         var el = $(el),
             options = {
                 'air': el.hasClass('no-bar'),
-                'airButtons': ['formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'image'],
+                'airButtons': ['formatting', '|', 'bold', 'italic', 'underline', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'image'],
+                'buttons': ['html', '|', 'formatting', '|', 'bold',
+                    'italic', 'underline', 'deleted', '|', 'unorderedlist',
+                    'orderedlist', 'outdent', 'indent', '|', 'image', 'video',
+                    'file', 'table', 'link', '|', 'alignment', '|',
+                    'horizontalrule'],
                 'autoresize': !el.hasClass('no-bar'),
                 'minHeight': el.hasClass('small') ? 75 : 150,
                 'focus': false,
diff --git a/scp/forms.php b/scp/forms.php
index 5af2dd327781d18adca740b46ad654eb272929db..6f14be6c1a2bec06aea87c6f1c4c97d9db177efd 100644
--- a/scp/forms.php
+++ b/scp/forms.php
@@ -99,6 +99,7 @@ if($_POST) {
                 'private'=>$_POST["private-new-$i"] == 'on' ? 1 : 0,
                 'required'=>$_POST["required-new-$i"] == 'on' ? 1 : 0
             ));
+            $field->setForm($form);
             if ($field->isValid())
                 $field->save();
             else
diff --git a/setup/inc/class.installer.php b/setup/inc/class.installer.php
index 4ec86eee5479e8902871bc1081f407e19deb3bda..3912a3a99a8d66049adb9de64b51b49e837c248d 100644
--- a/setup/inc/class.installer.php
+++ b/setup/inc/class.installer.php
@@ -82,9 +82,7 @@ class Installer extends SetupWizard {
 
         // Support port number specified in the hostname with a colon (:)
         list($host, $port) = explode(':', $vars['dbhost']);
-        if ($port && (!is_numeric($port) || !((int)$port)))
-            $this->errors['db'] = 'Database port number must be a number';
-        elseif ($port && ($port < 1 || $port > 65535))
+        if ($port && is_numeric($port) && ($port < 1 || $port > 65535))
             $this->errors['db'] = 'Invalid database port number';
 
         //MYSQL: Connect to the DB and check the version & database (create database if it doesn't exist!)