diff --git a/WHATSNEW.md b/WHATSNEW.md
index cd03e65ce837b16d3fb34b45f30fbfe8c3592237..ce06e6a8acee174be1ff20dd5a6822deb904a771 100644
--- a/WHATSNEW.md
+++ b/WHATSNEW.md
@@ -1,3 +1,71 @@
+osTicket v1.9.4
+===============
+### Major New Features
+  * New ticket states (resolved, archived, and deleted) (#1094, #1159)
+  * Custom ticket statuses (#1159)
+  * Custom ticket number formats (#1128)
+  * Full text search capabilities (*beta*)
+  * Multiselect for choice fields and custom list selections
+  * Phase II Multi-Lingual Support (User Interface) (see
+    http://i18n.osticket.com and http://jipt.i18n.osticket.com) (#1096)
+    * Active interface translations of 46 languages currently
+    * Popup help tip documentation in all languages
+    * Flags displayed on client portal for manual switch of UI language by
+      EndUsers
+    * Automatic detection of enduser and agent language preference as
+      advertised by the browser
+    * Improved PDF ticket printing support, including greater support for
+      eastern characters such as Thai, Korean, Chinese, and Japanese
+    * Proper support for searching, including breaking words for languages
+      which do not use word breaks, such as Japanese
+    * Proper user interface layout for right-to-left languages such as Hebrew,
+      Arabic, and Farsi
+    * Right-to-Left support for the HTML text editor, regardless of the viewing
+      user’s current language setting
+    * Proper handling of bidirectional text in PDF output and in the ticket
+      view
+
+### Enhancements
+  * Plugins can have custom configurations (#1156)
+  * Upgrade to mPDF to v5.7.3 (#1356)
+  * Add support for PDF fonts in language packs (#1356)
+  * Advanced search improved to support multiple selections, custom status and flags
+
+### Improvements
+  * Fix display of text thread entries with HTML characters (`<`) (#1360)
+  * Fix crash creating new ticket if organization custom data has a selection field (#1361)
+  * Fix footer disappearance on PJAX navigation (#1366)
+  * Fix User Directory not sortable by user status (#1375)
+  * Fix loss of enduser or agent priority selection on new ticket (#1365)
+  * Add validation error if setting EndUser username to an email address (#1368)
+  * Fix skipped validation of some fields (#1369) (*regression from rc4*)
+  * Fix detection of inline attachments from rich text inputs (#1357)
+  * Fix dropping attachments when updating canned responses (#1357)
+  * Fix PJAX navigation crash in some browsers (#1378)
+  * Fix searching for tickets in the client portal (#1379) (*regression from rc4*)
+  * Fix crash submitting new ticket as agent with validation errors (#1380)
+  * Fix display of unanswered tickets in open queue (#1384)
+  * Fix incorrect statistics on dashboard page (#1345)
+  * Fix sorting by ticket number if using sequential numbers
+  * Fix threading if HTML is enabled and QR is disabled (#1197)
+  * Export ticket `created` date (#1201)
+  * Fix duplicate email where a collaborator would receive a confirmation
+    for his own message (#1235)
+  * Fix multi-line display of checkbox descriptions (#1160)
+  * Fix API validation failure for custom list selections (#1238)
+  * Fix crash adding a new user with a selection field custom data
+  * Fix failed user identification from email headers if `References` header
+    is sorted differently be mail client (#1263)
+  * Fix deletion of inline images on pages if draft was not saved (#1288)
+  * Fix corruption of custom date time fields on client portal if using non
+    US date format (#1320)
+  * Fix corruption of email mailbox if improperly encoded as ISO-8859-1
+    without RFC 2047 charset hint (#1332)
+  * Fix occasional MySQL Commands OOS error from ORM (#1334)
+
+### Performance and Security
+  * Fix possible XSS vulnerability in email template management (#1163)
+
 osTicket v1.9.3
 ===============
 ### Enhancements
diff --git a/ajax.php b/ajax.php
index bfa481a20aed7cf8c1d6134ff835a46584dcbb0e..8ea5226439f971b9cd917a13f53c099579394ed1 100644
--- a/ajax.php
+++ b/ajax.php
@@ -31,6 +31,7 @@ $dispatcher = patterns('',
     )),
     url('^/draft/', patterns('ajax.draft.php:DraftAjaxAPI',
         url_post('^(?P<id>\d+)$', 'updateDraftClient'),
+        url_delete('^(?P<id>\d+)$', 'deleteDraftClient'),
         url_post('^(?P<id>\d+)/attach$', 'uploadInlineImageClient'),
         url_get('^(?P<namespace>[\w.]+)$', 'getDraftClient'),
         url_post('^(?P<namespace>[\w.]+)$', 'createDraftClient')
diff --git a/include/ajax.draft.php b/include/ajax.draft.php
index 41fde2be24ff955b793f1fbf773b9d8372616c0b..f727e2cc9a10040786f6abb3502a8a21f7e04806 100644
--- a/include/ajax.draft.php
+++ b/include/ajax.draft.php
@@ -186,6 +186,23 @@ class DraftAjaxAPI extends AjaxController {
         return self::_updateDraft($draft);
     }
 
+    function deleteDraftClient($id) {
+        global $thisclient;
+
+        if (!($draft = Draft::lookup($id)))
+            Http::response(205, "Draft not found. Create one first");
+        elseif ($thisclient) {
+            if ($draft->getStaffId() != $thisclient->getId())
+                Http::response(404, "Draft not found");
+        }
+        else {
+            if (substr(session_id(), -12) != substr($draft->getNamespace(), -12))
+                Http::response(404, "Draft not found");
+        }
+
+        $draft->delete();
+    }
+
     function uploadInlineImageClient($id) {
         global $thisclient;
 
diff --git a/include/class.config.php b/include/class.config.php
index 8a7d5eebdb6d53a6d23d5667d543ded34ad8b5ca..04a7cea4546bdf65430034564fd3d2812a5fc50a 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -523,15 +523,6 @@ class OsticketConfig extends Config {
         return $this->get('max_file_size');
     }
 
-    function getStaffMaxFileUploads() {
-        return $this->get('max_staff_file_uploads');
-    }
-
-    function getClientMaxFileUploads() {
-        //TODO: change max_user_file_uploads to max_client_file_uploads
-        return $this->get('max_user_file_uploads');
-    }
-
     function getLogLevel() {
         return $this->get('log_level');
     }
@@ -971,7 +962,6 @@ class OsticketConfig extends Config {
             'enable_html_thread'=>isset($vars['enable_html_thread'])?1:0,
             'allow_client_updates'=>isset($vars['allow_client_updates'])?1:0,
             'max_file_size'=>$vars['max_file_size'],
-            'email_attachments'=>isset($vars['email_attachments'])?1:0,
         ));
     }
 
@@ -1005,6 +995,7 @@ class OsticketConfig extends Config {
             'accept_unregistered_email'=>isset($vars['accept_unregistered_email'])?1:0,
             'add_email_collabs'=>isset($vars['add_email_collabs'])?1:0,
             'reply_separator'=>$vars['reply_separator'],
+            'email_attachments'=>isset($vars['email_attachments'])?1:0,
          ));
     }
 
diff --git a/include/class.cron.php b/include/class.cron.php
index af5a1476865f7cf11f40f1b39f195f11a1660f63..f231ac5b3db00695cfa0a0a1045ff62d0bec9745 100644
--- a/include/class.cron.php
+++ b/include/class.cron.php
@@ -98,7 +98,9 @@ class Cron {
         self::MailFetcher();
         self::TicketMonitor();
         self::PurgeLogs();
-        self::CleanOrphanedFiles();
+        // Run file purging about every 10 cron runs
+        if (mt_rand(1, 9) == 4)
+            self::CleanOrphanedFiles();
         self::PurgeDrafts();
         self::MaybeOptimizeTables();
 
diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index 1b31e882d75025c248c48969627a2af75d617471..94b9b786e76c6cff6afede8f44f12ba509939cdd 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -690,7 +690,7 @@ class DynamicFormEntry extends VerySimpleModel {
     function getForm() {
         if (!isset($this->_form)) {
             $this->_form = DynamicForm::lookup($this->get('form_id'));
-            if (isset($this->id))
+            if ($this->_form && isset($this->id))
                 $this->_form->data($this);
         }
         return $this->_form;
@@ -1122,12 +1122,20 @@ class SelectionField extends FormField {
     }
 
     function to_php($value, $id=false) {
-        $value = ($value && !is_array($value))
-            ? JsonDataParser::parse($value) : $value;
+        if (is_string($value))
+            $value = JsonDataParser::parse($value) ?: $value;
+
         if (!is_array($value)) {
+            $config = $this->getConfiguration();
+            if (!$config['multiselect']) {
+                // CDATA may be built with comma-list
+                list($value,) = explode(',', $value, 2);
+            }
             $choices = $this->getChoices();
             if (isset($choices[$value]))
-                $value = $choices[$value];
+                $value = array($value => $choices[$value]);
+            elseif ($id && isset($choices[$id]))
+                $value = array($id => $choices[$id]);
         }
         // Don't set the ID here as multiselect prevents using exactly one
         // ID value. Instead, stick with the JSON value only.
diff --git a/include/class.error.php b/include/class.error.php
index 7e9ecb8a9cc15cc45d422fd7a13d39278af95091..72909a615e2a61cde374ccdcba9213a80f4e2515 100644
--- a/include/class.error.php
+++ b/include/class.error.php
@@ -27,7 +27,7 @@ class Error extends Exception {
         parent::__construct(__($message));
         $message = str_replace(ROOT_DIR, '(root)/', _S($message));
 
-        if ($ost->getConfig()->getLogLevel() == 3)
+        if ($ost && $ost->getConfig()->getLogLevel() == 3)
             $message .= "\n\n" . $this->getBacktrace();
 
         $ost->logError($this->getTitle(), $message, static::$sendAlert);
diff --git a/include/class.format.php b/include/class.format.php
index 55635f5aab7d6cd57dc6ad9b90c3cc9ff37b6ce3..2357e45e5583990767b37298d5dd6ced66517fed 100644
--- a/include/class.format.php
+++ b/include/class.format.php
@@ -30,6 +30,16 @@ class Format {
         return round(($bytes/1048576),1).' mb';
     }
 
+    function filesize2bytes($size) {
+        switch (substr($size, -1)) {
+        case 'M': case 'm': return (int)$size <<= 20;
+        case 'K': case 'k': return (int)$size <<= 10;
+        case 'G': case 'g': return (int)$size <<= 30;
+        }
+
+        return $size;
+    }
+
 	/* encode text into desired encoding - taking into accout charset when available. */
     function encode($text, $charset=null, $encoding='utf-8') {
 
diff --git a/include/class.forms.php b/include/class.forms.php
index 54ef6e9003821df2da2091adcf0b0125b01f61e1..c80c34ba1567cea62f7cb38150e11b020ca39228 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -676,7 +676,7 @@ class TextboxField extends FormField {
                     $wrapped = "/".$value."/iu";
                     if (false === @preg_match($value, ' ')
                             && false !== @preg_match($wrapped, ' ')) {
-                        return $wrapped;
+                        $value = $wrapped;
                     }
                     if ($value == '//iu')
                         return '';
@@ -950,16 +950,22 @@ class ChoiceField extends FormField {
         else
             $array = $value;
         $config = $this->getConfiguration();
-        if (is_array($array) && !$config['multiselect'] && count($array) < 2) {
-            reset($array);
-            return key($array);
+        if (!$config['multiselect']) {
+            if (is_array($array) && count($array) < 2) {
+                reset($array);
+                return key($array);
+            }
+            if (is_string($array) && strpos($array, ',') !== false) {
+                list($array,) = explode(',', $array, 2);
+            }
         }
         return $array;
     }
 
     function toString($value) {
         $selection = $this->getChoice($value);
-        return is_array($selection) ? implode(', ', array_filter($selection))
+        return is_array($selection)
+            ? (implode(', ', array_filter($selection)) ?: $value)
             : (string) $selection;
     }
 
@@ -1128,7 +1134,9 @@ class ThreadEntryField extends FormField {
 
         $attachments = new FileUploadField();
         $fileupload_config = $attachments->getConfigurationOptions();
-        $fileupload_config['extensions']->set('default', $cfg->getAllowedFileTypes());
+        if ($cfg->getAllowedFileTypes())
+            $fileupload_config['extensions']->set('default', $cfg->getAllowedFileTypes());
+
         return array(
             'attachments' => new BooleanField(array(
                 'label'=>__('Enable Attachments'),
@@ -1831,7 +1839,7 @@ class ChoicesWidget extends Widget {
             $values = array($values => $this->field->getChoice($values));
         }
 
-        if ($values === null)
+        if (!is_array($values))
             $values = $have_def ? array($def_key => $choices[$def_key]) : array();
 
         ?>
diff --git a/include/class.i18n.php b/include/class.i18n.php
index 4a9540520c26816b0c405ccfe318402ecf6ef840..4a0d138f295bec3586369127a16d255d3d51e61a 100644
--- a/include/class.i18n.php
+++ b/include/class.i18n.php
@@ -104,8 +104,18 @@ class Internationalization {
             }
         }
 
-        // Pages and content
+        // Load core config
         $_config = new OsticketConfig();
+
+        // Determine reasonable default max_file_size
+        $max_size = Format::filesize2bytes(strtoupper(ini_get('upload_max_filesize')));
+        $val = ((int) $max_size/2);
+        $po2 = 1;
+        while( $po2 < $val ) $po2 <<= 1;
+
+        $_config->set('max_file_size', $po2);
+
+        // Pages and content
         foreach (array('landing','thank-you','offline',
                 'registration-staff', 'pwreset-staff', 'banner-staff',
                 'registration-client', 'pwreset-client', 'banner-client',
diff --git a/include/class.list.php b/include/class.list.php
index dfee67d558d3ef2a56ac81f16e14fa8a090c82a7..f5f9c4bd2551f5605dc4228da9b827e6c62ac010 100644
--- a/include/class.list.php
+++ b/include/class.list.php
@@ -109,10 +109,15 @@ abstract class CustomListHandler {
         return $rv;
     }
 
+    function __get($field) {
+        return $this->_list->{$field};
+    }
+
     function update($vars, &$errors) {
         return $this->_list->update($vars, $errors);
     }
 
+    abstract function getListOrderBy();
     abstract function getNumItems();
     abstract function getAllItems();
     abstract function getItems($criteria);
@@ -334,6 +339,7 @@ class DynamicList extends VerySimpleModel implements CustomList {
             $this->set('updated', new SqlFunction('NOW'));
         if (isset($this->dirty['notes']))
             $this->notes = Format::sanitize($this->notes);
+
         return parent::save($refetch);
     }
 
@@ -656,6 +662,14 @@ class TicketStatusList extends CustomListHandler {
     var $_items;
     var $_form;
 
+    function getListOrderBy() {
+        switch ($this->getSortMode()) {
+            case 'Alpha':   return 'name';
+            case '-Alpha':  return '-name';
+            case 'SortCol': return 'sort';
+        }
+    }
+
     function getNumItems() {
         return TicketStatus::objects()->count();
     }
@@ -774,8 +788,6 @@ class TicketStatus  extends VerySimpleModel implements CustomListItem {
     const ENABLED   = 0x0001;
     const INTERNAL  = 0x0002; // Forbid deletion or name and status change.
 
-
-
     function __construct() {
         call_user_func_array(array('parent', '__construct'), func_get_args());
     }
diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php
index 4e6546549f44d7ad520670bb01af8e946534aaea..1210bffa3f3bfd924169e87939d369c229a4a9ce 100644
--- a/include/class.mailfetch.php
+++ b/include/class.mailfetch.php
@@ -138,7 +138,7 @@ class MailFetcher {
             $args += array(NULL, 0, array(
                 'DISABLE_AUTHENTICATOR' => array('GSSAPI', 'NTLM')));
 
-        $this->mbox = call_user_func_array('imap_open', $args);
+        $this->mbox = @call_user_func_array('imap_open', $args);
 
         return $this->mbox;
     }
diff --git a/include/class.search.php b/include/class.search.php
index c3b2b2c1e0cb2ef30aef3ddd43cadc53d2bf15d1..fd8c7e2cff06868b63439c980252d4b4719a71a0 100644
--- a/include/class.search.php
+++ b/include/class.search.php
@@ -257,12 +257,34 @@ class MysqlSearchBackend extends SearchBackend {
         return db_query($sql);
     }
 
+    // Quote things like email addresses
+    function quote($query) {
+        $parts = array();
+        if (!preg_match_all('`([^\s"\']+)|"[^"]*"|\'[^\']*\'`', $query, $parts,
+                PREG_SET_ORDER))
+            return $query;
+
+        $results = array();
+        foreach ($parts as $m) {
+            // Check for quoting
+            if ($m[1] // Already quoted?
+                && preg_match('`@`u', $m[0])
+            ) {
+                $char = strpos($m[1], '"') ? "'" : '"';
+                $m[0] = $char . $m[0] . $char;
+            }
+            $results[] = $m[0];
+        }
+        return implode(' ', $results);
+    }
+
     function find($query, $criteria=array(), $model=false, $sort=array()) {
         global $thisstaff;
 
         $mode = ' IN BOOLEAN MODE';
         #if (count(explode(' ', $query)) == 1)
         #    $mode = ' WITH QUERY EXPANSION';
+        $query = $this->quote($query);
         $search = 'MATCH (search.title, search.content) AGAINST ('
             .db_input($query)
             .$mode.')';
@@ -403,14 +425,30 @@ class MysqlSearchBackend extends SearchBackend {
     }
 
     static function createSearchTable() {
-        $sql = 'CREATE TABLE IF NOT EXISTS '.TABLE_PREFIX.'_search (
+        // Use InnoDB with Galera, MyISAM with v5.5, and the database
+        // default otherwise
+        $sql = "select count(*) from information_schema.tables where
+            table_schema='information_schema' and table_name =
+            'INNODB_FT_CONFIG'";
+        $mysql56 = db_result(db_query($sql));
+
+        $sql = "show status like 'wsrep_local_state'";
+        $galera = db_result(db_query($sql));
+
+        if ($galera && !$mysql56)
+            throw new Exception('Galera cannot be used with MyISAM tables');
+        $engine = $galera ? 'InnodB' : ($mysql56 ? '' : 'MyISAM');
+        if ($engine)
+            $engine = 'ENGINE='.$engine;
+
+        $sql = 'CREATE TABLE IF NOT EXISTS '.TABLE_PREFIX."_search (
             `object_type` varchar(8) not null,
             `object_id` int(11) unsigned not null,
             `title` text collate utf8_general_ci,
             `content` text collate utf8_general_ci,
             primary key `object` (`object_type`, `object_id`),
             fulltext key `search` (`title`, `content`)
-        ) ENGINE=MyISAM CHARSET=utf8';
+        ) $engine CHARSET=utf8";
         return db_query($sql);
     }
 
diff --git a/include/class.sequence.php b/include/class.sequence.php
index 1b3fc3182ec1e890d8517d3c2969818d862fb40a..cc27801c596a8dd17acac088286e15bc7de6b4aa 100644
--- a/include/class.sequence.php
+++ b/include/class.sequence.php
@@ -151,6 +151,9 @@ class Sequence extends VerySimpleModel {
      * and assured to be session-wise atomic before the value is returned.
      */
     function __next($digits=false) {
+        // Ensure this block is executed in a single transaction
+        db_autocommit(false);
+
         // Lock the database object -- this is important to handle concurrent
         // requests for new numbers
         static::objects()->filter(array('id'=>$this->id))->lock()->one();
@@ -161,6 +164,8 @@ class Sequence extends VerySimpleModel {
         $this->updated = SqlFunction::NOW();
         $this->save();
 
+        db_autocommit(true);
+
         return $next;
     }
 
diff --git a/include/class.thread.php b/include/class.thread.php
index c009ed2fbe4c4323a1138b0f6cdb2ac665a62aa0..b57da97133ac53e51f11bb8fd87a3ed6370ef1a6 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -1427,7 +1427,7 @@ class TextThreadBody extends ThreadBody {
 }
 class HtmlThreadBody extends ThreadBody {
     function __construct($body, $options=array()) {
-        if ($options['strip-embedded'])
+        if (!isset($options['strip-embedded']) || $options['strip-embedded'])
             $body = $this->extractEmbeddedHtmlImages($body);
         parent::__construct($body, 'html', $options);
     }
diff --git a/include/class.ticket.php b/include/class.ticket.php
index c4b6d3e94a36cb9c38b83ad0a411f4779151c9d9..e65347a03663ef74276f52e78f054a175856d7bc 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -54,12 +54,6 @@ class Ticket {
 
     var $thread; //Thread obj.
 
-    // Status -- listed here until we have a formal status class
-    static $STATUSES = array(
-        /* @trans */ 'open',
-        /* @trans */ 'closed',
-    );
-
     function Ticket($id) {
         $this->id = 0;
         $this->load($id);
@@ -2368,11 +2362,14 @@ class Ticket {
         // Create and verify the dynamic form entry for the new ticket
         $form = TicketForm::getNewInstance();
         $form->setSource($vars);
-        // If submitting via email, ensure we have a subject and such
-        foreach ($form->getFields() as $field) {
-            $fname = $field->get('name');
-            if ($fname && isset($vars[$fname]) && !$field->value)
-                $field->value = $field->parse($vars[$fname]);
+
+        // If submitting via email or api, ensure we have a subject and such
+        if (!in_array(strtolower($origin), array('web', 'staff'))) {
+            foreach ($form->getFields() as $field) {
+                $fname = $field->get('name');
+                if ($fname && isset($vars[$fname]) && !$field->value)
+                    $field->value = $field->parse($vars[$fname]);
+            }
         }
 
         if (!$form->isValid($field_filter('ticket')))
diff --git a/include/class.topic.php b/include/class.topic.php
index 1dd6d0b48f67a6fa9bb3e2c5bbf30e463337990f..9139f9e6d0926efe6d27bc2dc9145ef02f17b151 100644
--- a/include/class.topic.php
+++ b/include/class.topic.php
@@ -256,7 +256,7 @@ class Topic {
 
     static function getHelpTopics($publicOnly=false, $disabled=false) {
         global $cfg;
-        static $topics, $names;
+        static $topics, $names = array();
 
         if (!$names) {
             $sql = 'SELECT topic_id, topic_pid, ispublic, isactive, topic FROM '.TOPIC_TABLE
diff --git a/include/client/header.inc.php b/include/client/header.inc.php
index 4cb8b22ca788748a2e928abbafbd2308cbbf5fb5..92e9dfe13bdda7cc9ad08fc6cedb9b6f7c0d0b7b 100644
--- a/include/client/header.inc.php
+++ b/include/client/header.inc.php
@@ -63,7 +63,7 @@ if (($lang = Internationalization::getCurrentLanguage())
                     && !$thisclient->isGuest()) {
                  echo Format::htmlchars($thisclient->getName()).'&nbsp;|';
                  ?>
-                <a href="<?php echo ROOT_PATH; ?>account.php"><?php echo __('Profile'); ?></a> |
+                <a href="<?php echo ROOT_PATH; ?>profile.php"><?php echo __('Profile'); ?></a> |
                 <a href="<?php echo ROOT_PATH; ?>tickets.php"><?php echo sprintf(__('Tickets <b>(%d)</b>'), $thisclient->getNumTickets()); ?></a> -
                 <a href="<?php echo $signout_url; ?>"><?php echo __('Sign Out'); ?></a>
             <?php
diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php
index 2c1a04a97c208e2bdadc6281cca2ee0cd093e9c0..6b8e9df1af38ee40b56dcc831ad260c156d5e235 100644
--- a/include/client/tickets.inc.php
+++ b/include/client/tickets.inc.php
@@ -157,11 +157,14 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting
     </thead>
     <tbody>
     <?php
+     $subject_field = TicketForm::objects()->one()->getField('subject');
      if($res && ($num=db_num_rows($res))) {
         $defaultDept=Dept::getDefaultDeptName(); //Default public dept.
         while ($row = db_fetch_array($res)) {
             $dept= $row['ispublic']? $row['dept_name'] : $defaultDept;
-            $subject=Format::htmlchars(Format::truncate($row['subject'],40));
+            $subject = Format::truncate($subject_field->display(
+                $subject_field->to_php($row['subject']) ?: $row['subject']
+            ), 40);
             if($row['attachments'])
                 $subject.='  &nbsp;&nbsp;<span class="Icon file"></span>';
 
diff --git a/include/i18n/en_US/config.yaml b/include/i18n/en_US/config.yaml
index a63764ba9c1b3b1aeb8b56488c0cbc6c4aa4409e..b29df10ace86324407ba25ab5605ed7d569cdf04 100644
--- a/include/i18n/en_US/config.yaml
+++ b/include/i18n/en_US/config.yaml
@@ -15,7 +15,6 @@ core:
     reply_separator: '-- reply above this line --'
 
     # Do not translate below here
-    allowed_filetypes: '.doc, .pdf, .jpg, .jpeg, .gif, .png, .xls, .docx, .xlsx, .txt'
     isonline: 1
     staff_ip_binding: 0
     staff_max_logins: 4
@@ -27,9 +26,6 @@ core:
     client_session_timeout: 30
     max_page_size: 25
     max_open_tickets: 0
-    max_file_size: 1048576
-    max_user_file_uploads: 1
-    max_staff_file_uploads: 1
     autolock_minutes: 3
     default_priority_id: 2
     default_smtp_id: 0
diff --git a/include/mysqli.php b/include/mysqli.php
index 71681975cd8bdd8e1281a3290eac9234690fa6af..ed70cd82ef3c5fc8ae0f209759ed9c969f2ceb61 100644
--- a/include/mysqli.php
+++ b/include/mysqli.php
@@ -76,18 +76,7 @@ function db_connect($host, $user, $passwd, $options = array()) {
 
     @db_set_variable('sql_mode', '');
 
-    // Start a new transaction -- for performance. Transactions are always
-    // committed at shutdown (below)
-    $__db->autocommit(false);
-
-    // Auto commit the transaction at shutdown and re-enable statement-level
-    // autocommit
-    register_shutdown_function(function() {
-        global $__db, $err;
-        if (!$__db->commit())
-            $err = 'Unable to save changes to database';
-        $__db->autocommit(true);
-    });
+    $__db->autocommit(true);
 
     // Use connection timing to seed the random number generator
     Misc::__rand_seed((microtime(true) - $start) * 1000000);
@@ -95,6 +84,12 @@ function db_connect($host, $user, $passwd, $options = array()) {
     return $__db;
 }
 
+function db_autocommit($enable=true) {
+    global $__db;
+
+    return $__db->autocommit($enable);
+}
+
 function db_close() {
     global $__db;
     return @$__db->close();
diff --git a/include/staff/dynamic-list.inc.php b/include/staff/dynamic-list.inc.php
index f42abef7c27a9b035c5d4927147219bed01a5ad4..0b4ed80996b418ff19d6bf020dc7bc190e08ef37 100644
--- a/include/staff/dynamic-list.inc.php
+++ b/include/staff/dynamic-list.inc.php
@@ -8,7 +8,7 @@ if ($list) {
     $info = $list->getInfo();
     $newcount=2;
 } else {
-    $title = __('Add new custom list');
+    $title = __('Add New Custom List');
     $action = 'add';
     $submit_text = __('Add List');
     $newcount=4;
diff --git a/include/staff/settings-emails.inc.php b/include/staff/settings-emails.inc.php
index 228791074b65c01b5afd8d3ca254a71ee12d69e9..b10facabe98d61a05eccf10736e37b4269721c20 100644
--- a/include/staff/settings-emails.inc.php
+++ b/include/staff/settings-emails.inc.php
@@ -153,6 +153,14 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config)
                  <i class="help-tip icon-question-sign" href="#default_mta"></i>
            </td>
        </tr>
+        <tr>
+            <td width="180"><?php echo __('Attachments');?>:</td>
+            <td>
+                <input type="checkbox" name="email_attachments" <?php echo $config['email_attachments']?'checked="checked"':''; ?>>
+                <?php echo __('Email attachments to the user'); ?>
+                <i class="help-tip icon-question-sign" href="#ticket_response_files"></i>
+            </td>
+        </tr>
     </tbody>
 </table>
 <p style="padding-left:250px;">
diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php
index 2e23da95068b404e91ace8b02749690fb2b9a802..d64c52ed12a4f7e4148598e75d292a93430cbe59 100644
--- a/include/staff/settings-tickets.inc.php
+++ b/include/staff/settings-tickets.inc.php
@@ -268,14 +268,6 @@ if(!($maxfileuploads=ini_get('max_file_uploads')))
                 <div class="error"><?php echo $errors['max_file_size']; ?></div>
             </td>
         </tr>
-        <tr>
-            <td width="180"><?php echo __('Ticket Response Files');?>:</td>
-            <td>
-                <input type="checkbox" name="email_attachments" <?php echo $config['email_attachments']?'checked="checked"':''; ?>>
-                <?php echo __('Email attachments to the user'); ?>
-                <i class="help-tip icon-question-sign" href="#ticket_response_files"></i>
-            </td>
-        </tr>
         <?php if (($bks = FileStorageBackend::allRegistered())
                 && count($bks) > 1) { ?>
         <tr>
diff --git a/include/staff/templates/ticket-status.tmpl.php b/include/staff/templates/ticket-status.tmpl.php
index c033e0caa8d541b2a9bbed596fb3e8ba2d8c7c80..5e0d84ae3c82be28c5880b5444ffc65ea513a279 100644
--- a/include/staff/templates/ticket-status.tmpl.php
+++ b/include/staff/templates/ticket-status.tmpl.php
@@ -110,9 +110,12 @@ $action = $info['action'] ?: ('#tickets/status/'. $state);
 $(function() {
     // Copy checked tickets to status form.
     $('form#tickets input[name="tids[]"]:checkbox:checked')
-    .clone()
-    .prop('type', 'hidden')
-    .removeAttr('class')
-    .appendTo('form#status');
- });
+    .each(function() {
+        $('<input>')
+        .prop('type', 'hidden')
+        .attr('name', 'tids[]')
+        .val($(this).val())
+        .appendTo('form#status');
+    });
+});
 </script>
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index f6c17982c7aa39e88027b0b3f46a0c171e045360..70acec9195642decac3530a7be2267fb00a176b2 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -406,6 +406,8 @@ if ($results) {
      </thead>
      <tbody>
         <?php
+        // Setup Subject field for display
+        $subject_field = TicketForm::objects()->one()->getField('subject');
         $class = "row1";
         $total=0;
         if($res && ($num=count($results))):
@@ -430,7 +432,10 @@ if ($results) {
                     $lc=Format::truncate($row['dept_name'],40);
                 }
                 $tid=$row['number'];
-                $subject = Format::htmlchars(Format::truncate($row['subject'],40));
+
+                $subject = Format::truncate($subject_field->display(
+                    $subject_field->to_php($row['subject']) ?: $row['subject']
+                ), 40);
                 $threadcount=$row['thread_count'];
                 if(!strcasecmp($row['state'],'open') && !$row['isanswered'] && !$row['lock_id']) {
                     $tid=sprintf('<b>%s</b>',$tid);
diff --git a/js/filedrop.field.js b/js/filedrop.field.js
index d07a2a675455cf3c5af0f8046a82aa5822c4497b..4eb5d27e91ebdb78033b0e6cdab7e4d20b57f0f8 100644
--- a/js/filedrop.field.js
+++ b/js/filedrop.field.js
@@ -554,7 +554,7 @@
             if (fileIndex === files_count) {
               return;
             }
-            var reader = new FileReader(),
+            var reader = new window.FileReader(),
                 max_file_size = 1048576 * opts.maxfilesize;
 
             reader.index = fileIndex;
@@ -807,26 +807,4 @@
 
   function empty() {}
 
-  try {
-    if (XMLHttpRequest.prototype.sendAsBinary) {
-        return;
-    }
-    XMLHttpRequest.prototype.sendAsBinary = function(datastr) {
-      function byteValue(x) {
-        return x.charCodeAt(0) & 0xff;
-      }
-      var ords = Array.prototype.map.call(datastr, byteValue);
-      var ui8a = new Uint8Array(ords);
-
-      // Not pretty: Chrome 22 deprecated sending ArrayBuffer, moving instead
-      // to sending ArrayBufferView.  Sadly, no proper way to detect this
-      // functionality has been discovered.  Happily, Chrome 22 also introduced
-      // the base ArrayBufferView class, not present in Chrome 21.
-      if ('ArrayBufferView' in window)
-        this.send(ui8a);
-      else
-        this.send(ui8a.buffer);
-    };
-  } catch (e) {}
-
 })(jQuery);
diff --git a/logout.php b/logout.php
index 4b9ea91b133fae4adf96823b8037428d1ed5acd0..74d73cc377b58049f286551771ff880dfc800fab 100644
--- a/logout.php
+++ b/logout.php
@@ -16,10 +16,9 @@
 
 require('client.inc.php');
 //Check token: Make sure the user actually clicked on the link to logout.
-if(!$thisclient || !$_GET['auth'] || !$ost->validateLinkToken($_GET['auth']))
-   @header('Location: index.php');
+if ($thisclient && $_GET['auth'] && $ost->validateLinkToken($_GET['auth']))
+   $thisclient->logOut();
 
-$thisclient->logOut();
-header('Location: index.php');
-require('index.php');
+
+Http::redirect('index.php');
 ?>
diff --git a/scp/autocron.php b/scp/autocron.php
index b6016399e0b4745c62891dd7cce4b3f77df6fcca..eabc67151374c7d28eaf4895efe409f21652e5d9 100644
--- a/scp/autocron.php
+++ b/scp/autocron.php
@@ -44,6 +44,11 @@ require_once(INCLUDE_DIR.'class.cron.php');
 
 $thisstaff = null; //Clear staff obj to avoid false credit internal notes & auto-assignment
 Cron::TicketMonitor(); //Age tickets: We're going to age tickets regardless of cron settings.
+
+// Run file purging about every 30 minutes
+if (mt_rand(1, 9) == 4)
+    Cron::CleanOrphanedFiles();
+
 if($cfg && $cfg->isAutoCronEnabled()) { //ONLY fetch tickets if autocron is enabled!
     Cron::MailFetcher();  //Fetch mail.
     $ost->logDebug(_S('Auto Cron'), sprintf(_S('Mail fetcher cron call [%s]'), $caller));
diff --git a/scp/js/dashboard.inc.js b/scp/js/dashboard.inc.js
index b16aebf103a94e8c0f22e873e28681227e797da1..c902e4be2d77352909602d2d72b31758909136a8 100644
--- a/scp/js/dashboard.inc.js
+++ b/scp/js/dashboard.inc.js
@@ -140,7 +140,7 @@
             method:     'GET',
             dataType:   'json',
             url:        'ajax.php/report/overview/table',
-            data:       {group: group, start: start, stop: stop},
+            data:       {group: group, start: start, period: stop},
             success:    function(json) {
                 var q = $('<table>').attr({'class':'table table-condensed table-striped'}),
                     h = $('<tr>').appendTo($('<thead>').appendTo(q)),
diff --git a/setup/cli/modules/deploy.php b/setup/cli/modules/deploy.php
index 19aa38cee7e62b6b24cdde477c24173740052ca9..f402e99e2fb355159983473232f6e6f8b710e8b8 100644
--- a/setup/cli/modules/deploy.php
+++ b/setup/cli/modules/deploy.php
@@ -106,10 +106,10 @@ class Deployment extends Unpacker {
             return parent::copyFile($src, $dest);
 
         $source = file_get_contents($src);
-        $source = preg_replace(':<script(.*) src="(.*).js"></script>:',
+        $source = preg_replace(':<script(.*) src="([^"]+)\.js"></script>:',
             '<script$1 src="$2.js?'.$short.'"></script>',
             $source);
-        $source = preg_replace(':<link(.*) href="(.*).css"([^/>]*)/?>:', # <?php
+        $source = preg_replace(':<link(.*) href="([^"]+)\.css"([^/>]*)/?>:', # <?php
             '<link$1 href="$2.css?'.$short.'"$3/>',
             $source);
         // Set THIS_VERSION
diff --git a/setup/cli/modules/i18n.php b/setup/cli/modules/i18n.php
index 57a8bd0abf91f0d5fc6d016c22c56928bfeb9199..fca97e411f50560c9e8a4e3d0b5b1d18f026edb5 100644
--- a/setup/cli/modules/i18n.php
+++ b/setup/cli/modules/i18n.php
@@ -253,8 +253,6 @@ class i18n_Compiler extends Module {
             );
         }
 
-        list($code, $zip) = $this->_request("download/$lang.zip");
-
         // Include a manifest
         include_once INCLUDE_DIR . 'class.mailfetch.php';