From f7384359b09be1d29107397e087167d6351f46c6 Mon Sep 17 00:00:00 2001
From: Jared Hancock <jared@osticket.com>
Date: Tue, 8 Oct 2013 23:09:30 +0000
Subject: [PATCH] Significant fixes made after final feature review

---
 assets/default/css/theme.css                  |   4 +-
 include/ajax.content.php                      |  15 +-
 include/ajax.forms.php                        |  47 +++----
 include/ajax.tickets.php                      |  18 ++-
 include/ajax.users.php                        |   2 +-
 include/class.client.php                      |  23 +--
 include/class.config.php                      |  16 +--
 include/class.dynamic_forms.php               |  96 +++++++++----
 include/class.filter.php                      |   2 +-
 include/class.forms.php                       | 131 +++++++++++-------
 include/class.staff.php                       |   3 +-
 include/class.ticket.php                      |  39 ++++--
 include/class.topic.php                       |   2 +-
 include/class.user.php                        |  97 +++++++++++--
 include/client/open.inc.php                   |  40 ++----
 .../client/templates/dynamic-form.tmpl.php    |  18 +--
 include/client/tickets.inc.php                |   2 +-
 include/i18n/en_US/form.yaml                  |  35 ++++-
 include/i18n/en_US/help_topic.yaml            |   4 +-
 include/staff/dynamic-form.inc.php            |  81 +++++------
 include/staff/dynamic-forms.inc.php           |  57 +++++++-
 include/staff/dynamic-list.inc.php            |  19 +--
 include/staff/dynamic-lists.inc.php           |  57 +++++++-
 include/staff/settings-emails.inc.php         |  17 +++
 include/staff/settings-system.inc.php         |  30 ++--
 include/staff/settings-tickets.inc.php        |   7 -
 include/staff/templates/user-info.tmpl.php    |   1 -
 include/staff/ticket-open.inc.php             |   3 +-
 open.php                                      |  11 ++
 scp/css/scp.css                               |   6 +-
 scp/forms.php                                 |  34 ++++-
 scp/js/scp.js                                 |   9 +-
 scp/lists.php                                 |  24 ++++
 scp/tickets.php                               |   7 +-
 setup/doc/forms.md                            |  51 +++++++
 setup/doc/orm.md                              |  11 ++
 36 files changed, 705 insertions(+), 314 deletions(-)
 create mode 100644 setup/doc/forms.md
 create mode 100644 setup/doc/orm.md

diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css
index 4a1af12ac..bacbd67a0 100644
--- a/assets/default/css/theme.css
+++ b/assets/default/css/theme.css
@@ -532,9 +532,11 @@ body {
   width: 140px;
   float: left;
 }
+label.required {
+  font-weight: bold;
+}
 #ticketForm div label.required,
 #clientLogin div label.required {
-  font-weight: bold;
   text-align: left;
 }
 #ticketForm div input,
diff --git a/include/ajax.content.php b/include/ajax.content.php
index 197d75ffe..2451880ac 100644
--- a/include/ajax.content.php
+++ b/include/ajax.content.php
@@ -49,7 +49,8 @@ class ContentAjaxAPI extends AjaxController {
                     <tr><td width="130">%{ticket.id}</td><td>Ticket ID (internal ID)</td></tr>
                     <tr><td>%{ticket.number}</td><td>Ticket number (external ID)</td></tr>
                     <tr><td>%{ticket.email}</td><td>Email address</td></tr>
-                    <tr><td>%{ticket.name}</td><td>Full name</td></tr>
+                    <tr><td>%{ticket.name}</td><td>Full name &mdash;
+                        <em>see name expansion</em></td></tr>
                     <tr><td>%{ticket.subject}</td><td>Subject</td></tr>
                     <tr><td>%{ticket.phone}</td><td>Phone number | ext</td></tr>
                     <tr><td>%{ticket.status}</td><td>Status</td></tr>
@@ -80,6 +81,18 @@ class ContentAjaxAPI extends AjaxController {
                     <tr><td>%{reset_link}</td>
                         <td>Reset link used by the password reset feature</td></tr>
                 </table>
+                <table width="100%" border="0" cellspacing=1 cellpadding=1>
+                    <tr><td colspan="2"><b>Name Expansion</b></td></tr>
+                    <tr><td>.first</td><td>First Name</td></tr>
+                    <tr><td>.middle</td><td>Middle Name(s)</td></tr>
+                    <tr><td>.last</td><td>Last Name</td></tr>
+                    <tr><td>.full</td><td>First Last</td></tr>
+                    <tr><td>.legal</td><td>First M. Last</td></tr>
+                    <tr><td>.short</td><td>First L.</td></tr>
+                    <tr><td>.formal</td><td>Mr. Last</td></tr>
+                    <tr><td>.shortformal</td><td>F. Last</td></tr>
+                    <tr><td>.lastfirst</td><td>Last, First</td></tr>
+                </table>
             </td>
         </tr>
     </table>
diff --git a/include/ajax.forms.php b/include/ajax.forms.php
index 27fac6672..a04c327a0 100644
--- a/include/ajax.forms.php
+++ b/include/ajax.forms.php
@@ -15,7 +15,8 @@ class DynamicFormsAjaxAPI extends AjaxController {
 
     function getFormsForHelpTopic($topic_id, $client=false) {
         $topic = Topic::lookup($topic_id);
-        if ($form =DynamicForm::lookup($topic->ht['form_id']))
+        if ($topic->ht['form_id']
+                && ($form = DynamicForm::lookup($topic->ht['form_id'])))
             $form->render(!$client);
     }
 
@@ -36,31 +37,21 @@ class DynamicFormsAjaxAPI extends AjaxController {
             $field->save();
     }
 
-    function _getUserForms() {
-        $static = new Form(array(
-            'name' => new TextboxField(array(
-                'label'=>'Full Name', 'required'=>true, 'configuration'=>array('size'=>40))
-            ),
-            'email' => new TextboxField(array(
-                'label'=>'Default Email', 'required'=>true, 'configuration'=>array(
-                    'validator'=>'email', 'size'=>40))
-            ),
-        ));
-
-        return $static;
-    }
-
     function getUserInfo($user_id) {
         $user = User::lookup($user_id);
-        $static = $this->_getUserForms();
 
         $data = $user->ht;
         $data['email'] = $user->default_email->address;
-        $static->data($data);
 
         $custom = array();
         foreach ($user->getDynamicData() as $cd) {
             $cd->addMissingFields();
+            foreach ($cd->getFields() as $f) {
+                if ($f->get('name') == 'name')
+                    $f->value = $user->getFullName();
+                elseif ($f->get('name') == 'email')
+                    $f->value = $user->getEmail();
+            }
             $custom[] = $cd->getForm();
         }
 
@@ -69,11 +60,10 @@ class DynamicFormsAjaxAPI extends AjaxController {
 
     function saveUserInfo($user_id) {
         $user = User::lookup($user_id);
-        $static = $this->_getUserForms();
-        $valid = $static->isValid();
 
         $custom_data = $user->getDynamicData();
         $custom = array();
+        $valid = true;
         foreach ($custom_data as $cd) {
             $cd->addMissingFields();
             $cf = $custom[] = $cd->getForm();
@@ -85,15 +75,20 @@ class DynamicFormsAjaxAPI extends AjaxController {
             return;
         }
 
-        $data = $static->getClean();
-        $user->name = $data['name'];
-        $user->default_email->address = $data['email'];
-        $user->default_email->save();
-        $user->save();
-
         // Save custom data
-        foreach ($custom_data as $cd)
+        foreach ($custom_data as $cd) {
+            foreach ($cd->getFields() as $f) {
+                if ($f->get('name') == 'name') {
+                    $user->name = $f->getClean();
+                    $user->save();
+                }
+                elseif ($f->get('name') == 'email') {
+                    $user->default_email->address = $f->getClean();
+                    $user->default_email->save();
+                }
+            }
             $cd->save();
+        }
     }
 }
 
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index c664466fb..0f373efd6 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -49,9 +49,14 @@ class TicketsAjaxAPI extends AjaxController {
             .' ORDER BY ticket.created LIMIT '.$limit;
 
         if(($res=db_query($sql)) && db_num_rows($res)) {
-            while(list($id, $email)=db_fetch_row($res))
-                $tickets[] = array('id'=>$id, 'email'=>$email, 'value'=>$id, 'info'=>"$id - $email");
+            while(list($id, $email)=db_fetch_row($res)) {
+                $info = "$id - $email";
+                $tickets[] = array('id'=>$id, 'email'=>$email, 'value'=>$id,
+                    'info'=>$info, 'matches'=>$_REQUEST['q']);
+            }
         }
+        if (!$tickets)
+            return self::lookupByEmail();
 
         return $this->json_encode($tickets);
     }
@@ -67,7 +72,11 @@ class TicketsAjaxAPI extends AjaxController {
             .' FROM '.TICKET_TABLE.' ticket'
             .' JOIN '.USER_TABLE.' user ON user.id = ticket.user_id'
             .' JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id'
-            .' WHERE email.address LIKE \'%'.db_input(strtolower($_REQUEST['q']), false).'%\' ';
+            .' LEFT JOIN '.FORM_ENTRY_TABLE.' entry ON (entry.object_id = user.id
+                AND entry.object_type=\'U\')
+               LEFT JOIN '.FORM_ANSWER_TABLE.' data ON (data.entry_id = entry.id)'
+            .' WHERE (email.address LIKE \'%'.db_input(strtolower($_REQUEST['q']), false).'%\'
+                OR data.value LIKE \'%'.db_input($_REQUEST['q'], false).'%\')';
 
         $sql.=' AND ( staff_id='.db_input($thisstaff->getId());
 
@@ -83,7 +92,8 @@ class TicketsAjaxAPI extends AjaxController {
 
         if(($res=db_query($sql)) && db_num_rows($res)) {
             while(list($email, $count)=db_fetch_row($res))
-                $tickets[] = array('email'=>$email, 'value'=>$email, 'info'=>"$email ($count)");
+                $tickets[] = array('email'=>$email, 'value'=>$email,
+                    'info'=>"$email ($count)", 'matches'=>$_REQUEST['q']);
         }
 
         return $this->json_encode($tickets);
diff --git a/include/ajax.users.php b/include/ajax.users.php
index 46e868827..a075ff421 100644
--- a/include/ajax.users.php
+++ b/include/ajax.users.php
@@ -31,7 +31,7 @@ class UsersAjaxAPI extends AjaxController {
         $limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25;
         $users=array();
 
-        $sql='SELECT DISTINCT email.address, concat_ws(" ", first, last) as name '
+        $sql='SELECT DISTINCT email.address, name '
             .' FROM '.USER_TABLE.' user '
             .' JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id '
             .' WHERE email.address LIKE \'%'.db_input(strtolower($_REQUEST['q']), false).'%\' '
diff --git a/include/class.client.php b/include/class.client.php
index 3c4ce5243..3ee32c8f7 100644
--- a/include/class.client.php
+++ b/include/class.client.php
@@ -24,6 +24,8 @@ class Client {
     var $username;
     var $email;
 
+    var $_answers;
+
     var $ticket_id;
     var $ticketID;
 
@@ -40,15 +42,10 @@ class Client {
         if(!$id && !($id=$this->getId()))
             return false;
 
-        $sql='SELECT ticket.ticket_id, ticketID, email.address as email, phone.value as phone '
+        $sql='SELECT ticket.ticket_id, ticketID, email.address as email '
             .' 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 '.FORM_ENTRY_TABLE.' entry ON
-                    (entry.object_id = ticket.ticket_id AND entry.object_type = "T")'
-            .' LEFT JOIN '.FORM_ANSWER_TABLE.' phone ON phone.entry_id = entry.id'
-            .' LEFT JOIN '.FORM_FIELD_TABLE.' field ON
-                   (phone.field_id = field.id AND field.name="phone")'
             .' WHERE ticketID='.db_input($id);
 
         if($email)
@@ -73,6 +70,14 @@ class Client {
         return($this->id);
     }
 
+    function loadDynamicData() {
+        $this->_answers = array();
+        foreach (DynamicFormEntry::forClient($this->getId()) as $form)
+            foreach ($form->getAnswers() as $answer)
+                $this->_answers[$answer->getField()->get('name')] =
+                    $answer->getValue();
+    }
+
     function reload() {
         return $this->load();
     }
@@ -98,11 +103,7 @@ class Client {
     }
 
     function getPhone() {
-        return $this->ht['phone'];
-    }
-
-    function getPhoneExt() {
-        return $this->ht['phone_ext'];
+        return $this->_answers['phone'];
     }
 
     function getTicketID() {
diff --git a/include/class.config.php b/include/class.config.php
index 5d0c8160f..acd72ca30 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -146,6 +146,7 @@ class OsticketConfig extends Config {
         'allow_email_attachments' => true,
         'allow_online_attachments' => true,
         'allow_online_attachments_onlogin' => false,
+        'name_format' =>        'full', # First Last
     );
 
     function OsticketConfig($section=null) {
@@ -326,6 +327,10 @@ class OsticketConfig extends Config {
         return $this->get('autolock_minutes');
     }
 
+    function getDefaultNameFormat() {
+        return $this->get('name_format');
+    }
+
     function getDefaultDeptId() {
         return $this->get('default_dept_id');
     }
@@ -519,11 +524,6 @@ class OsticketConfig extends Config {
         return ($this->get('enable_mail_polling'));
     }
 
-    function allowPriorityChange() {
-        return ($this->get('allow_priority_change'));
-    }
-
-
     function useEmailPriority() {
         return ($this->get('use_email_priority'));
     }
@@ -768,7 +768,6 @@ class OsticketConfig extends Config {
         $f['helpdesk_url']=array('type'=>'string',   'required'=>1, 'error'=>'Helpdesk URl required');
         $f['helpdesk_title']=array('type'=>'string',   'required'=>1, 'error'=>'Helpdesk title required');
         $f['default_dept_id']=array('type'=>'int',   'required'=>1, 'error'=>'Default Dept. required');
-        $f['default_template_id']=array('type'=>'int',   'required'=>1, 'error'=>'You must select template.');
         $f['staff_session_timeout']=array('type'=>'int',   'required'=>1, 'error'=>'Enter idle time in minutes');
         $f['client_session_timeout']=array('type'=>'int',   'required'=>1, 'error'=>'Enter idle time in minutes');
         //Date & Time Options
@@ -789,10 +788,10 @@ class OsticketConfig extends Config {
             'helpdesk_title'=>$vars['helpdesk_title'],
             'helpdesk_url'=>$vars['helpdesk_url'],
             'default_dept_id'=>$vars['default_dept_id'],
-            'default_template_id'=>$vars['default_template_id'],
             'max_page_size'=>$vars['max_page_size'],
             'log_level'=>$vars['log_level'],
             'log_graceperiod'=>$vars['log_graceperiod'],
+            'name_format'=>$vars['name_format'],
             'passwd_reset_period'=>$vars['passwd_reset_period'],
             'staff_max_logins'=>$vars['staff_max_logins'],
             'staff_login_timeout'=>$vars['staff_login_timeout'],
@@ -861,7 +860,6 @@ class OsticketConfig extends Config {
             'default_sla_id'=>$vars['default_sla_id'],
             'max_open_tickets'=>$vars['max_open_tickets'],
             'autolock_minutes'=>$vars['autolock_minutes'],
-            'allow_priority_change'=>isset($vars['allow_priority_change'])?1:0,
             'use_email_priority'=>isset($vars['use_email_priority'])?1:0,
             'enable_captcha'=>isset($vars['enable_captcha'])?1:0,
             'log_ticket_activity'=>isset($vars['log_ticket_activity'])?1:0,
@@ -889,6 +887,7 @@ class OsticketConfig extends Config {
     function updateEmailsSettings($vars, &$errors) {
 
         $f=array();
+        $f['default_template_id']=array('type'=>'int',   'required'=>1, 'error'=>'You must select template.');
         $f['default_email_id']=array('type'=>'int',   'required'=>1, 'error'=>'Default email required');
         $f['alert_email_id']=array('type'=>'int',   'required'=>1, 'error'=>'Selection required');
         $f['admin_email']=array('type'=>'email',   'required'=>1, 'error'=>'System admin email required');
@@ -903,6 +902,7 @@ class OsticketConfig extends Config {
             return false;
 
         return $this->updateAll(array(
+            'default_template_id'=>$vars['default_template_id'],
             'default_email_id'=>$vars['default_email_id'],
             'alert_email_id'=>$vars['alert_email_id'],
             'default_smtp_id'=>$vars['default_smtp_id'],
diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index 7f034a05e..a72a568a2 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -61,8 +61,10 @@ class DynamicForm extends VerySimpleModel {
 
     // Multiple inheritance -- delegate to Form
     function __call($what, $args) {
-        return call_user_func_array(
-            array($this->getForm(), $what), $args);
+        $delegate = array($this->getForm(), $what);
+        if (!is_callable($delegate))
+            throw new Exception($what.': Call to non-existing function');
+        return call_user_func_array($delegate, $args);
     }
 
     function hasField($name) {
@@ -77,11 +79,16 @@ class DynamicForm extends VerySimpleModel {
     function getForm() {
         if (!$this->_form) {
             $fields = $this->getFields();
-            $this->_form = new Form($fields, $this->title, $this->instructions);
+            $this->_form = new Form($fields, false, array(
+                'title'=>$this->title, 'instructions'=>$this->instructions));
         }
         return $this->_form;
     }
 
+    function isDeletable() {
+        return $this->get('deletable');
+    }
+
     function instanciate($sort=1) {
         return DynamicFormEntry::create(array(
             'form_id'=>$this->get('id'), 'sort'=>$sort));
@@ -93,10 +100,17 @@ class DynamicForm extends VerySimpleModel {
         }
     }
 
-    function save() {
+    function save($refetch=false) {
         if (count($this->dirty))
             $this->set('updated', new SqlFunction('NOW'));
-        return parent::save();
+        return parent::save($refetch);
+    }
+
+    function delete() {
+        if (!$this->isDeletable())
+            return false;
+        else
+            return parent::delete();
     }
 
     static function create($ht=false) {
@@ -122,6 +136,22 @@ class UserForm extends DynamicForm {
         return $os->filter(array('type'=>'U'));
     }
 
+    function getFields() {
+        $fields = parent::getFields();
+        foreach ($fields as $f) {
+            if ($f->get('name') == 'email') {
+                $f->getConfiguration();
+                $f->_config['classes'] = 'auto email typeahead';
+                $f->_config['autocomplete'] = false;
+            }
+            elseif ($f->get('name') == 'name') {
+                $f->getConfiguration();
+                $f->_config['classes'] = 'auto name';
+            }
+        }
+        return $fields;
+    }
+
     static function getInstance() {
         if (!isset(static::$instance)) {
             $o = static::objects();
@@ -129,24 +159,6 @@ class UserForm extends DynamicForm {
         }
         return static::$instance;
     }
-
-    function getStaticForm() {
-        static $form = null;
-        if (!$form)
-            $form = new Form(array(
-            'email' => new TextboxField(array(
-                'id'=>'email', 'label'=>'Email Address', 'required'=>true,
-                'validator' => 'email', 'configuration'=>array(
-                    'autocomplete'=>false, 'classes'=>'typeahead',
-                    'size'=>40)
-            )),
-            'name' => new TextboxField(array(
-                'id'=>'name', 'label'=>'Full Name', 'required'=>true,
-                'configuration' => array('size'=>40),
-            )),
-            ), 'User Information');
-        return $form;
-    }
 }
 
 class TicketForm extends DynamicForm {
@@ -166,7 +178,7 @@ class TicketForm extends DynamicForm {
     }
 }
 // Add fields from the standard ticket form to the ticket filterable fields
-Filter::addSupportedMatches('Dynamic Fields', function() {
+Filter::addSupportedMatches('Custom Fields', function() {
     $matches = array();
     foreach (TicketForm::getInstance()->getFields() as $f) {
         if (!$f->hasData())
@@ -240,7 +252,7 @@ class DynamicFormField extends VerySimpleModel {
     }
 
     function isDeletable() {
-        return $this->get('edit_mask') & 1;
+        return ($this->get('edit_mask') & 1) == 0;
     }
     function isNameForced() {
         return $this->get('edit_mask') & 2;
@@ -376,7 +388,8 @@ class DynamicFormEntry extends VerySimpleModel {
         if (!$this->_clean) {
             $this->_clean = array();
             foreach ($this->getFields() as $field)
-                $this->_clean[$field->get('id')] = $field->getClean();
+                $this->_clean[$field->get('id')]
+                    = $this->_clean[$field->get('name')] = $field->getClean();
         }
         return $this->_clean;
     }
@@ -432,10 +445,18 @@ class DynamicFormEntry extends VerySimpleModel {
                 $this->_values[] = $a;
                 $this->_fields[] = $field;
                 // Omit fields without data
-                if ($field->hasData())
+                // For user entries, the name and email fields should not be
+                // saved with the rest of the data
+                if (!($this->object_type == 'U'
+                        && in_array($field->get('name'), array('name','email')))
+                        && $field->hasData())
                     $a->save();
-                unset($this->_form);
+                $this->_form = null;
             }
+            // Sort the form the way it is declared to be sorted
+            usort($this->_fields, function($a, $b) {
+                return $a->get('sort') - $b->get('sort');
+            });
         }
     }
 
@@ -445,6 +466,9 @@ class DynamicFormEntry extends VerySimpleModel {
         parent::save();
         foreach ($this->getAnswers() as $a) {
             $field = $a->getField();
+            if ($this->object_type == 'U'
+                    && in_array($field->get('name'), array('name','email')))
+                continue;
             $val = $field->to_database($field->getClean());
             if (is_array($val)) {
                 $a->set('value', $val[0]);
@@ -460,6 +484,12 @@ class DynamicFormEntry extends VerySimpleModel {
         $this->_values = array();
     }
 
+    function delete() {
+        foreach ($this->getAnswers() as $a)
+            $a->delete();
+        return parent::delete();
+    }
+
     static function create($ht=false) {
         $inst = parent::create($ht);
         $inst->set('created', new SqlFunction('NOW'));
@@ -598,6 +628,16 @@ class DynamicList extends VerySimpleModel {
         return parent::save($refetch);
     }
 
+    function delete() {
+        $fields = DynamicFormField::objects()->filter(array(
+            'type'=>'list-'.$this->id))->count();
+        if ($fields == 0)
+            return parent::delete();
+        else
+            // Refuse to delete lists that are in use by fields
+            return false;
+    }
+
     static function create($ht=false) {
         $inst = parent::create($ht);
         $inst->set('created', new SqlFunction('NOW'));
diff --git a/include/class.filter.php b/include/class.filter.php
index 2ee4fbbf2..81c6a39fe 100644
--- a/include/class.filter.php
+++ b/include/class.filter.php
@@ -176,7 +176,7 @@ class Filter {
 
     function addRule($what, $how, $val,$extra=array()) {
 
-        $rule= array_merge($extra,array('w'=>$what, 'h'=>$how, 'v'=>$val));
+        $rule= array_merge($extra,array('what'=>$what, 'how'=>$how, 'val'=>$val));
         $rule['filter_id']=$this->getId();
 
         return FilterRule::create($rule,$errors);               # nolint
diff --git a/include/class.forms.php b/include/class.forms.php
index da4ed00a9..963abcb64 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -20,18 +20,25 @@
  */
 class Form {
     var $fields = array();
-    var $title = '';
+    var $title = 'Unnamed';
     var $instructions = '';
 
     var $_errors;
+    var $_source = false;
 
     function Form() {
         call_user_func_array(array($this, '__construct'), func_get_args());
     }
-    function __construct($fields=array(), $title=false, $instructions=false) {
+    function __construct($fields=array(), $source=null, $options=array()) {
         $this->fields = $fields;
-        $this->title = $title;
-        $this->instructions = $instructions;
+        foreach ($fields as $f)
+            $f->setForm($this);
+        if (isset($options['title']))
+            $this->title = $options['title'];
+        if (isset($options['instructions']))
+            $this->instructions = $options['instructions'];
+        // Use POST data if source was not specified
+        $this->_source = ($source) ? $source : $_POST;
     }
     function data($source) {
         foreach ($this->fields as $name=>$f)
@@ -44,6 +51,7 @@ class Form {
     }
     function getTitle() { return $this->title; }
     function getInstructions() { return $this->instructions; }
+    function getSource() { return $this->_source; }
 
     function isValid() {
         if (!is_array($this->_errors)) {
@@ -59,8 +67,10 @@ class Form {
     function getClean() {
         if (!$this->_clean) {
             $this->_clean = array();
-            foreach ($this->getFields() as $key=>$field)
-                $this->_clean[$key] = $field->getClean();
+            foreach ($this->getFields() as $key=>$field) {
+                $this->_clean[$key] = $this->_clean[$field->get('name')]
+                    = $field->getClean();
+            }
         }
         return $this->_clean;
     }
@@ -85,6 +95,8 @@ class Form {
 require_once(INCLUDE_DIR . "class.json.php");
 
 class FormField {
+    static $widget = false;
+
     var $ht = array(
         'label' => 'Unlabeled',
         'required' => false,
@@ -92,10 +104,13 @@ class FormField {
         'configuration' => array(),
     );
 
+    var $_form;
     var $_cform;
     var $_clean;
     var $_errors = array();
+    var $_widget;
     var $parent;
+    var $presentation_only = false;
 
     static $types = array(
         'Basic Fields' => array(
@@ -111,9 +126,6 @@ class FormField {
     );
     static $more_types = array();
 
-    function FormField() {
-        call_user_func_array(array($this, '__construct'), func_get_args());
-    }
     function __construct($options=array()) {
         static $uid = 100;
         $this->ht = array_merge($this->ht, $options);
@@ -271,11 +283,14 @@ class FormField {
         $clazz = $type[1];
         $inst = new $clazz($this->ht);
         $inst->parent = $parent;
+        $inst->setForm($this->_form);
         return $inst;
     }
 
     function __call($what, $args) {
         // XXX: Throw exception if $this->parent is not set
+        if (!$this->parent)
+            throw new Exception($what.': Call to undefined function');
         // BEWARE: DynamicFormField has a __call() which will create a new
         //      FormField instance and invoke __call() on it or bounce
         //      immediately back
@@ -292,6 +307,26 @@ class FormField {
             return $this->get('id');
     }
 
+    function setForm($form) {
+        $this->_form = $form;
+    }
+    function getForm() {
+        return $this->_form;
+    }
+    /**
+     * Returns the data source for this field. If created from a form, the
+     * data source from the form is returned. Otherwise, if the request is a
+     * POST, then _POST is returned.
+     */
+    function getSource() {
+        if ($this->_form)
+            return $this->_form->getSource();
+        elseif ($_SERVER['REQUEST_METHOD'] == 'POST')
+            return $_POST;
+        else
+            return array();
+    }
+
     function render($mode=null) {
         $this->getWidget()->render($mode);
     }
@@ -363,7 +398,7 @@ class FormField {
      * some static processing will store the data elsewhere.
      */
     function isPresentationOnly() {
-        return false;
+        return $this->presentation_only;
     }
 
     function getConfigurationForm() {
@@ -376,12 +411,17 @@ class FormField {
         return $this->_cform;
     }
 
+    function getWidget() {
+        if (!static::$widget)
+            throw new Exception('Widget not defined for this field');
+        if (!isset($this->_widget))
+            $this->_widget = new static::$widget($this);
+        return $this->_widget;;
+    }
 }
 
 class TextboxField extends FormField {
-    function getWidget() {
-        return new TextboxWidget($this);
-    }
+    static $widget = 'TextboxWidget';
 
     function getConfigurationOptions() {
         return array(
@@ -430,9 +470,8 @@ class TextboxField extends FormField {
 }
 
 class TextareaField extends FormField {
-    function getWidget() {
-        return new TextareaWidget($this);
-    }
+    static $widget = 'TextareaWidget';
+
     function getConfigurationOptions() {
         return array(
             'cols'  =>  new TextboxField(array(
@@ -446,6 +485,8 @@ class TextareaField extends FormField {
 }
 
 class PhoneField extends FormField {
+    static $widget = 'PhoneNumberWidget';
+
     function validateEntry($value) {
         parent::validateEntry($value);
         # Run validator against $this->value for email type
@@ -459,9 +500,6 @@ class PhoneField extends FormField {
                 $this->_errors[] = "Enter a phone number for the extension";
         }
     }
-    function getWidget() {
-        return new PhoneNumberWidget($this);
-    }
 
     function toString($value) {
         list($phone, $ext) = explode("X", $value, 2);
@@ -473,9 +511,7 @@ class PhoneField extends FormField {
 }
 
 class BooleanField extends FormField {
-    function getWidget() {
-        return new CheckboxWidget($this);
-    }
+    static $widget = 'CheckboxWidget';
 
     function getConfigurationOptions() {
         return array(
@@ -500,9 +536,7 @@ class BooleanField extends FormField {
 }
 
 class ChoiceField extends FormField {
-    function getWidget() {
-        return new ChoicesWidget($this);
-    }
+    static $widget = 'ChoicesWidget';
 
     function getConfigurationOptions() {
         return array(
@@ -541,9 +575,7 @@ class ChoiceField extends FormField {
 }
 
 class DatetimeField extends FormField {
-    function getWidget() {
-        return new DatetimePickerWidget($this);
-    }
+    static $widget = 'DatetimePickerWidget';
 
     function to_database($value) {
         // Store time in gmt time, unix epoch format
@@ -617,9 +649,7 @@ class DatetimeField extends FormField {
  * a field to provide a horizontal section break in the display of a form
  */
 class SectionBreakField extends FormField {
-    function getWidget() {
-        return new SectionBreakWidget($this);
-    }
+    static $widget = 'SectionBreakWidget';
 
     function hasData() {
         return false;
@@ -631,9 +661,8 @@ class SectionBreakField extends FormField {
 }
 
 class ThreadEntryField extends FormField {
-    function getWidget() {
-        return new ThreadEntryWidget($this);
-    }
+    static $widget = 'ThreadEntryWidget';
+
     function isChangeable() {
         return false;
     }
@@ -700,24 +729,22 @@ FormField::addFieldTypes('Built-in Lists', function() {
 });
 
 class Widget {
-    function Widget() {
-        # Not called in PHP5
-        call_user_func_array(array(&$this, '__construct'), func_get_args());
-    }
 
     function __construct($field) {
         $this->field = $field;
         $this->name = $field->getFormName();
-        if ($_SERVER['REQUEST_METHOD'] == 'POST')
-            $this->value = $this->getValue();
-        elseif (is_object($field->getAnswer()))
+        $this->value = $this->getValue();
+        if (!isset($this->value) && is_object($field->getAnswer()))
             $this->value = $field->getAnswer()->getValue();
-        if (!$this->value && $field->value)
+        if (!isset($this->value) && $field->value)
             $this->value = $field->value;
     }
 
     function getValue() {
-        return $_POST[$this->name];
+        $data = $this->field->getSource();
+        if (!isset($data[$this->name]))
+            return null;
+        return $data[$this->name];
     }
 }
 
@@ -775,9 +802,13 @@ class PhoneNumberWidget extends Widget {
     }
 
     function getValue() {
-        $ext = $_POST["{$this->name}-ext"];
+        $data = $this->field->getSource();
+        $base = parent::getValue();
+        if ($base === null)
+            return $base;
+        $ext = $data["{$this->name}-ext"];
         if ($ext) $ext = 'X'.$ext;
-        return parent::getValue() . $ext;
+        return $base . $ext;
     }
 }
 
@@ -832,8 +863,9 @@ class CheckboxWidget extends Widget {
     }
 
     function getValue() {
-        if (count($_POST))
-            return @in_array($this->field->get('id'), $_POST[$this->name]);
+        $data = $this->field->getSource();
+        if (count($data))
+            return @in_array($this->field->get('id'), $data[$this->name]);
         return parent::getValue();
     }
 }
@@ -886,9 +918,10 @@ class DatetimePickerWidget extends Widget {
      * time value into a single date and time string value.
      */
     function getValue() {
+        $data = $this->field->getSource();
         $datetime = parent::getValue();
-        if ($datetime && isset($_POST[$this->name . ':time']))
-            $datetime .= ' ' . $_POST[$this->name . ':time'];
+        if ($datetime && isset($data[$this->name . ':time']))
+            $datetime .= ' ' . $data[$this->name . ':time'];
         return $datetime;
     }
 }
diff --git a/include/class.staff.php b/include/class.staff.php
index a1fe8e7c8..93708bc0c 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -19,6 +19,7 @@ include_once(INCLUDE_DIR.'class.error.php');
 include_once(INCLUDE_DIR.'class.team.php');
 include_once(INCLUDE_DIR.'class.group.php');
 include_once(INCLUDE_DIR.'class.passwd.php');
+include_once(INCLUDE_DIR.'class.user.php');
 
 class Staff {
 
@@ -165,7 +166,7 @@ class Staff {
     }
 
     function getName() {
-        return ucfirst($this->ht['firstname'].' '.$this->ht['lastname']);
+        return new PersonsName($this->ht['firstname'].' '.$this->ht['lastname']);
     }
 
     function getFirstName() {
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 9bf716e39..6edb55e8b 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -84,6 +84,7 @@ class Ticket {
 
         $this->id       = $this->ht['ticket_id'];
         $this->number   = $this->ht['ticketID'];
+        $this->_answers = array();
 
         $this->loadDynamicData();
 
@@ -105,11 +106,12 @@ class Ticket {
     }
 
     function loadDynamicData() {
-        $this->_answers = array();
-        foreach (DynamicFormEntry::forTicket($this->getId()) as $form)
-            foreach ($form->getAnswers() as $answer)
-                $this->_answers[$answer->getField()->get('name')] =
-                    $answer->getValue();
+        if (!$this->_answers) {
+            foreach (DynamicFormEntry::forTicket($this->getId()) as $form)
+                foreach ($form->getAnswers() as $answer)
+                    $this->_answers[$answer->getField()->get('name')] =
+                        $answer->getValue();
+        }
     }
 
     function reload() {
@@ -1642,6 +1644,9 @@ class Ticket {
         //delete just orphaned ticket thread & associated attachments.
         $this->getThread()->delete();
 
+        foreach (DynamicFormEntry::forTicket($this->getId()) as $form)
+            $form->delete();
+
         return true;
     }
 
@@ -1852,10 +1857,12 @@ class Ticket {
     function create($vars, &$errors, $origin, $autorespond=true, $alertstaff=true) {
         global $ost, $cfg, $thisclient, $_FILES;
 
-        // Drop extra whitespace
-        foreach (array('phone', 'subject') as $f)
-            if (isset($vars[$f]))
-                $vars[$f] = trim($vars[$f]);
+        // Identify the user creating the ticket and unpack user information
+        // fields into local scope for filtering and banning purposes
+        $user_form = UserForm::getInstance();
+        $user_info = $user_form->getClean();
+        if ($user_form->isValid())
+            $vars += $user_info;
 
         //Check for 403
         if ($vars['email']  && Validator::is_email($vars['email'])) {
@@ -1949,19 +1956,21 @@ class Ticket {
                 $errors['duedate']='Due date must be in the future';
         }
 
-        # Identify the user creating the ticket
-        $user_info = UserForm::getStaticForm()->getClean();
         // Data is slightly different between HTTP posts and emails
-        if (isset($vars['emailId']) || !isset($user_info['email']))
+        if ((isset($vars['emailId']) && $vars['emailId'])
+                || !isset($user_info['email']) || !$user_info['email']) {
             $user_info = $vars;
-        elseif (!UserForm::getStaticForm()->isValid())
+        }
+        elseif (!$user_form->isValid()) {
             $errors['user'] = 'Incomplete client information';
-        $user = User::fromForm($user_info);
-        $user_email = UserEmail::ensure($user_info['email']);
+        }
 
         //Any error above is fatal.
         if($errors)  return 0;
 
+        $user = User::fromForm($user_info);
+        $user_email = UserEmail::ensure($user_info['email']);
+
         # Perform ticket filter actions on the new ticket arguments
         if ($ticket_filter) $ticket_filter->apply($vars);
 
diff --git a/include/class.topic.php b/include/class.topic.php
index 8f1eb7365..688cb7aaa 100644
--- a/include/class.topic.php
+++ b/include/class.topic.php
@@ -227,7 +227,7 @@ class Topic {
             .',page_id='.db_input($vars['page_id'])
             .',isactive='.db_input($vars['isactive'])
             .',ispublic='.db_input($vars['ispublic'])
-            .',noautoresp='.db_input(isset($vars['noautoresp'])?1:0)
+            .',noautoresp='.db_input(isset($vars['noautoresp']) && $vars['noautoresp']?1:0)
             .',notes='.db_input(Format::sanitize($vars['notes']));
 
         //Auto assign ID is overloaded...
diff --git a/include/class.user.php b/include/class.user.php
index 8a01c159c..b38ec2528 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -79,10 +79,17 @@ class User extends UserModel {
         if (!$user) {
             $user = User::create(array('name'=>$data['name'],
                 'default_email'=>
-                UserEmail::create(array('address'=>$data['email']))
+                    UserEmail::create(array('address'=>$data['email']))
             ));
             $user->save();
             $user->emails->add($user->default_email);
+
+            // Attach initial custom fields
+            $uf = UserForm::getInstance();
+            foreach ($uf->getFields() as $f)
+                if (isset($data[$f->get('name')]))
+                    $uf->setAnswer($f->get('name'), $data[$f->get('name')]);
+            $uf->save();
         }
 
         return $user;
@@ -104,11 +111,9 @@ class User extends UserModel {
         $data = DynamicFormEntry::forClient($this->id);
         if (!$data[0]) {
             $data = array();
-            foreach (UserForm::objects() as $f) {
-                $g = $f->instanciate();
-                $g->setClientId($this->id);
-                $data[] = $g;
-            }
+            $g = UserForm::getInstance();
+            $g->setClientId($this->id);
+            $data[] = $g;
         }
         return $data;
     }
@@ -136,6 +141,18 @@ class PersonsName {
     var $parts;
     var $name;
 
+    static $formats = array(
+        'first' => array("First", 'getFirst'),
+        'last' => array("Last", 'getLast'),
+        'full' => array("First Last", 'getFull'),
+        'legal' => array("First M. Last", 'getLegal'),
+        'lastfirst' => array("Last, First", 'getLastFirst'),
+        'formal' => array("Mr. Last", 'getFormal'),
+        'short' => array("First L.", 'getShort'),
+        'shortformal' => array("F. Last", 'getShortFormal'),
+        'complete' => array("Mr. First M. Last Sr.", 'getComplete'),
+    );
+
     function __construct($name) {
         $this->parts = static::splitName($name);
         $this->name = $name;
@@ -149,6 +166,14 @@ class PersonsName {
         return $this->parts['last'];
     }
 
+    function getMiddle() {
+        return $this->parts['middle'];
+    }
+
+    function getMiddleInitial() {
+        return mb_substr($this->parts['middle'],0,1).'.';
+    }
+
     function getFormal() {
         return trim($this->parts['salutation'].' '.$this->parts['last']);
     }
@@ -157,9 +182,26 @@ class PersonsName {
         return trim($this->parts['first'].' '.$this->parts['last']);
     }
 
+    function getLegal() {
+        $parts = array(
+            $this->parts['first'],
+            mb_substr($this->parts['middle'],0,1),
+            $this->parts['last'],
+        );
+        if ($parts[1]) $parts[1] .= '.';
+        return implode(' ', array_filter($parts));
+    }
+
     function getComplete() {
-        return trim($this->parts['salutation'].' '.$this->parts['first']
-            .' '.$this->parts['last'].' '.$this->parts['suffix']);
+        $parts = array(
+            $this->parts['salutation'],
+            $this->parts['first'],
+            mb_substr($this->parts['middle'],0,1),
+            $this->parts['last'],
+            $this->parts['suffix']
+        );
+        if ($parts[2]) $parts[2] .= '.';
+        return implode(' ', array_filter($parts));
     }
 
     function getLastFirst() {
@@ -169,8 +211,32 @@ class PersonsName {
         return $name;
     }
 
+    function getShort() {
+        return $this->parts['first'].' '.mb_substr($this->parts['last'],0,1).'.';
+    }
+
+    function getShortFormal() {
+        return mb_substr($this->parts['first'],0,1).'. '.$this->parts['last'];
+    }
+
+    function getOriginal() {
+        return $this->name;
+    }
+
+    function asVar() {
+        return $this->__toString();
+    }
+
     function __toString() {
-        return $this->getLastFirst();
+        global $cfg;
+        $format = $cfg->getDefaultNameFormat();
+        list(,$func) = static::$formats[$format];
+        if (!$func) $func = 'getFull';
+        return call_user_func(array($this, $func));
+    }
+
+    static function allFormats() {
+        return static::$formats;
     }
 
     /**
@@ -208,12 +274,19 @@ class PersonsName {
         $start = ($results['salutation']) ? 2 : 1;
         $end = ($results['suffix']) ? $size - 2 : $size - 1;
 
-        $last = '';
+        $middle = array();
         for ($i = $start; $i <= $end; $i++)
         {
-            $last .= ' '.$r[$i];
+            $middle[] = $r[$i];
+        }
+        if (count($middle) > 1) {
+            $results['last'] = array_pop($middle);
+            $results['middle'] = implode(' ', $middle);
+        }
+        else {
+            $results['last'] = $middle[0];
+            $results['middle'] = '';
         }
-        $results['last'] = trim($last);
 
         return $results;
     }
diff --git a/include/client/open.inc.php b/include/client/open.inc.php
index 7bb1d9599..b9ba42e24 100644
--- a/include/client/open.inc.php
+++ b/include/client/open.inc.php
@@ -4,8 +4,7 @@ $info=array();
 if($thisclient && $thisclient->isValid()) {
     $info=array('name'=>$thisclient->getName(),
                 'email'=>$thisclient->getEmail(),
-                'phone'=>$thisclient->getPhone(),
-                'phone_ext'=>$thisclient->getPhoneExt());
+                'phone'=>$thisclient->getPhone());
 }
 
 $info=($_POST && $errors)?Format::htmlchars($_POST):$info;
@@ -40,40 +39,23 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):$info;
         </td>
     </tr>
 <?php
-        UserForm::getStaticForm()->render(false, 'Your Information');
+        if (!$thisclient) {
+            UserForm::getInstance()->render(false, 'Your Information');
+        }
+        else { ?>
+            <tr><td colspan="2"><hr /></td></tr>
+        <tr><td>Email:</td><td><?php echo $thisclient->getEmail(); ?></td></tr>
+        <tr><td>Client:</td><td><?php echo $thisclient->getName(); ?></td></tr>
+        <?php }
         TicketForm::getInstance()->render(false); ?>
     </tbody>
     <tbody id="dynamic-form">
-        <?php if ($forms) {
-            foreach ($forms as $form) {
-                include(CLIENTINC_DIR . 'templates/dynamic-form.tmpl.php');
-            }
+        <?php if ($form) {
+            include(CLIENTINC_DIR . 'templates/dynamic-form.tmpl.php');
         } ?>
     </tbody>
     <tbody>
     <?php
-    if($cfg->allowPriorityChange() && ($priorities=Priority::getPriorities())) { ?>
-    <tr>
-        <td>Ticket Priority:</td>
-        <td>
-            <select id="priority" name="priorityId">
-                <?php
-                    if(!$info['priorityId'])
-                        $info['priorityId'] = $cfg->getDefaultPriorityId(); //System default.
-                    foreach($priorities as $id =>$name) {
-                        echo sprintf('<option value="%d" %s>%s</option>',
-                                        $id, ($info['priorityId']==$id)?'selected="selected"':'', $name);
-
-                    }
-                ?>
-            </select>
-            <font class="error">&nbsp;<?php echo $errors['priorityId']; ?></font>
-        </td>
-    </tr>
-    <?php
-    }
-    ?>
-    <?php
     if($cfg && $cfg->isCaptchaEnabled() && (!$thisclient || !$thisclient->isValid())) {
         if($_POST && $errors && !$errors['captcha'])
             $errors['captcha']='Please re-enter the text again';
diff --git a/include/client/templates/dynamic-form.tmpl.php b/include/client/templates/dynamic-form.tmpl.php
index 1e4e79e8c..5eac1d38a 100644
--- a/include/client/templates/dynamic-form.tmpl.php
+++ b/include/client/templates/dynamic-form.tmpl.php
@@ -14,19 +14,6 @@
     // 'private' are not included in the output for clients
     global $thisclient;
     foreach ($form->getFields() as $field) {
-        if ($thisclient) {
-            switch ($field->get('name')) {
-                case 'name':
-                    $field->value = $thisclient->getName();
-                    break;
-                case 'email':
-                    $field->value = $thisclient->getEmail();
-                    break;
-                case 'phone':
-                    $field->value = $thisclient->getPhone();
-                    break;
-            }
-        }
         if ($field->get('private'))
             continue;
         ?>
@@ -36,8 +23,9 @@
             <?php
             }
             else { ?>
-                <td class="<?php if ($field->get('required')) echo 'required'; ?>">
-                <?php echo Format::htmlchars($field->get('label')); ?>:</td><td>
+                <td><label for="<?php echo $field->getFormname(); ?>" class="<?php
+                    if ($field->get('required')) echo 'required'; ?>">
+                <?php echo Format::htmlchars($field->get('label')); ?>:</label></td><td>
             <?php
             }
             $field->render('client'); ?>
diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php
index d38d113ce..7c2c6e564 100644
--- a/include/client/tickets.inc.php
+++ b/include/client/tickets.inc.php
@@ -69,7 +69,7 @@ if($search) {
     } else {//Deep search!
         $queryterm=db_real_escape($_REQUEST['q'],false); //escape the term ONLY...no quotes.
         $qwhere.=' AND ( '
-                ." ticket.subject LIKE '%$queryterm%'"
+                ." subject.value LIKE '%$queryterm%'"
                 ." OR thread.body LIKE '%$queryterm%'"
                 .' ) ';
         $deep_search=true;
diff --git a/include/i18n/en_US/form.yaml b/include/i18n/en_US/form.yaml
index 745948812..69b3ae30b 100644
--- a/include/i18n/en_US/form.yaml
+++ b/include/i18n/en_US/form.yaml
@@ -28,28 +28,50 @@
 ---
 - id: 1
   type: U # notrans
-  title: Client Details
+  title: Contact Information
   deletable: false
   fields:
+    - type: text # notrans
+      name: email # notrans
+      label: Email Address
+      required: true
+      sort: 1
+      edit_mask: 15
+      configuration:
+        size: 40
+        length: 64
+        validator: email # notrans
+
+    - type: text # notrans
+      name: name # notrans
+      label: Full Name
+      required: true
+      sort: 2
+      edit_mask: 15
+      configuration:
+        size: 40
+        length: 64
+
     - type: phone # notrans
       name: phone # notrans
       label: Phone Number
       required: false
-      sort: 1
+      sort: 3
 
     - type: memo # notrans
       name: notes
       label: Internal Notes
       required: false
       private: true
-      sort: 2
+      sort: 4
       configuration:
         rows: 4
         cols: 40
 
 - id: 2
   type: T # notrans
-  title: Standard Ticket Form
+  title: Ticket Details
+  instructions: Please Describe Your Issue
   notes: |
       This form will be attached to every ticket, regardless of its source.
       You can add any fields to this form and they will be available to all
@@ -58,8 +80,7 @@
   fields:
     - type: text # notrans
       name: subject # notrans
-      label: Subject
-      hint: Issue Summary
+      label: Issue Summary
       required: true
       edit_mask: 15
       sort: 1
@@ -69,7 +90,7 @@
 
     - type: thread # notrans
       name: message # notrans
-      label: Issue
+      label: Issue Details
       hint: Details on the reason(s) for opening the ticket.
       required: true
       edit_mask: 15
diff --git a/include/i18n/en_US/help_topic.yaml b/include/i18n/en_US/help_topic.yaml
index 8ee30e318..fe5294319 100644
--- a/include/i18n/en_US/help_topic.yaml
+++ b/include/i18n/en_US/help_topic.yaml
@@ -23,7 +23,7 @@
   dept_id: 1
   sla_id: 1
   priority_id: 2
-  form_id: 1
+  form_id: 0
   topic: Support
   notes: |
     Tickets that primarily concern the support deparment
@@ -33,7 +33,7 @@
   dept_id: 1
   sla_id: 0
   priority_id: 2
-  form_id: 1
+  form_id: 0
   topic: Billing
   notes: |
     Tickets that primarily concern the sales and billing deparments
diff --git a/include/staff/dynamic-form.inc.php b/include/staff/dynamic-form.inc.php
index bdb38041c..4e48664ab 100644
--- a/include/staff/dynamic-form.inc.php
+++ b/include/staff/dynamic-form.inc.php
@@ -20,13 +20,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
     <?php csrf_token(); ?>
     <input type="hidden" name="do" value="<?php echo $action; ?>">
     <input type="hidden" name="id" value="<?php echo $info['id']; ?>">
-    <h2>Dynamic Form</h2>
+    <h2>Custom Form</h2>
     <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
     <thead>
         <tr>
             <th colspan="2">
                 <h4><?php echo $title; ?></h4>
-                <em>Dynamic forms are used to allow custom data to be
+                <em>Custom forms are used to allow custom data to be
                 associated with tickets</em>
             </th>
         </tr>
@@ -57,22 +57,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
             <th></th>
             <th>Label</th>
             <th>Type</th>
-            <th>Name</th>
-            <th>Private</th>
+            <th>Internal</th>
             <th>Required</th>
+            <th>Name</th>
+            <th>Delete</th>
         </tr>
     </thead>
     <tbody>
-        <tr>
-            <td><input type="checkbox" disabled="disabled"/></td>
-            <td>Full Name</td><td>Short Answer</td><td>name</td>
-            <td><input type="checkbox" disabled="disabled"/></td>
-            <td><input type="checkbox" disabled="disabled" checked="checked"/></td></tr>
-        <tr>
-            <td><input type="checkbox" disabled="disabled"/></td>
-            <td>Email Address</td><td>Short Answer</td><td>email</td>
-            <td><input type="checkbox" disabled="disabled"/></td>
-            <td><input type="checkbox" disabled="disabled" checked="checked"/></td></tr>
     <?php
         $uform = UserForm::objects()->all();
         $ftypes = FormField::allTypes();
@@ -80,13 +71,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
             if ($f->get('private')) continue;
         ?>
         <tr>
-            <td><input type="checkbox" disabled="disabled"/></td>
+            <td></td>
             <td><?php echo $f->get('label'); ?></td>
             <td><?php $t=FormField::getFieldType($f->get('type')); echo $t[0]; ?></td>
-            <td><?php echo $f->get('name'); ?></td>
             <td><input type="checkbox" disabled="disabled"/></td>
             <td><input type="checkbox" disabled="disabled"
-                <?php echo $f->get('required') ? 'checked="checked"' : ''; ?>/></td></tr>
+                <?php echo $f->get('required') ? 'checked="checked"' : ''; ?>/></td>
+            <td><?php echo $f->get('name'); ?></td>
+            <td><input type="checkbox" disabled="disabled"/></td></tr>
 
         <?php } ?>
     </tbody>
@@ -98,31 +90,25 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
             </th>
         </tr>
         <tr>
-            <th>Delete</th>
+            <th>Sort</th>
             <th>Label</th>
             <th>Type</th>
-            <th>Name</th>
-            <th>Private</th>
+            <th>Internal</th>
             <th>Required</th>
+            <th>Name</th>
+            <th>Delete</th>
         </tr>
     </thead>
     <tbody class="sortable-rows" data-sort="sort-">
     <?php if ($form) foreach ($form->getFields() as $f) {
         $id = $f->get('id');
-        $deletable = $f->isDeletable() ? 'disabled="disabled"' : '';
+        $deletable = !$f->isDeletable() ? 'disabled="disabled"' : '';
         $force_name = $f->isNameForced() ? 'disabled="disabled"' : '';
         $force_privacy = $f->isPrivacyForced() ? 'disabled="disabled"' : '';
         $force_required = $f->isRequirementForced() ? 'disabled="disabled"' : '';
         $errors = $f->errors(); ?>
         <tr>
-            <td><input type="checkbox" name="delete-<?php echo $id; ?>"
-                    <?php echo $deletable; ?>/>
-                <input type="hidden" name="sort-<?php echo $id; ?>"
-                    value="<?php echo $f->get('sort'); ?>"/>
-                <font class="error"><?php
-                    if ($errors['sort']) echo '<br/>'; echo $errors['sort'];
-                ?></font>
-                </td>
+            <td><i class="icon-sort"></i></td>
             <td><input type="text" size="32" name="label-<?php echo $id; ?>"
                 value="<?php echo $f->get('label'); ?>"/></td>
             <td><select name="type-<?php echo $id; ?>" <?php
@@ -140,7 +126,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 <?php } ?>
             </select>
             <?php if ($f->isConfigurable()) { ?>
-                <a class="action-button" style="float:none"
+                <a class="action-button" style="float:none;overflow:inherit"
                     href="ajax.php/form/field-config/<?php
                         echo $f->get('id'); ?>"
                     onclick="javascript:
@@ -150,6 +136,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                         return false;
                     "><i class="icon-edit"></i> Config</a>
             <?php } ?></td>
+            <td><input type="checkbox" name="private-<?php echo $id; ?>"
+                <?php if ($f->get('private')) echo 'checked="checked"'; ?>
+                <?php echo $force_privacy ?>/></td>
+            <td><input type="checkbox" name="required-<?php echo $id; ?>"
+                <?php if ($f->get('required')) echo 'checked="checked"'; ?>
+                <?php echo $force_required ?>/>
+            </td>
             <td>
                 <input type="text" size="20" name="name-<?php echo $id; ?>"
                     value="<?php echo $f->get('name'); ?>" <?php echo $force_name ?>/>
@@ -157,41 +150,35 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                     if ($errors['name']) echo '<br/>'; echo $errors['name'];
                 ?></font>
                 </td>
-            <td><input type="checkbox" name="private-<?php echo $id; ?>"
-                <?php if ($f->get('private')) echo 'checked="checked"'; ?>
-                <?php echo $force_privacy ?>/></td>
-            <td><input type="checkbox" name="required-<?php echo $id; ?>"
-                <?php if ($f->get('required')) echo 'checked="checked"'; ?>
-                <?php echo $force_required ?>/>
-            <?php if (!$f->get('editable')) { ?>
-                <input type="hidden" name="private-<?php echo $id; ?>" value="<?php
-                    echo ($f->get('private')) ? 'on' : ''; ?>" />
-                <input type="hidden" name="required-<?php echo $id; ?>" value="<?php
-                    echo ($f->get('required')) ? 'on' : ''; ?>" />
-            <?php
-            } ?></td>
+            <td><input type="checkbox" name="delete-<?php echo $id; ?>"
+                    <?php echo $deletable; ?>/>
+                <input type="hidden" name="sort-<?php echo $id; ?>"
+                    value="<?php echo $f->get('sort'); ?>"/>
+                </td>
         </tr>
     <?php
     }
     for ($i=0; $i<$newcount; $i++) { ?>
-            <td><em>add</em>
+            <td><em>+</em>
                 <input type="hidden" name="sort-new-<?php echo $i; ?>"/></td>
             <td><input type="text" size="32" name="label-new-<?php echo $i; ?>"/></td>
             <td><select name="type-new-<?php echo $i; ?>">
                 <?php foreach (FormField::allTypes() as $group=>$types) {
                     ?><optgroup label="<?php echo Format::htmlchars($group); ?>"><?php
-                    foreach ($types as $type=>$nfo) { ?>
+                    foreach ($types as $type=>$nfo) {
+                        if (isset($nfo[2]) && !$nfo[2]) continue; ?>
                 <option value="<?php echo $type; ?>">
                     <?php echo $nfo[0]; ?></option>
                     <?php } ?>
                 </optgroup>
                 <?php } ?>
             </select></td>
-            <td><input type="text" size="20" name="name-new-<?php echo $i; ?>"/></td>
             <td><input type="checkbox" name="private-new-<?php echo $i; ?>"
                 <?php if ($form && $form->get('type') == 'U')
                     echo 'checked="checked"'; ?>/></td>
             <td><input type="checkbox" name="required-new-<?php echo $i; ?>"/></td>
+            <td><input type="text" size="20" name="name-new-<?php echo $i; ?>"/></td>
+            <td></td>
         </tr>
     <?php } ?>
     </tbody>
@@ -208,7 +195,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
         </tr>
     </tbody>
     </table>
-<p style="padding-left:225px;">
+<p class="centered">
     <input type="submit" name="submit" value="<?php echo $submit_text; ?>">
     <input type="reset"  name="reset"  value="Reset">
     <input type="button" name="cancel" value="Cancel" onclick='window.location.href="?"'>
diff --git a/include/staff/dynamic-forms.inc.php b/include/staff/dynamic-forms.inc.php
index 7c590d435..0b4a8e154 100644
--- a/include/staff/dynamic-forms.inc.php
+++ b/include/staff/dynamic-forms.inc.php
@@ -1,8 +1,8 @@
 <div style="width:700;padding-top:5px; float:left;">
- <h2>Dynamic Forms</h2>
+ <h2>Custom Forms</h2>
 </div>
 <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;">
- <b><a href="forms.php?a=add" class="Icon">Add New Dynamic Form</a></b></div>
+ <b><a href="forms.php?a=add" class="Icon">Add New Custom Form</a></b></div>
 <div class="clear"></div>
 
 <?php
@@ -13,6 +13,10 @@ $pageNav->setURL('forms.php');
 $showing=$pageNav->showing().' forms';
 ?>
 
+<form action="forms.php" method="POST" name="forms">
+<?php csrf_token(); ?>
+<input type="hidden" name="do" value="mass_process" >
+<input type="hidden" id="action" name="a" value="" >
 <table class="list" border="0" cellspacing="1" cellpadding="0" width="940">
     <thead>
         <tr>
@@ -63,17 +67,62 @@ $showing=$pageNav->showing().' forms';
     <?php foreach (DynamicForm::objects()->filter(array('type'=>'G'))
                 ->order_by('title')
                 ->limit($pageNav->getLimit())
-                ->offset($pageNav->getStart()) as $form) { ?>
+                ->offset($pageNav->getStart()) as $form) {
+            $sel=false;
+            if($ids && in_array($form->get('id'),$ids))
+                $sel=true; ?>
         <tr>
-            <td/>
+            <td><?php if ($form->isDeletable()) { ?>
+                <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $form->get('id'); ?>"
+                    <?php echo $sel?'checked="checked"':''; ?>>
+            <?php } ?></td>
             <td><a href="?id=<?php echo $form->get('id'); ?>"><?php echo $form->get('title'); ?></a></td>
             <td><?php echo $form->get('updated'); ?></td>
         </tr>
     <?php }
     ?>
     </tbody>
+    <tfoot>
+     <tr>
+        <td colspan="3">
+            <?php if($count){ ?>
+            Select:&nbsp;
+            <a id="selectAll" href="#ckb">All</a>&nbsp;&nbsp;
+            <a id="selectNone" href="#ckb">None</a>&nbsp;&nbsp;
+            <a id="selectToggle" href="#ckb">Toggle</a>&nbsp;&nbsp;
+            <?php }else{
+                echo 'No extra forms defined yet &mdash; add one!';
+            } ?>
+        </td>
+     </tr>
+    </tfoot>
 </table>
 <?php
 if ($count) //Show options..
     echo '<div>&nbsp;Page:'.$pageNav->getPageLinks().'&nbsp;</div>';
 ?>
+<p class="centered" id="actions">
+    <input class="button" type="submit" name="delete" value="Delete">
+</p>
+</form>
+
+<div style="display:none;" class="dialog" id="confirm-action">
+    <h3>Please Confirm</h3>
+    <a class="close" href="">&times;</a>
+    <hr/>
+    <p class="confirm-action" style="display:none;" id="delete-confirm">
+        <font color="red"><strong>Are you sure you want to DELETE selected forms?</strong></font>
+        <br><br>Deleted forms CANNOT be recovered.
+    </p>
+    <div>Please confirm to continue.</div>
+    <hr style="margin-top:1em"/>
+    <p class="full-width">
+        <span class="buttons" style="float:left">
+            <input type="button" value="No, Cancel" class="close">
+        </span>
+        <span class="buttons" style="float:right">
+            <input type="button" value="Yes, Do it!" class="confirm">
+        </span>
+     </p>
+    <div class="clear"></div>
+</div>
diff --git a/include/staff/dynamic-list.inc.php b/include/staff/dynamic-list.inc.php
index 3496251ff..b137edb7a 100644
--- a/include/staff/dynamic-list.inc.php
+++ b/include/staff/dynamic-list.inc.php
@@ -2,13 +2,13 @@
 
 $info=array();
 if($list && $_REQUEST['a']!='add') {
-    $title = 'Update dynamic list';
+    $title = 'Update custom list';
     $action = 'update';
     $submit_text='Save Changes';
     $info = $list->ht;
     $newcount=2;
 } else {
-    $title = 'Add new dynamic list';
+    $title = 'Add new custom list';
     $action = 'add';
     $submit_text='Add List';
     $newcount=4;
@@ -20,13 +20,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
     <?php csrf_token(); ?>
     <input type="hidden" name="do" value="<?php echo $action; ?>">
     <input type="hidden" name="id" value="<?php echo $info['id']; ?>">
-    <h2>Dynamic List</h2>
+    <h2>Custom List</h2>
     <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
     <thead>
         <tr>
             <th colspan="2">
                 <h4><?php echo $title; ?></h4>
-                <em>Dynamic lists are used to provide selection boxes for dynamic forms</em>
+                <em>Custom lists are used to provide drop-down lists for custom forms</em>
             </th>
         </tr>
     </thead>
@@ -69,9 +69,10 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
             </th>
         </tr>
         <tr>
-            <th>Delete</th>
+            <th></th>
             <th>Value</th>
             <th>Extra <em style="display:inline">&mdash; abbreviations and such</em></th>
+            <th>Delete</th>
         </tr>
     </thead>
     <tbody <?php if ($info['sort_mode'] == 'SortCol') { ?>
@@ -84,22 +85,24 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
             $id = $i->get('id'); ?>
         <tr>
             <td><?php echo $icon; ?>
-                <input type="checkbox" name="delete-<?php echo $id; ?>"/>
                 <input type="hidden" name="sort-<?php echo $id; ?>"
                 value="<?php echo $i->get('sort'); ?>"/></td>
             <td><input type="text" size="40" name="value-<?php echo $id; ?>"
                 value="<?php echo $i->get('value'); ?>"/></td>
             <td><input type="text" size="30" name="extra-<?php echo $id; ?>"
                 value="<?php echo $i->get('extra'); ?>"/></td>
+            <td>
+                <input type="checkbox" name="delete-<?php echo $id; ?>"/></td>
         </tr>
     <?php }
     }
     for ($i=0; $i<$newcount; $i++) { ?>
         <tr>
-            <td><?php echo $icon; ?> <em>add</em>
+            <td><?php echo $icon; ?> <em>+</em>
                 <input type="hidden" name="sort-new-<?php echo $i; ?>"/></td>
             <td><input type="text" size="40" name="value-new-<?php echo $i; ?>"/></td>
             <td><input type="text" size="30" name="extra-new-<?php echo $i; ?>"/></td>
+            <td></td>
         </tr>
     <?php } ?>
     </tbody>
@@ -117,7 +120,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
     </tbody>
     </table>
     </table>
-<p style="padding-left:225px;">
+<p class="centered">
     <input type="submit" name="submit" value="<?php echo $submit_text; ?>">
     <input type="reset"  name="reset"  value="Reset">
     <input type="button" name="cancel" value="Cancel" onclick='window.location.href="?"'>
diff --git a/include/staff/dynamic-lists.inc.php b/include/staff/dynamic-lists.inc.php
index a5742f3fd..67510792d 100644
--- a/include/staff/dynamic-lists.inc.php
+++ b/include/staff/dynamic-lists.inc.php
@@ -1,8 +1,8 @@
 <div style="width:700;padding-top:5px; float:left;">
- <h2>Dynamic Lists</h2>
+ <h2>Custom Lists</h2>
 </div>
 <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;">
- <b><a href="lists.php?a=add" class="Icon">Add New Dynamic List</a></b></div>
+ <b><a href="lists.php?a=add" class="Icon">Add New Custom List</a></b></div>
 <div class="clear"></div>
 
 <?php
@@ -13,6 +13,10 @@ $pageNav->setURL('lists.php');
 $showing=$pageNav->showing().' dynamic lists';
 ?>
 
+<form action="lists.php" method="POST" name="lists">
+<?php csrf_token(); ?>
+<input type="hidden" name="do" value="mass_process" >
+<input type="hidden" id="action" name="a" value="" >
 <table class="list" border="0" cellspacing="1" cellpadding="0" width="940">
     <caption><?php echo $showing; ?></caption>
     <thead>
@@ -26,9 +30,14 @@ $showing=$pageNav->showing().' dynamic lists';
     <tbody>
     <?php foreach (DynamicList::objects()->order_by('name')
                 ->limit($pageNav->getLimit())
-                ->offset($pageNav->getStart()) as $list) { ?>
+                ->offset($pageNav->getStart()) as $list) {
+            $sel = false;
+            if ($ids && in_array($form->get('id'),$ids))
+                $sel = true; ?>
         <tr>
-            <td/>
+            <td>
+                <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $list->get('id'); ?>"
+                    <?php echo $sel?'checked="checked"':''; ?>></td>
             <td><a href="?id=<?php echo $list->get('id'); ?>"><?php echo $list->get('name'); ?></a></td>
             <td><?php echo $list->get('created'); ?></td>
             <td><?php echo $list->get('updated'); ?></td>
@@ -36,8 +45,48 @@ $showing=$pageNav->showing().' dynamic lists';
     <?php }
     ?>
     </tbody>
+    <tfoot>
+     <tr>
+        <td colspan="4">
+            <?php if($count){ ?>
+            Select:&nbsp;
+            <a id="selectAll" href="#ckb">All</a>&nbsp;&nbsp;
+            <a id="selectNone" href="#ckb">None</a>&nbsp;&nbsp;
+            <a id="selectToggle" href="#ckb">Toggle</a>&nbsp;&nbsp;
+            <?php } else {
+                echo 'No custom lists defined yet &mdash; add one!';
+            } ?>
+        </td>
+     </tr>
+    </tfoot>
 </table>
 <?php
 if ($count) //Show options..
     echo '<div>&nbsp;Page:'.$pageNav->getPageLinks().'&nbsp;</div>';
 ?>
+
+<p class="centered" id="actions">
+    <input class="button" type="submit" name="delete" value="Delete">
+</p>
+</form>
+
+<div style="display:none;" class="dialog" id="confirm-action">
+    <h3>Please Confirm</h3>
+    <a class="close" href="">&times;</a>
+    <hr/>
+    <p class="confirm-action" style="display:none;" id="delete-confirm">
+        <font color="red"><strong>Are you sure you want to DELETE selected lists?</strong></font>
+        <br><br>Deleted forms CANNOT be recovered.
+    </p>
+    <div>Please confirm to continue.</div>
+    <hr style="margin-top:1em"/>
+    <p class="full-width">
+        <span class="buttons" style="float:left">
+            <input type="button" value="No, Cancel" class="close">
+        </span>
+        <span class="buttons" style="float:right">
+            <input type="button" value="Yes, Do it!" class="confirm">
+        </span>
+    </p>
+    <div class="clear"></div>
+</div>
diff --git a/include/staff/settings-emails.inc.php b/include/staff/settings-emails.inc.php
index e433cd7a2..f8b96f04b 100644
--- a/include/staff/settings-emails.inc.php
+++ b/include/staff/settings-emails.inc.php
@@ -15,6 +15,23 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config)
         </tr>
     </thead>
     <tbody>
+        <tr>
+            <td width="180" class="required">Default Email Templates:</td>
+            <td>
+                <select name="default_template_id">
+                    <option value="">&mdash; Select Default Template &mdash;</option>
+                    <?php
+                    $sql='SELECT tpl_id,name FROM '.EMAIL_TEMPLATE_GRP_TABLE.' WHERE isactive=1 ORDER BY name';
+                    if(($res=db_query($sql)) && db_num_rows($res)){
+                        while (list($id, $name) = db_fetch_row($res)){
+                            $selected = ($config['default_template_id']==$id)?'selected="selected"':''; ?>
+                            <option value="<?php echo $id; ?>"<?php echo $selected; ?>><?php echo $name; ?></option>
+                        <?php
+                        }
+                    } ?>
+                </select>&nbsp;<font class="error">*&nbsp;<?php echo $errors['default_template_id']; ?></font>
+            </td>
+        </tr>
         <tr>
             <td width="180" class="required">Default System Email:</td>
             <td>
diff --git a/include/staff/settings-system.inc.php b/include/staff/settings-system.inc.php
index 8dd170e41..838b4c0fc 100644
--- a/include/staff/settings-system.inc.php
+++ b/include/staff/settings-system.inc.php
@@ -54,23 +54,6 @@ $gmtime = Misc::gmtime();
                 </select>&nbsp;<font class="error">*&nbsp;<?php echo $errors['default_dept_id']; ?></font>
             </td>
         </tr>
-        <tr>
-            <td width="220" class="required">Default Email Templates:</td>
-            <td>
-                <select name="default_template_id">
-                    <option value="">&mdash; Select Default Template &mdash;</option>
-                    <?php
-                    $sql='SELECT tpl_id,name FROM '.EMAIL_TEMPLATE_GRP_TABLE.' WHERE isactive=1 ORDER BY name';
-                    if(($res=db_query($sql)) && db_num_rows($res)){
-                        while (list($id, $name) = db_fetch_row($res)){
-                            $selected = ($config['default_template_id']==$id)?'selected="selected"':''; ?>
-                            <option value="<?php echo $id; ?>"<?php echo $selected; ?>><?php echo $name; ?></option>
-                        <?php
-                        }
-                    } ?>
-                </select>&nbsp;<font class="error">*&nbsp;<?php echo $errors['default_template_id']; ?></font>
-            </td>
-        </tr>
 
         <tr><td>Default Page Size:</td>
             <td>
@@ -112,6 +95,19 @@ $gmtime = Misc::gmtime();
                 </select>
             </td>
         </tr>
+        <tr>
+            <td width="180">Default Name Formatting:</td>
+            <td>
+                <select name="name_format">
+<?php foreach (PersonsName::allFormats() as $n=>$f) {
+    list($desc, $func) = $f;
+    $selected = ($config['name_format'] == $n) ? 'selected="selected"' : ''; ?>
+                    <option value="<?php echo $n; ?>" <?php echo $selected;
+                        ?>><?php echo $desc; ?></option>
+<?php } ?>
+                </select>
+            </td>
+        </tr>
         <tr>
             <th colspan="2">
                 <em><b>Authentication Settings</b></em>
diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php
index d360b4d29..6585e8aaa 100644
--- a/include/staff/settings-tickets.inc.php
+++ b/include/staff/settings-tickets.inc.php
@@ -77,13 +77,6 @@ if(!($maxfileuploads=ini_get('max_file_uploads')))
             </td>
         </tr>
         <tr>
-                    <td width="180">Web Tickets Priority:</td>
-                    <td>
-                        <input type="checkbox" name="allow_priority_change" value="1" <?php echo $config['allow_priority_change'] ?'checked="checked"':''; ?>>
-                        <em>(Allow user to override/set priority)</em>
-                    </td>
-                </tr>
-                <tr>
                     <td width="180">Emailed Tickets Priority:</td>
                     <td>
                         <input type="checkbox" name="use_email_priority" value="1" <?php echo $config['use_email_priority'] ?'checked="checked"':''; ?> >
diff --git a/include/staff/templates/user-info.tmpl.php b/include/staff/templates/user-info.tmpl.php
index 3cfb9d467..b9802b8f0 100644
--- a/include/staff/templates/user-info.tmpl.php
+++ b/include/staff/templates/user-info.tmpl.php
@@ -18,7 +18,6 @@
     <table width="100%">
     <?php
         echo csrf_token();
-        $static->render();
         foreach ($custom as $form)
             $form->render();
     ?>
diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php
index 46356bbba..826d19e79 100644
--- a/include/staff/ticket-open.inc.php
+++ b/include/staff/ticket-open.inc.php
@@ -18,7 +18,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
     </thead>
     <tbody>
         <?php
-        UserForm::getStaticForm()->render();
+        $uf = UserForm::objects();
+        $uf[0]->render();
         if($cfg->notifyONNewStaffTicket()) {  ?>
         <tr>
             <td width="160">Alert:</td>
diff --git a/open.php b/open.php
index 1370e4c9c..468f700b5 100644
--- a/open.php
+++ b/open.php
@@ -38,6 +38,13 @@ if($_POST):
                 $errors += $form->errors();
         }
     }
+    // Don't process contact information for logged-in clients
+    if (!$thisclient) {
+        $contact_form = UserForm::getInstance();
+        if (!$contact_form->isValid())
+            $errors += $contact_form->errors();
+    }
+
     if (!$errors && $cfg->allowOnlineAttachments() && $_FILES['attachments'])
         $vars['files'] = AttachmentFile::format($_FILES['attachments'], true);
 
@@ -51,6 +58,10 @@ if($_POST):
             $form->save();
             $ticket->loadDynamicData();
         }
+        if (isset($contact_form)) {
+            $contact_form->setClientId($ticket->getOwnerId());
+            $contact_form->save();
+        }
         //Logged in...simply view the newly created ticket.
         if($thisclient && $thisclient->isValid()) {
             if(!$cfg->showRelatedTickets())
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 070aafeeb..e255d2e59 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -581,11 +581,15 @@ div.section-break h3 {
 }
 
 .form_table th h4 {
-    margin:0;
+    margin: -0.3em -0.3em;
     color:#fff;
     background:#929292;
+    padding: 0.3em;
 }
 
+.form_table th em:not(:first-child) {
+    margin-top: 0.6em;
+}
 .form_table th em {
     display:block;
     color:#000;
diff --git a/scp/forms.php b/scp/forms.php
index a26aa7dfc..5c0a2ad04 100644
--- a/scp/forms.php
+++ b/scp/forms.php
@@ -9,13 +9,14 @@ if($_REQUEST['id'] && !($form=DynamicForm::lookup($_REQUEST['id'])))
 if($_POST) {
     $fields = array('title', 'notes', 'instructions');
     $required = array('subject');
+    $max_sort = 0;
     switch(strtolower($_POST['do'])) {
         case 'update':
             foreach ($fields as $f)
                 if (isset($_POST[$f]))
                     $form->set($f, $_POST[$f]);
             if ($form->isValid())
-                $form->save();
+                $form->save(true);
             foreach ($form->getDynamicFields() as $field) {
                 $id = $field->get('id');
                 if ($_POST["delete-$id"] == 'on' && $field->isDeletable()) {
@@ -25,7 +26,7 @@ if($_POST) {
                 }
                 if (isset($_POST["type-$id"]) && $field->isChangeable())
                     $field->set('type', $_POST["type-$id"]);
-                if (isset($_POST["name-$id"]) && $field->isNameEditable())
+                if (isset($_POST["name-$id"]) && !$field->isNameForced())
                     $field->set('name', $_POST["name-$id"]);
                 # TODO: make sure all help topics still have all required fields
                 if (!$field->isRequirementForced())
@@ -39,6 +40,8 @@ if($_POST) {
                 }
                 if ($field->isValid())
                     $field->save();
+                // Keep track of the last sort number
+                $max_sort = max($max_sort, $field->get('sort'));
             }
             break;
         case 'add':
@@ -47,7 +50,30 @@ if($_POST) {
                 'instructions'=>$_POST['instructions'],
                 'notes'=>$_POST['notes']));
             if ($form->isValid())
-                $form->save();
+                $form->save(true);
+            break;
+
+        case 'mass_process':
+            if(!$_POST['ids'] || !is_array($_POST['ids']) || !count($_POST['ids'])) {
+                $errors['err'] = 'You must select at least one API key';
+            } else {
+                $count = count($_POST['ids']);
+                switch(strtolower($_POST['a'])) {
+                    case 'delete':
+                        $i=0;
+                        foreach($_POST['ids'] as $k=>$v) {
+                            if(($t=DynamicForm::lookup($v)) && $t->delete())
+                                $i++;
+                        }
+                        if ($i && $i==$count)
+                            $msg = 'Selected custom forms deleted successfully';
+                        elseif ($i > 0)
+                            $warn = "$i of $count selected forms deleted";
+                        elseif (!$errors['err'])
+                            $errors['err'] = 'Unable to delete selected custom forms';
+                        break;
+                }
+            }
             break;
     }
 
@@ -57,7 +83,7 @@ if($_POST) {
                 continue;
             $field = DynamicFormField::create(array(
                 'form_id'=>$form->get('id'),
-                'sort'=>$_POST["sort-new-$i"],
+                'sort'=>$_POST["sort-new-$i"] ? $_POST["sort-new-$i"] : $max_sort++,
                 'label'=>$_POST["label-new-$i"],
                 'type'=>$_POST["type-new-$i"],
                 'name'=>$_POST["name-new-$i"],
diff --git a/scp/js/scp.js b/scp/js/scp.js
index d4e90e1b8..961e9e932 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -295,13 +295,14 @@ $(document).ready(function(){
             });
         },
         onselect: function (obj) {
+            $('#basic-ticket-search').val(obj.value);
             $('#basic-ticket-search').closest('form').submit();
         },
-        property: "value"
+        property: "matches"
     });
 
     /* Typeahead user lookup */
-    $('#email.typeahead').typeahead({
+    $('.email.typeahead').typeahead({
         source: function (typeahead, query) {
             if(query.length > 2) {
                 $.ajax({
@@ -314,9 +315,9 @@ $(document).ready(function(){
             }
         },
         onselect: function (obj) {
-            var fObj=$('#email.typeahead').closest('form');
+            var fObj=$('.email.typeahead').closest('form');
             if(obj.name)
-                $('#name', fObj).val(obj.name);
+                $('.auto.name', fObj).val(obj.name);
         },
         property: "email"
     });
diff --git a/scp/lists.php b/scp/lists.php
index acd3cc9c0..81cf2663d 100644
--- a/scp/lists.php
+++ b/scp/lists.php
@@ -37,6 +37,30 @@ if($_POST) {
             if ($list->isValid())
                 $list->save(true);
             break;
+
+        case 'mass_process':
+            if(!$_POST['ids'] || !is_array($_POST['ids']) || !count($_POST['ids'])) {
+                $errors['err'] = 'You must select at least one API key';
+            } else {
+                $count = count($_POST['ids']);
+                switch(strtolower($_POST['a'])) {
+                    case 'delete':
+                        $i=0;
+                        foreach($_POST['ids'] as $k=>$v) {
+                            if(($t=DynamicList::lookup($v)) && $t->delete())
+                                $i++;
+                        }
+                        if ($i && $i==$count)
+                            $msg = 'Selected custom lists deleted successfully';
+                        elseif ($i > 0)
+                            $warn = "$i of $count selected lists deleted";
+                        elseif (!$errors['err'])
+                            $errors['err'] = 'Unable to delete selected custom lists'
+                                .' &mdash; they may be in use on a custom form';
+                        break;
+                }
+            }
+            break;
     }
 
     if ($list) {
diff --git a/scp/tickets.php b/scp/tickets.php
index c5ade077c..067df4f70 100644
--- a/scp/tickets.php
+++ b/scp/tickets.php
@@ -483,14 +483,15 @@ if($_POST && !$errors):
                     if(($ticket=Ticket::open($vars, $errors))) {
                         $msg='Ticket created successfully';
                         $_REQUEST['a']=null;
-                        # TODO: Save dynamic form(s)
+                        # Save extra dynamic form(s)
                         if (isset($form)) {
                             $form->setTicketId($ticket->getId());
                             $form->save();
-                            $ticket->loadDynamicData();
                         }
-                        if(!$ticket->checkStaffAccess($thisstaff) || $ticket->isClosed())
+                        if (!$ticket->checkStaffAccess($thisstaff) || $ticket->isClosed())
                             $ticket=null;
+                        else
+                            $ticket->loadDynamicData();
                         Draft::deleteForNamespace('ticket.staff%', $thisstaff->getId());
                     } elseif(!$errors['err']) {
                         $errors['err']='Unable to create the ticket. Correct the error(s) and try again';
diff --git a/setup/doc/forms.md b/setup/doc/forms.md
new file mode 100644
index 000000000..c3545096c
--- /dev/null
+++ b/setup/doc/forms.md
@@ -0,0 +1,51 @@
+osTicket Forms API
+==================
+
+osTicket now includes a (relatively) complete forms API. Forms can be
+created as objects and used to render their corresponding widgets to the
+screen without actually writing and HTML.
+
+Defining a form
+---------------
+Instanciate a form from a list of fields. PHP unfortunately does not allow
+one to create a class and instanciate a list of instances into a static
+property. Because of this limitation, the field list is specified at the
+form instance construction time by passing the list of fields into the
+constructor.
+
+The simplest way to create forms is to instanciate the Form instance
+directly:
+
+    $form = new Form(array(
+        'email' => new TextboxField(array('label'=>'Email Address')),
+    );
+
+The form can then be rendered to HTML and sent to the user. Later the form
+can be recreated after a POST and the data from the request will
+automatically be placed into the form. Check if the form is valid:
+
+    if ($form->isValid())
+        $object->update($form->getClean());
+
+The `getClean()` method will return a hash array, where the keys are the
+keys in the field array passed to the form constructor, and the values are
+the cleaned values from the form fields based on the data from the request.
+
+To create a class that defines the fields statically, one might write a
+trampoline constructor:
+
+    class UserForm extends Form {
+        function __construct() {
+            $args = func_get_args();
+            $fields = array(
+                'email' => new TextboxField(array(
+                    'label'=>'Email Address')
+                ),
+            );
+            array_unshift($args, $fields);
+            call_user_func_array(array('parent','__construct'), $args);
+        }
+    }
+
+Here, the fields are defined statically in the constructor. Do not bother
+trying to specify the fields in a static property. You'll end up crying.
diff --git a/setup/doc/orm.md b/setup/doc/orm.md
new file mode 100644
index 000000000..3a77fc7de
--- /dev/null
+++ b/setup/doc/orm.md
@@ -0,0 +1,11 @@
+osTicket ORM
+============
+
+Creating a new model
+--------------------
+Eventually, we will have scripts to create the orm model from a mockup file
+with field definitions, complete with validation routines. Currently, the
+model validation must be written manually, and the fields are assumed based
+on the fields available in the database on fetch, and the name of the field
+updated on update.
+
-- 
GitLab