diff --git a/bootstrap.php b/bootstrap.php
index 03007ba9b7f9f9e6db4d97afecc6a13560188833..41cd7cf4f5d700772919a697182d4eb50f882f1f 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -179,6 +179,7 @@ class Bootstrap {
 
     function loadCode() {
         #include required files
+        require_once INCLUDE_DIR.'class.util.php';
         require(INCLUDE_DIR.'class.signal.php');
         require(INCLUDE_DIR.'class.user.php');
         require(INCLUDE_DIR.'class.auth.php');
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index 23c978a93e608833839f6a80fd0176a1d83124b8..708bb6dbdc88b3f6adadba7205cc60efb5da8b4c 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -176,15 +176,15 @@ class TicketsAjaxAPI extends AjaxController {
 
             if($req['staffId'] && !$req['status']) //Assigned TO + Closed By
                 $where.= ' OR (ticket.staff_id='.db_input($req['staffId']).
-                    ' AND status.state IN("resolved", "closed")) ';
+                    ' AND status.state IN("closed")) ';
             elseif($req['staffId']) // closed by any
-                $where.= ' OR status.state IN("resolved", "closed") ';
+                $where.= ' OR status.state IN("closed") ';
 
             $where.= ' ) ';
         } elseif($req['staffId']) { # closed-by
             $where.=' AND (ticket.staff_id='.db_input($req['staffId']).' AND
-                status.state IN("resolved", "closed")) ';
-            $criteria['state__in'] = array('resolved','closed');
+                status.state IN("closed")) ';
+            $criteria['state__in'] = array('closed');
             $criteria['staff_id'] = $req['staffId'];
         }
 
@@ -721,9 +721,6 @@ class TicketsAjaxAPI extends AjaxController {
             case 'reopen':
                 $state = 'open';
                 break;
-            case 'resolve':
-                $state = 'resolved';
-                break;
             case 'close':
                 if (!$thisstaff->canCloseTickets())
                     Http::response(403, 'Access denied');
@@ -772,7 +769,6 @@ class TicketsAjaxAPI extends AjaxController {
                         $errors['err'] = sprintf(__('You do not have permission %s.'),
                                 __('to reopen tickets'));
                     break;
-                case 'resolved':
                 case 'closed':
                     if (!$thisstaff->canCloseTickets())
                         $errors['err'] = sprintf(__('You do not have permission %s.'),
@@ -836,9 +832,6 @@ class TicketsAjaxAPI extends AjaxController {
             case 'reopen':
                 $state = 'open';
                 break;
-            case 'resolve':
-                $state = 'resolved';
-                break;
             case 'close':
                 if (!$thisstaff->canCloseTickets())
                     Http::response(403, 'Access denied');
@@ -884,7 +877,6 @@ class TicketsAjaxAPI extends AjaxController {
                         $errors['err'] = sprintf(__('You do not have permission %s.'),
                                 __('to reopen tickets'));
                     break;
-                case 'resolved':
                 case 'closed':
                     if (!$thisstaff->canCloseTickets())
                         $errors['err'] = sprintf(__('You do not have permission %s.'),
diff --git a/include/api.tickets.php b/include/api.tickets.php
index 47d978a498aac405381d4f5659f2b8072d092da6..6371daa632ff5240b0bee90cb3bed69c0ac56384 100644
--- a/include/api.tickets.php
+++ b/include/api.tickets.php
@@ -149,6 +149,10 @@ class TicketApiController extends ApiController {
             $data = $this->getEmailRequest();
 
         if (($thread = ThreadEntry::lookupByEmailHeaders($data))
+                && ($t=$thread->getTicket())
+                && ($data['staffId']
+                    || !$t->isClosed()
+                    || $t->isReopenable())
                 && $thread->postEmail($data)) {
             return $thread->getTicket();
         }
diff --git a/include/class.client.php b/include/class.client.php
index 7dc764d1c8cfe4b59e083abf270089d26ac781e4..bfcffeaca09f2a1251a0d3ae7fd8125cd5d08e1b 100644
--- a/include/class.client.php
+++ b/include/class.client.php
@@ -250,17 +250,13 @@ class  EndUser extends AuthenticatedUser {
         if (!($stats=$this->getTicketStats()))
             return 0;
 
-        return $stats['open']+$stats['resolved']+ $stats['closed'];
+        return $stats['open']+$stats['closed'];
     }
 
     function getNumOpenTickets() {
         return ($stats=$this->getTicketStats())?$stats['open']:0;
     }
 
-    function getNumResolvedTickets() {
-        return ($stats=$this->getTicketStats())?$stats['resolved']:0;
-    }
-
     function getNumClosedTickets() {
         return ($stats=$this->getTicketStats())?$stats['closed']:0;
     }
@@ -303,14 +299,6 @@ class  EndUser extends AuthenticatedUser {
                 . $join
                 . $where
 
-                .'UNION SELECT \'resolved\', count( ticket.ticket_id ) AS tickets '
-                .'FROM ' . TICKET_TABLE . ' ticket '
-                .'INNER JOIN '.TICKET_STATUS_TABLE. ' status
-                    ON (ticket.status_id=status.id
-                            AND status.state=\'resolved\') '
-                . $join
-                . $where
-
                 .'UNION SELECT \'closed\', count( ticket.ticket_id ) AS tickets '
                 .'FROM ' . TICKET_TABLE . ' ticket '
                 .'INNER JOIN '.TICKET_STATUS_TABLE. ' status
diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index eaa20067974cf9afbce123e9296356b100cc2e42..8637598c23f3b05d3be1450a337a036ed9b1a772 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -53,11 +53,11 @@ class DynamicForm extends VerySimpleModel {
             $fields = &$this->_fields;
 
         if (!$fields) {
-            $fields = new ArrayObject();
+            $fields = new ListObject();
             foreach ($this->getDynamicFields() as $f)
-                // TODO: Index by field name or id
-                $fields[$f->get('id')] = $f->getImpl($f);
+                $fields->append($f->getImpl($f));
         }
+
         return $fields;
     }
 
diff --git a/include/class.forms.php b/include/class.forms.php
index 585772fdf9a914b937bcbc0501e582b58abf65ea..0ebf717d835c6f211a230fcc4530e2be5aeac6ba 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -236,7 +236,21 @@ class FormField {
         if (!isset($this->_clean)) {
             $this->_clean = (isset($this->value))
                 ? $this->value : $this->parse($this->getWidget()->value);
-            $this->validateEntry($this->_clean);
+
+            if ($vs = $this->get('cleaners')) {
+                if (is_array($vs)) {
+                    foreach ($vs as $cleaner)
+                        if (is_callable($cleaner))
+                            $this->_clean = call_user_func_array(
+                                    $cleaner, array($this, $this->_clean));
+                }
+                elseif (is_callable($vs))
+                    $this->_clean = call_user_func_array(
+                            $vs, array($this, $this->_clean));
+            }
+
+            if ($this->isVisible())
+                $this->validateEntry($this->_clean);
         }
         return $this->_clean;
     }
@@ -291,6 +305,31 @@ class FormField {
         }
     }
 
+    /**
+     * isVisible
+     *
+     * If this field has visibility configuration, then it will parse the
+     * constraints with the visibility configuration to determine if the
+     * field is visible and should be considered for validation
+     */
+    function isVisible() {
+        $config = $this->getConfiguration();
+        if ($this->get('visibility') instanceof VisibilityConstraint) {
+            return $this->get('visibility')->isVisible($this);
+        }
+        return true;
+    }
+
+    /**
+     * FIXME: Temp
+     *
+     */
+
+    function isEditable() {
+        return (($this->get('edit_mask') & 32) == 0);
+    }
+
+
     /**
      * parse
      *
@@ -452,7 +491,11 @@ class FormField {
     }
 
     function render($mode=null) {
-        return $this->getWidget()->render($mode);
+        $rv = $this->getWidget()->render($mode);
+        if ($v = $this->get('visibility')) {
+            $v->emitJavascript($this);
+        }
+        return $rv;
     }
 
     function renderExtras($mode=null) {
@@ -619,7 +662,27 @@ class TextboxField extends FormField {
             'validator' => new ChoiceField(array(
                 'id'=>3, 'label'=>__('Validator'), 'required'=>false, 'default'=>'',
                 'choices' => array('phone'=>__('Phone Number'),'email'=>__('Email Address'),
-                    'ip'=>__('IP Address'), 'number'=>__('Number'), ''=>__('None')))),
+                    'ip'=>__('IP Address'), 'number'=>__('Number'),
+                    'regex'=>__('Custom (Regular Expression)'), ''=>__('None')))),
+            'regex' => new TextboxField(array(
+                'id'=>6, 'label'=>__('Regular Expression'), 'required'=>true,
+                'configuration'=>array('size'=>40, 'length'=>100),
+                'visibility' => new VisibilityConstraint(
+                    new Q(array('validator__eq'=>'regex')),
+                    VisibilityConstraint::HIDDEN
+                ),
+                'cleaners' => function ($self, $value) {
+                    $wrapped = "/".$value."/iu";
+                    if (false === @preg_match($value, ' ')
+                            && false !== @preg_match($wrapped, ' ')) {
+                        return $wrapped;
+                    }
+                    return $value;
+                },
+                'validators' => function($self, $v) {
+                    if (false === @preg_match($v, ' '))
+                        $self->addError(__('Cannot compile this regular expression'));
+                })),
             'validator-error' => new TextboxField(array(
                 'id'=>4, 'label'=>__('Validation Error'), 'default'=>'',
                 'configuration'=>array('size'=>40, 'length'=>60),
@@ -647,7 +710,13 @@ class TextboxField extends FormField {
                 __('Enter a valid phone number')),
             'ip' =>     array(array('Validator', 'is_ip'),
                 __('Enter a valid IP address')),
-            'number' => array('is_numeric', __('Enter a number'))
+            'number' => array('is_numeric', __('Enter a number')),
+            'regex' => array(
+                function($v) use ($config) {
+                    $regex = $config['regex'];
+                    return @preg_match($regex, $v);
+                }, __('Value does not match required pattern')
+            ),
         );
         // Support configuration forms, as well as GUI-based form fields
         $valid = $this->get('validator');
@@ -995,11 +1064,12 @@ class DatetimeField extends FormField {
                 'hint'=>__('Earliest date selectable'))),
             'max' => new DatetimeField(array(
                 'id'=>4, 'label'=>__('Latest'), 'required'=>false,
-                'default'=>null)),
+                'default'=>null, 'hint'=>__('Latest date selectable'))),
             'future' => new BooleanField(array(
                 'id'=>5, 'label'=>__('Allow Future Dates'), 'required'=>false,
                 'default'=>true, 'configuration'=>array(
-                    'desc'=>__('Allow entries into the future' /* Used in the date field */)))),
+                    'desc'=>__('Allow entries into the future' /* Used in the date field */)),
+            )),
         );
     }
 
@@ -1159,10 +1229,6 @@ class TicketStateField extends ChoiceField {
                 'name' => /* @trans, @context "ticket state name" */ 'Open',
                 'verb' => /* @trans, @context "ticket state action" */ 'Open'
                 ),
-            'resolved' => array(
-                'name' => /* @trans, @context "ticket state name" */ 'Resolved',
-                'verb' => /* @trans, @context "ticket state action" */ 'Resolve'
-                ),
             'closed' => array(
                 'name' => /* @trans, @context "ticket state name" */ 'Closed',
                 'verb' => /* @trans, @context "ticket state action" */ 'Close'
@@ -1582,6 +1648,7 @@ class Widget {
     function __construct($field) {
         $this->field = $field;
         $this->name = $field->getFormName();
+        $this->id = '_' . $this->name;
     }
 
     function parseValue() {
@@ -1601,6 +1668,18 @@ class Widget {
             return $data[$this->field->get('name')];
         return null;
     }
+
+    /**
+     * getJsValueGetter
+     *
+     * Used with the dependent fields feature, this function should return a
+     * single javascript expression which can be used in a larger expression
+     * (<> == true, where <> is the result of this function). The %s token
+     * will be replaced with a jQuery variable representing this widget.
+     */
+    function getJsValueGetter() {
+        return '%s.val()';
+    }
 }
 
 class TextboxWidget extends Widget {
@@ -1621,7 +1700,7 @@ class TextboxWidget extends Widget {
         ?>
         <span style="display:inline-block">
         <input type="<?php echo static::$input_type; ?>"
-            id="<?php echo $this->name; ?>"
+            id="<?php echo $this->id; ?>"
             <?php echo implode(' ', array_filter(array(
                 $size, $maxlength, $classes, $autocomplete, $disabled)))
                 .' placeholder="'.$config['placeholder'].'"'; ?>
@@ -1663,6 +1742,7 @@ class TextareaWidget extends Widget {
         <span style="display:inline-block;width:100%">
         <textarea <?php echo $rows." ".$cols." ".$maxlength." ".$class
                 .' placeholder="'.$config['placeholder'].'"'; ?>
+            id="<?php echo $this->id; ?>"
             name="<?php echo $this->name; ?>"><?php
                 echo Format::htmlchars($this->value);
             ?></textarea>
@@ -1676,7 +1756,7 @@ class PhoneNumberWidget extends Widget {
         $config = $this->field->getConfiguration();
         list($phone, $ext) = explode("X", $this->value);
         ?>
-        <input type="text" name="<?php echo $this->name; ?>" value="<?php
+        <input id="<?php echo $this->id; ?>" type="text" name="<?php echo $this->name; ?>" value="<?php
         echo Format::htmlchars($phone); ?>"/><?php
         // Allow display of extension field even if disabled if the phone
         // number being edited has an extension
@@ -1748,7 +1828,7 @@ class ChoicesWidget extends Widget {
 
         ?>
         <select name="<?php echo $this->name; ?>[]"
-            id="<?php echo $this->name; ?>"
+            id="<?php echo $this->id; ?>"
             data-prompt="<?php echo $prompt; ?>"
             <?php if ($config['multiselect'])
                 echo ' multiple="multiple" class="multiselect"'; ?>>
@@ -1793,6 +1873,10 @@ class ChoicesWidget extends Widget {
         }
         return $values;
     }
+
+    function getJsValueGetter() {
+        return '%s.find(":selected").val()';
+    }
 }
 
 class CheckboxWidget extends Widget {
@@ -1806,7 +1890,8 @@ class CheckboxWidget extends Widget {
         if (!isset($this->value))
             $this->value = $this->field->get('default');
         ?>
-        <input style="vertical-align:top;" type="checkbox" name="<?php echo $this->name; ?>[]" <?php
+        <input id="<?php echo $this->id; ?>" style="vertical-align:top;"
+            type="checkbox" name="<?php echo $this->name; ?>[]" <?php
             if ($this->value) echo 'checked="checked"'; ?> value="<?php
             echo $this->field->get('id'); ?>"/>
         <?php
@@ -1822,6 +1907,10 @@ class CheckboxWidget extends Widget {
             return @in_array($this->field->get('id'), $data[$this->name]);
         return parent::getValue();
     }
+
+    function getJsValueGetter() {
+        return '%s.is(":checked")';
+    }
 }
 
 class DatetimePickerWidget extends Widget {
@@ -1841,6 +1930,7 @@ class DatetimePickerWidget extends Widget {
         }
         ?>
         <input type="text" name="<?php echo $this->name; ?>"
+            id="<?php echo $this->id; ?>"
             value="<?php echo Format::htmlchars($this->value); ?>" size="12"
             autocomplete="off" class="dp" />
         <script type="text/javascript">
@@ -2035,4 +2125,172 @@ class FileUploadWidget extends Widget {
 
 class FileUploadError extends Exception {}
 
+class VisibilityConstraint {
+
+    const HIDDEN =      0x0001;
+    const VISIBLE =     0x0002;
+
+    var $initial;
+    var $constraint;
+
+    function __construct($constraint, $initial=self::VISIBLE) {
+        $this->constraint = $constraint;
+        $this->initial = $initial;
+    }
+
+    function emitJavascript($field) {
+        $func = 'recheck';
+        $form = $field->getForm();
+?>
+    <script type="text/javascript">
+      !(function() {
+        var <?php echo $func; ?> = function() {
+          var target = $('#field<?php echo $field->getWidget()->id; ?>');
+
+<?php   $fields = $this->getAllFields($this->constraint);
+        foreach ($fields as $f) {
+            $field = $form->getField($f);
+            echo sprintf('var %1$s = $("#%1$s");',
+                $field->getWidget()->id);
+        }
+        $expression = $this->compileQ($this->constraint, $form);
+?>
+          if (<?php echo $expression; ?>)
+            target.slideDown('fast', function (){
+                $(this).trigger('show');
+                });
+          else
+            target.slideUp('fast', function (){
+                $(this).trigger('hide');
+                });
+        };
+
+<?php   foreach ($fields as $f) {
+            $w = $form->getField($f)->getWidget();
+?>
+        $('#<?php echo $w->id; ?>').on('change', <?php echo $func; ?>);
+        $('#field<?php echo $w->id; ?>').on('show hide', <?php
+                echo $func; ?>);
+<?php   } ?>
+      })();
+    </script><?php
+    }
+
+    /**
+     * Determines if the field was visible when the form was submitted
+     */
+    function isVisible($field) {
+        return $this->compileQPhp($this->constraint, $field);
+    }
+
+    function compileQPhp(Q $Q, $field) {
+        $form = $field->getForm();
+        $expr = array();
+        foreach ($Q->constraints as $c=>$value) {
+            if ($value instanceof Q) {
+                $expr[] = $this->compileQPhp($value, $field);
+            }
+            else {
+                @list($f, $op) = explode('__', $c, 2);
+                $field = $form->getField($f);
+                $wval = $field->getClean();
+                switch ($op) {
+                case 'eq':
+                case null:
+                    $expr[] = ($wval == $value && $field->isVisible());
+                }
+            }
+        }
+        $glue = $Q->isOred()
+            ? function($a, $b) { return $a || $b; }
+            : function($a, $b) { return $a && $b; };
+        $initial = !$Q->isOred();
+        $expression = array_reduce($expr, $glue, $initial);
+        if ($Q->isNegated)
+            $expression = !$expression;
+        return $expression;
+    }
+
+    function getAllFields(Q $Q, &$fields=array()) {
+        foreach ($Q->constraints as $c=>$value) {
+            if ($c instanceof Q) {
+                $this->getAllFields($c, $fields);
+            }
+            else {
+                list($f, $op) = explode('__', $c, 2);
+                $fields[$f] = true;
+            }
+        }
+        return array_keys($fields);
+    }
+
+    function compileQ($Q, $form) {
+        $expr = array();
+        foreach ($Q->constraints as $c=>$value) {
+            if ($value instanceof Q) {
+                $expr[] = $this->compileQ($value, $form);
+            }
+            else {
+                list($f, $op) = explode('__', $c, 2);
+                $widget = $form->getField($f)->getWidget();
+                $id = $widget->id;
+                switch ($op) {
+                case 'eq':
+                case null:
+                    $expr[] = sprintf('(%s.is(":visible") && %s)',
+                            $id,
+                            sprintf('%s == %s',
+                                sprintf($widget->getJsValueGetter(), $id),
+                                JsonDataEncoder::encode($value))
+                            );
+                }
+            }
+        }
+        $glue = $Q->isOred() ? ' || ' : ' && ';
+        $expression = implode($glue, $expr);
+        if (count($expr) > 1)
+            $expression = '('.$expression.')';
+        if ($Q->isNegated)
+            $expression = '!'.$expression;
+        return $expression;
+    }
+}
+
+class Q {
+    const NEGATED = 0x0001;
+    const ANY =     0x0002;
+
+    var $constraints;
+    var $flags;
+    var $negated = false;
+    var $ored = false;
+
+    function __construct($filter, $flags=0) {
+        $this->constraints = $filter;
+        $this->negated = $flags & self::NEGATED;
+        $this->ored = $flags & self::ANY;
+    }
+
+    function isNegated() {
+        return $this->negated;
+    }
+
+    function isOred() {
+        return $this->ored;
+    }
+
+    function negate() {
+        $this->negated = !$this->negated;
+        return $this;
+    }
+
+    static function not(array $constraints) {
+        return new static($constraints, self::NEGATED);
+    }
+
+    static function any(array $constraints) {
+        return new static($constraints, self::ORED);
+    }
+}
+
 ?>
diff --git a/include/class.list.php b/include/class.list.php
index 274009de9e1a2f82ae458143f0b405aa78363aa4..dfee67d558d3ef2a56ac81f16e14fa8a090c82a7 100644
--- a/include/class.list.php
+++ b/include/class.list.php
@@ -554,7 +554,8 @@ class DynamicListItem extends VerySimpleModel implements CustomListItem {
             $this->_form = DynamicForm::lookup(
                 array('type'=>'L'.$this->get('list_id')))->getForm($source);
             if (!$source && $config) {
-                foreach ($this->_form->getFields() as $f) {
+                $fields = $this->_form->getFields();
+                foreach ($fields as $f) {
                     $name = $f->get('id');
                     if (isset($config[$name]))
                         $f->value = $f->to_php($config[$name]);
@@ -563,6 +564,7 @@ class DynamicListItem extends VerySimpleModel implements CustomListItem {
                 }
             }
         }
+
         return $this->_form;
     }
 
@@ -766,7 +768,6 @@ class TicketStatus  extends VerySimpleModel implements CustomListItem {
 
     var $_list;
     var $_form;
-    var $_config;
     var $_settings;
     var $_properties;
 
@@ -777,7 +778,6 @@ class TicketStatus  extends VerySimpleModel implements CustomListItem {
 
     function __construct() {
         call_user_func_array(array('parent', '__construct'), func_get_args());
-        $this->_config = new Config('TS.'.$this->getId());
     }
 
     protected function hasFlag($field, $flag) {
@@ -793,7 +793,7 @@ class TicketStatus  extends VerySimpleModel implements CustomListItem {
     }
 
     protected function hasProperties() {
-        return ($this->_config->get('properties'));
+        return ($this->get('properties'));
     }
 
     function getForm() {
@@ -804,21 +804,81 @@ class TicketStatus  extends VerySimpleModel implements CustomListItem {
         return $this->_form;
     }
 
+    function getExtraConfigOptions($source=null) {
+
+
+        $status_choices = array( 0 => __('System Default'));
+        if (($statuses=TicketStatusList::getStatuses(
+                        array( 'enabled' => true, 'states' =>
+                            array('open')))))
+            foreach ($statuses as $s)
+                $status_choices[$s->getId()] = $s->getName();
+
+
+        return array(
+            'allowreopen' => new BooleanField(array(
+                'label' =>__('Allow Reopen'),
+                'default' => isset($source['allowreopen'])
+                    ?  $source['allowreopen']: true,
+                'id' => 'allowreopen',
+                'name' => 'allowreopen',
+                'configuration' => array(
+                    'desc'=>__('Allow tickets on this status to be reopened by end users'),
+                ),
+                'visibility' => new VisibilityConstraint(
+                    new Q(array('state__eq'=>'closed')),
+                    VisibilityConstraint::HIDDEN
+                ),
+            )),
+            'reopenstatus' => new ChoiceField(array(
+                'label' => __('Reopen Status'),
+                'required' => false,
+                'default' => isset($source['reopenstatus'])
+                    ? $source['reopenstatus'] : 0,
+                'id' => 'reopenstatus',
+                'name' => 'reopenstatus',
+                'choices' => $status_choices,
+                'configuration' => array(
+                    'widget' => 'dropdown',
+                    'multiselect' =>false
+                ),
+                'visibility' => new VisibilityConstraint(
+                    new Q(array('allowreopen__eq'=> true)),
+                    VisibilityConstraint::HIDDEN
+                ),
+            ))
+        );
+    }
+
     function getConfigurationForm($source=null) {
 
-        if ($form = $this->getForm()) {
-            $config = $this->getConfiguration();
-            $form = $form->getForm($source);
-            if (!$source && $config) {
-                foreach ($form->getFields() as $f) {
-                    $name = $f->get('id');
-                    if (isset($config[$name]))
-                        $f->value = $f->to_php($config[$name]);
-                    else if ($f->get('default'))
-                        $f->value = $f->get('default');
+        if (!($form = $this->getForm()))
+            return null;
+
+        $config = $this->getConfiguration();
+        $form = $form->getForm($source);
+        $fields = $form->getFields();
+        foreach ($fields as $k => $f) {
+            if ($f->get('name') == 'state' //TODO: check if editable.
+                    && ($extras=$this->getExtraConfigOptions($source))) {
+                foreach ($extras as $extra) {
+                    $extra->setForm($form);
+                    $fields->insert(++$k, $extra);
                 }
+                break;
             }
         }
+
+        if (!$source && $config) {
+            foreach ($fields as $f) {
+                $name = $f->get('id');
+                if (isset($config[$name]))
+                    $f->value = $f->to_php($config[$name]);
+                else if ($f->get('default'))
+                    $f->value = $f->get('default');
+            }
+        }
+
         return $form;
     }
 
@@ -826,6 +886,34 @@ class TicketStatus  extends VerySimpleModel implements CustomListItem {
         return $this->hasFlag('mode', self::ENABLED);
     }
 
+    function isReopenable() {
+
+        if (strcasecmp($this->get('state'), 'closed'))
+            return true;
+
+        if (($c=$this->getConfiguration())
+                && $c['allowreopen']
+                && isset($c['reopenstatus']))
+            return true;
+
+        return false;
+    }
+
+    function getReopenStatus() {
+        global $cfg;
+
+        $status = null;
+        if ($this->isReopenable()
+                && ($c = $this->getConfiguration())
+                && isset($c['reopenstatus']))
+            $status = TicketStatus::lookup(
+                    $c['reopenstatus'] ?: $cfg->getDefaultTicketStatusId());
+
+        return ($status
+                && !strcasecmp($status->getState(), 'open'))
+            ?  $status : null;
+    }
+
     function enable() {
 
         // Ticket status without properties cannot be enabled!
@@ -900,7 +988,7 @@ class TicketStatus  extends VerySimpleModel implements CustomListItem {
     private function getProperties() {
 
         if (!isset($this->_properties)) {
-            $this->_properties = $this->_config->get('properties');
+            $this->_properties = $this->get('properties');
             if (is_string($this->_properties))
                 $this->_properties = JsonDataParser::parse($this->_properties);
             elseif (!$this->_properties)
@@ -980,20 +1068,16 @@ class TicketStatus  extends VerySimpleModel implements CustomListItem {
         }
 
         if (count($errors) === 0) {
+            if ($properties && is_array($properties))
+                $properties = JsonDataEncoder::encode($properties);
+
+            $this->set('properties', $properties);
             $this->save(true);
-            $this->setProperties($properties);
         }
 
         return count($errors) === 0;
     }
 
-    function setProperties($properties) {
-        if ($properties && is_array($properties))
-            $properties = JsonDataEncoder::encode($properties);
-
-        $this->_config->set('properties', $properties);
-    }
-
     function update($vars, &$errors) {
 
         $fields = array('value' => 'name', 'sort' => 'sort');
@@ -1042,13 +1126,9 @@ class TicketStatus  extends VerySimpleModel implements CustomListItem {
     static function __create($ht, &$error=false) {
         global $ost;
 
-        $properties = JsonDataEncoder::encode($ht['properties']);
-        unset($ht['properties']);
-        if ($status = TicketStatus::create($ht)) {
+        $ht['properties'] = JsonDataEncoder::encode($ht['properties']);
+        if (($status = TicketStatus::create($ht)))
             $status->save(true);
-            $status->_config = new Config('TS.'.$status->getId());
-            $status->_config->set('properties', $properties);
-        }
 
         return $status;
     }
diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php
index 45758c75c052057061c354e77e2c5faef639e4ff..4e6546549f44d7ad520670bb01af8e946534aaea 100644
--- a/include/class.mailfetch.php
+++ b/include/class.mailfetch.php
@@ -721,6 +721,10 @@ class MailFetcher {
 
         $seen = false;
         if (($thread = ThreadEntry::lookupByEmailHeaders($vars, $seen))
+                && ($t=$thread->getTicket())
+                && ($vars['staffId']
+                    || !$t->isClosed()
+                    || $t->isReopenable())
                 && ($message = $thread->postEmail($vars))) {
             if (!$message instanceof ThreadEntry)
                 // Email has been processed previously
diff --git a/include/class.ticket.php b/include/class.ticket.php
index e4ccfd0769ece4d7c5d84b6d9705a9f25b9e1f6a..0dd0759524db699f4645db80604be81dfb0c4195 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -142,8 +142,8 @@ class Ticket {
         return ($this->getReopenDate());
     }
 
-    function isResolved() {
-        return $this->hasState('resolved');
+    function isReopenable() {
+        return $this->getStatus()->isReopenable();
     }
 
     function isClosed() {
@@ -849,7 +849,6 @@ class Ticket {
 
         $ecb = null;
         switch($status->getState()) {
-            case 'resolved':
             case 'closed':
                 $sql.=', closed=NOW(), duedate=NULL ';
                 if ($thisstaff)
@@ -944,11 +943,19 @@ class Ticket {
         return (db_query($sql) && db_affected_rows());
     }
 
-    //set status to open on a closed ticket.
-    function reopen($isanswered=0) {
+    function reopen() {
         global $cfg;
 
-        return $this->setStatus($cfg->getDefaultTicketStatusId());
+        if (!$this->isClosed())
+            return false;
+
+        // Set status to open based on current closed status settings
+        // If the closed status doesn't have configured "reopen" status then use the
+        // the default ticket status.
+        if (!($status=$this->getStatus()->getReopenStatus()))
+            $status = $cfg->getDefaultTicketStatusId();
+
+        return $status ? $this->setStatus($status, 'Reopened') : false;
     }
 
     function onNewTicket($message, $autorespond=true, $alertstaff=true) {
@@ -1154,8 +1161,9 @@ class Ticket {
             }
         }
 
-        // Reopen if NOT open
-        if(!$this->isOpen()) $this->reopen();
+        // Reopen if closed AND reopenable
+        if ($this->isClosed() && $this->isReopenable())
+            $this->reopen();
 
        /**********   double check auto-response  ************/
         if (!($user = $message->getUser()))
@@ -2281,14 +2289,6 @@ class Ticket {
                 .'WHERE ticket.staff_id = ' . db_input($staff->getId()) . ' '
                 . $where
 
-                .'UNION SELECT \'resolved\', count( ticket.ticket_id ) AS tickets '
-                .'FROM ' . TICKET_TABLE . ' ticket '
-                .'INNER JOIN '.TICKET_STATUS_TABLE. ' status
-                    ON (ticket.status_id=status.id
-                            AND status.state=\'resolved\') '
-                .'WHERE 1 '
-                . $where
-
                 .'UNION SELECT \'closed\', count( ticket.ticket_id ) AS tickets '
                 .'FROM ' . TICKET_TABLE . ' ticket '
                 .'INNER JOIN '.TICKET_STATUS_TABLE. ' status
diff --git a/include/class.user.php b/include/class.user.php
index c7c668caf98f5c4b1533b8bdcdc4293d91535d9d..19f83efafb2f825109053e971ba0ea998eb22f19 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -14,7 +14,8 @@
 
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
-require_once(INCLUDE_DIR . 'class.orm.php');
+require_once INCLUDE_DIR . 'class.orm.php';
+require_once INCLUDE_DIR . 'class.util.php';
 
 class UserEmailModel extends VerySimpleModel {
     static $meta = array(
@@ -1089,45 +1090,12 @@ class UserAccountStatus {
 /*
  *  Generic user list.
  */
-class UserList implements  IteratorAggregate, ArrayAccess {
-    private $users;
-
-    function __construct($list = array()) {
-        $this->users = $list;
-    }
-
-    function add($user) {
-        $this->offsetSet(null, $user);
-    }
-
-    function offsetSet($offset, $value) {
-
-        if (is_null($offset))
-            $this->users[] = $value;
-        else
-            $this->users[$offset] = $value;
-    }
-
-    function offsetExists($offset) {
-        return isset($this->users[$offset]);
-    }
-
-    function offsetUnset($offset) {
-        unset($this->users[$offset]);
-    }
-
-    function offsetGet($offset) {
-        return isset($this->users[$offset]) ? $this->users[$offset] : null;
-    }
-
-    function getIterator() {
-        return new ArrayIterator($this->users);
-    }
+class UserList extends ListObject {
 
     function __toString() {
 
         $list = array();
-        foreach($this->users as $user) {
+        foreach($this->storage as $user) {
             if (is_object($user))
                 $list [] = $user->getName();
         }
@@ -1135,6 +1103,7 @@ class UserList implements  IteratorAggregate, ArrayAccess {
         return $list ? implode(', ', $list) : '';
     }
 }
+
 require_once(INCLUDE_DIR . 'class.organization.php');
 User::_inspect();
 UserAccount::_inspect();
diff --git a/include/class.util.php b/include/class.util.php
new file mode 100644
index 0000000000000000000000000000000000000000..8fb9b3967a4ec5cb4e6d2117c58ece03baa1b0fd
--- /dev/null
+++ b/include/class.util.php
@@ -0,0 +1,169 @@
+<?php
+/**
+ * Jared Hancock <jared@osticket.com>
+ * Copyright (c)  2014
+ *
+ * Lightweight implementation of the Python list in PHP. This allows for
+ * treating an array like a simple list of items. The numeric indexes are
+ * automatically updated so that the indeces of the list will alway be from
+ * zero and increasing positively.
+ *
+ * Negative indexes are supported which reference from the end of the list.
+ * Therefore $queue[-1] will refer to the last item in the list.
+ */
+class ListObject implements IteratorAggregate, ArrayAccess, Serializable, Countable {
+
+    protected $storage = array();
+
+    function __construct(array $array=array()) {
+        foreach ($array as $v)
+            $this->storage[] = $v;
+    }
+
+    function append($what) {
+        if (is_array($what))
+            return $this->extend($what);
+
+        $this->storage[] = $what;
+    }
+
+    function add($what) {
+        $this->append($what);
+    }
+
+    function extend($iterable) {
+        foreach ($iterable as $v)
+            $this->storage[] = $v;
+    }
+
+    function insert($i, $value) {
+        array_splice($this->storage, $i, 0, array($value));
+    }
+
+    function remove($value) {
+        if (!($k = $this->index($value)))
+            throw new OutOfRangeException('No such item in the list');
+        unset($this->storage[$k]);
+    }
+
+    function pop($at=false) {
+        if ($at === false)
+            return array_pop($this->storage);
+        elseif (!isset($this->storage[$at]))
+            throw new OutOfRangeException('Index out of range');
+        else {
+            $rv = array_splice($this->storage, $at, 1);
+            return $rv[0];
+        }
+    }
+
+    function slice($offset, $length=null) {
+        return array_slice($this->storage, $offset, $length);
+    }
+
+    function splice($offset, $length=0, $replacement=null) {
+        return array_splice($this->storage, $offset, $length, $replacement);
+    }
+
+    function index($value) {
+        return array_search($this->storage, $value);
+    }
+
+    /**
+     * Sort the list in place.
+     *
+     * Parameters:
+     * $key - (callable|int) A callable function to produce the sort keys
+     *      or one of the SORT_ constants used by the array_multisort
+     *      function
+     * $reverse - (bool) true if the list should be sorted descending
+     */
+    function sort($key=false, $reverse=false) {
+        if (is_callable($key)) {
+            $keys = array_map($key, $this->storage);
+            array_multisort($keys, $this->storage,
+                $reverse ? SORT_DESC : SORT_ASC);
+        }
+        elseif ($key) {
+            array_multisort($this->storage,
+                $reverse ? SORT_DESC : SORT_ASC, $key);
+        }
+        elseif ($reverse) {
+            rsort($this->storage);
+        }
+        else
+            sort($this->storage);
+    }
+
+    function reverse() {
+        return array_reverse($this->storage);
+    }
+
+    function filter($callable) {
+        $new = new static();
+        foreach ($this->storage as $i=>$v)
+            if ($callable($v, $i))
+                $new[] = $v;
+        return $new;
+    }
+
+    // IteratorAggregate
+    function getIterator() {
+        return new ArrayIterator($this->storage);
+    }
+
+    // Countable
+    function count($mode=COUNT_NORMAL) {
+        return count($this->storage, $mode);
+    }
+
+    // ArrayAccess
+    function offsetGet($offset) {
+        if (!is_int($offset))
+            throw new InvalidArgumentException('List indices should be integers');
+        elseif ($offset < 0)
+            $offset += count($this->storage);
+        if (!isset($this->storage[$offset]))
+            throw new OutOfBoundsException('List index out of range');
+        return $this->storage[$offset];
+    }
+    function offsetSet($offset, $value) {
+        if ($offset === null)
+            return $this->storage[] = $value;
+        elseif (!is_int($offset))
+            throw new InvalidArgumentException('List indices should be integers');
+        elseif ($offset < 0)
+            $offset += count($this->storage);
+
+        if (!isset($this->storage[$offset]))
+            throw new OutOfBoundsException('List assignment out of range');
+
+        $this->storage[$offset] = $value;
+    }
+    function offsetExists($offset) {
+        if (!is_int($offset))
+            throw new InvalidArgumentException('List indices should be integers');
+        elseif ($offset < 0)
+            $offset += count($this->storage);
+        return isset($this->storage[$offset]);
+    }
+    function offsetUnset($offset) {
+        if (!is_int($offset))
+            throw new InvalidArgumentException('List indices should be integers');
+        elseif ($offset < 0)
+            $offset += count($this->storage);
+        unset($this->storage[$offset]);
+    }
+
+    // Serializable
+    function serialize() {
+        return serialize($this->storage);
+    }
+    function unserialize($what) {
+        $this->storage = unserialize($what);
+    }
+
+    function __toString() {
+        return '['.implode(', ', $this->storage).']';
+    }
+}
diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php
index 5b13aa8234249fb665267a982d7e97aed0c6533b..c2150b6515931efabfb1410e060b1f480af9da88 100644
--- a/include/client/tickets.inc.php
+++ b/include/client/tickets.inc.php
@@ -62,7 +62,6 @@ $qwhere = sprintf(' WHERE ( ticket.user_id=%d OR collab.user_id=%d )',
 
 $states = array(
         'open' => 'open',
-        'resolved' => 'resolved',
         'closed' => 'closed');
 if($status && isset($states[$status])){
     $qwhere.=' AND status.state='.db_input($states[$status]);
@@ -121,21 +120,14 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting
     <select name="status">
         <option value="">&mdash; <?php echo __('Any Status');?> &mdash;</option>
         <option value="open"
-            <?php echo ($status=='open')?'selected="selected"':'';?>><?php echo _P('ticket-status', 'Open');?> (<?php echo $thisclient->getNumOpenTickets(); ?>)</option>
-        <?php
-        if($thisclient->getNumResolvedTickets()) {
-            ?>
-        <option value="resolved"
-            <?php echo ($status=='resolved')?'selected="selected"':'';?>><?php
-            echo __('Resolved'); ?> (<?php echo $thisclient->getNumResolvedTickets(); ?>)</option>
-        <?php
-        } ?>
-
+            <?php echo ($status=='open') ? 'selected="selected"' : '';?>>
+            <?php echo _P('ticket-status', 'Open');?> (<?php echo $thisclient->getNumOpenTickets(); ?>)</option>
         <?php
         if($thisclient->getNumClosedTickets()) {
             ?>
         <option value="closed"
-            <?php echo ($status=='closed')?'selected="selected"':'';?>><?php echo __('Closed');?> (<?php echo $thisclient->getNumClosedTickets(); ?>)</option>
+            <?php echo ($status=='closed') ? 'selected="selected"' : '';?>>
+            <?php echo __('Closed');?> (<?php echo $thisclient->getNumClosedTickets(); ?>)</option>
         <?php
         } ?>
     </select>
diff --git a/include/client/view.inc.php b/include/client/view.inc.php
index 454cc9e7d80e8977d32803417dc927d8167ef0e7..1867ac467ab9b3ac50496c6f00c63c0abad3d150 100644
--- a/include/client/view.inc.php
+++ b/include/client/view.inc.php
@@ -4,6 +4,10 @@ if(!defined('OSTCLIENTINC') || !$thisclient || !$ticket || !$ticket->checkUserAc
 $info=($_POST && $errors)?Format::htmlchars($_POST):array();
 
 $dept = $ticket->getDept();
+
+if ($ticket->isClosed() && !$ticket->isReopenable())
+    $warn = __('This ticket is marked as closed and cannot be reopened.');
+
 //Making sure we don't leak out internal dept names
 if(!$dept || !$dept->isPublic())
     $dept = $cfg->getDefaultDept();
@@ -146,6 +150,10 @@ if($ticket->getThreadCount() && ($thread=$ticket->getClientThread())) {
 <?php }elseif($warn) { ?>
     <div id="msg_warning"><?php echo $warn; ?></div>
 <?php } ?>
+
+<?php
+
+if (!$ticket->isClosed() || $ticket->isReopenable()) { ?>
 <form id="reply" action="tickets.php?id=<?php echo $ticket->getId(); ?>#reply" name="reply" method="post" enctype="multipart/form-data">
     <?php csrf_token(); ?>
     <h2><?php echo __('Post a Reply');?></h2>
@@ -183,3 +191,5 @@ if($ticket->getThreadCount() && ($thread=$ticket->getClientThread())) {
         <input type="button" value="<?php echo __('Cancel');?>" onClick="history.go(-1)">
     </p>
 </form>
+<?php
+} ?>
diff --git a/include/i18n/en_US/ticket_status.yaml b/include/i18n/en_US/ticket_status.yaml
index 836deb2a5a83c7911c389927e6df5ecb72df9546..1a5d268a405e86f8ba16f8107c9d3861febaddb3 100644
--- a/include/i18n/en_US/ticket_status.yaml
+++ b/include/i18n/en_US/ticket_status.yaml
@@ -5,7 +5,7 @@
 #  id - (int:optional) id number in the database
 #  name - (string) descriptive name of the status
 #  state - (string) Main status of a ticket
-#  (open, resolved, closed, archived, deleted)
+#  (open, closed, archived, deleted)
 #  mode - (bit) access mask (1 - enabled, 2 - internal)
 #  flags - (bit) flags that can be set on a ticket
 #  properties:
@@ -24,14 +24,15 @@
 
 - id: 2
   name: Resolved
-  state: resolved
-  mode: 3
+  state: closed
+  mode: 1
   sort: 2
   flags: 0
   properties:
+    allowreopen: true
+    reopenstatus: 0
     description: >
-        Resolved tickets are closed tickets that can be reopened by the end user. This might be useful
-        when a trigger is used to close resolved tickets with notice sent to end user.
+        Resolved tickets
 
 - id: 3
   name: Closed
@@ -40,8 +41,10 @@
   sort: 3
   flags: 0
   properties:
+    allowreopen: true
+    reopenstatus: 0
     description: >
-        Tickets marked as closed cannot be reopened by the end user. Tickets will still be accessible on client and staff panels.
+        Closed tickets. Tickets will still be accessible on client and staff panels.
 
 - id: 4
   name: Archived
@@ -52,7 +55,7 @@
   properties:
     description: >
         Tickets only adminstratively available but no longer accessible on
-        ticket queues.
+        ticket queues and client panel.
 
 - id: 5
   name: Deleted
diff --git a/include/staff/templates/dynamic-field-config.tmpl.php b/include/staff/templates/dynamic-field-config.tmpl.php
index af257465289439f8748ffc948911be5fc62e2f1c..5b2716e4e1e53d09034af80cdcfd7663b2668e21 100644
--- a/include/staff/templates/dynamic-field-config.tmpl.php
+++ b/include/staff/templates/dynamic-field-config.tmpl.php
@@ -8,10 +8,15 @@
         $form = $field->getConfigurationForm();
         echo $form->getMedia();
         foreach ($form->getFields() as $name=>$f) { ?>
-            <div class="flush-left custom-field">
-            <div class="field-label">
+            <div class="flush-left custom-field" id="field<?php echo $f->getWidget()->id;
+                ?>" <?php if (!$f->isVisible()) echo 'style="display:none;"'; ?>>
+            <div class="field-label <?php if ($f->get('required')) echo 'required'; ?>">
             <label for="<?php echo $f->getWidget()->name; ?>">
-                <?php echo Format::htmlchars($f->get('label')); ?>:</label>
+                <?php echo Format::htmlchars($f->get('label')); ?>:
+      <?php if ($f->get('required')) { ?>
+                <span class="error">*</span>
+      <?php } ?>
+            </label>
             <?php
             if ($f->get('hint')) { ?>
                 <br/><em style="color:gray;display:inline-block"><?php
@@ -21,10 +26,6 @@
             </div><div>
             <?php
             $f->render();
-            if ($f->get('required')) { ?>
-                <font class="error">*</font>
-            <?php
-            }
             ?>
             </div>
             <?php
diff --git a/include/staff/templates/list-item-properties.tmpl.php b/include/staff/templates/list-item-properties.tmpl.php
index 51e26b65a55a3a205011cef58951d11280875619..a8d158f98e5641ade0376c8090fec148fe8ff62f 100644
--- a/include/staff/templates/list-item-properties.tmpl.php
+++ b/include/staff/templates/list-item-properties.tmpl.php
@@ -10,8 +10,12 @@
         $internal = $item->isInternal();
         $form = $item->getConfigurationForm();
         echo $form->getMedia();
-        foreach ($form->getFields() as $f) { ?>
-            <div class="custom-field">
+        foreach ($form->getFields() as $f) {
+            ?>
+            <div class="custom-field" id="field<?php
+                echo $f->getWidget()->id; ?>"
+                <?php
+                if (!$f->isVisible()) echo 'style="display:none;"'; ?>>
             <div class="field-label">
             <label for="<?php echo $f->getWidget()->name; ?>"
                 style="vertical-align:top;padding-top:0.2em">
diff --git a/include/staff/templates/status-options.tmpl.php b/include/staff/templates/status-options.tmpl.php
index a0047553c67e01a6c283317b0e030e41f2ffef68..78372ed09195a50376f55cd34696db6c43af91a6 100644
--- a/include/staff/templates/status-options.tmpl.php
+++ b/include/staff/templates/status-options.tmpl.php
@@ -3,13 +3,8 @@ global $thisstaff, $ticket;
 // Map states to actions
 $actions= array(
         'closed' => array(
-            'icon'  => 'icon-repeat',
-            'action' => 'close',
-            'href' => 'tickets.php'
-            ),
-        'resolved' => array(
             'icon'  => 'icon-ok-circle',
-            'action' => 'resolve',
+            'action' => 'close',
             'href' => 'tickets.php'
             ),
         'open' => array(
@@ -34,8 +29,7 @@ $actions= array(
     <?php
     $states = array('open');
     if ($thisstaff->canCloseTickets())
-        $states = array_merge($states,
-                array('resolved', 'closed'));
+        $states = array_merge($states, array('closed'));
 
     $statusId = $ticket ? $ticket->getStatusId() : 0;
     foreach (TicketStatusList::getStatuses(
diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php
index d677070ed7588a759d75e0fb248ea1412f5e4a10..5ca5cb5436b480e319d5c2b93a522ad036735249 100644
--- a/include/staff/ticket-open.inc.php
+++ b/include/staff/ticket-open.inc.php
@@ -317,7 +317,7 @@ print $response_form->getField('attachments')->render();
                     $statusId = $info['statusId'] ?: $cfg->getDefaultTicketStatusId();
                     $states = array('open');
                     if ($thisstaff->canCloseTickets())
-                        $states = array_merge($states, array('resolved', 'closed'));
+                        $states = array_merge($states, array('closed'));
                     foreach (TicketStatusList::getStatuses(
                                 array('states' => $states)) as $s) {
                         if (!$s->isEnabled()) continue;
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index 1b137738fec15f6baab1e27682c3c6ca49517301..b0dc9ee4aa4ee6cc4f91271c3be2a46adbc5c2d4 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -22,15 +22,27 @@ $lock  = $ticket->getLock();  //Ticket lock obj
 $id    = $ticket->getId();    //Ticket ID.
 
 //Useful warnings and errors the user might want to know!
-if($ticket->isAssigned() && (
-            ($staff && $staff->getId()!=$thisstaff->getId())
-         || ($team && !$team->hasMember($thisstaff))
+if ($ticket->isClosed() && !$ticket->isReopenable())
+    $warn = sprintf(
+            __('Current ticket status (%s) does not allow the end user to reply.'),
+            $ticket->getStatus());
+elseif ($ticket->isAssigned()
+        && (($staff && $staff->getId()!=$thisstaff->getId())
+            || ($team && !$team->hasMember($thisstaff))
         ))
-    $warn.='&nbsp;&nbsp;<span class="Icon assignedTicket">'.sprintf(__('Ticket is assigned to %s'),implode('/', $ticket->getAssignees())).'</span>';
-if(!$errors['err'] && ($lock && $lock->getStaffId()!=$thisstaff->getId()))
-    $errors['err']=sprintf(__('This ticket is currently locked by %s'),$lock->getStaffName());
-if(!$errors['err'] && ($emailBanned=TicketFilter::isBanned($ticket->getEmail())))
-    $errors['err']=__('Email is in banlist! Must be removed before any reply/response');
+    $warn.= sprintf('&nbsp;&nbsp;<span class="Icon assignedTicket">%</span>',
+            sprintf(__('Ticket is assigned to %s'),
+                implode('/', $ticket->getAssignees())
+                ));
+
+if (!$errors['err']) {
+
+    if ($lock && $lock->getStaffId()!=$thisstaff->getId())
+        $errors['err'] = sprintf(__('This ticket is currently locked by %s'),
+                $lock->getStaffName());
+    elseif (($emailBanned=TicketFilter::isBanned($ticket->getEmail())))
+        $errors['err'] = __('Email is in banlist! Must be removed before any reply/response');
+}
 
 $unbannable=($emailBanned) ? BanList::includes($ticket->getEmail()) : false;
 
@@ -201,13 +213,6 @@ if($ticket->isOverdue())
                                         echo sprintf('<li><a href="tickets.php?a=search&status=open&uid=%s"><i class="icon-folder-open-alt icon-fixed-width"></i> %s</a></li>',
                                                 $user->getId(), sprintf(_N('%d Open Ticket', '%d Open Tickets', $open), $open));
 
-                                    if(($resolved=$user->getNumResolvedTickets()))
-                                        echo sprintf('<li><a href="tickets.php?a=search&status=resolved&uid=%d"><i
-                                                class="icon-folder-close-alt icon-fixed-width"></i> %s</a></li>',
-                                                $user->getId(), sprintf(_N('%d Resolved Ticket', '%d Resolved Tickets', $resolved), $resolved));
-
-
-
                                     if(($closed=$user->getNumClosedTickets()))
                                         echo sprintf('<li><a href="tickets.php?a=search&status=closed&uid=%d"><i
                                                 class="icon-folder-close-alt icon-fixed-width"></i> %s</a></li>',
@@ -606,7 +611,7 @@ print $response_form->getField('attachments')->render();
                     $statusId = $info['reply_status_id'] ?: $ticket->getStatusId();
                     $states = array('open');
                     if ($thisstaff->canCloseTickets())
-                        $states = array_merge($states, array('resolved', 'closed'));
+                        $states = array_merge($states, array('closed'));
 
                     foreach (TicketStatusList::getStatuses(
                                 array('states' => $states)) as $s) {
@@ -687,7 +692,7 @@ print $note_form->getField('attachments')->render();
                         $statusId = $info['note_status_id'] ?: $ticket->getStatusId();
                         $states = array('open');
                         if ($thisstaff->canCloseTickets())
-                            $states = array_merge($states, array('resolved', 'closed'));
+                            $states = array_merge($states, array('closed'));
                         foreach (TicketStatusList::getStatuses(
                                     array('states' => $states)) as $s) {
                             if (!$s->isEnabled()) continue;
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index a5ad1dcdda5758228392cd5c45c2652406f48bf8..3b716905e3a12150365c39da98d19458daa2e23c 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -35,10 +35,6 @@ switch(strtolower($_REQUEST['status'])){ //Status is overloaded
 		$results_type=__('Closed Tickets');
         $showassigned=true; //closed by.
         break;
-    case 'resolved':
-        $status='resolved';
-        $showassigned=true;
-        break;
     case 'overdue':
         $status='open';
         $showoverdue=true;
@@ -84,7 +80,6 @@ $qwhere .= ' )';
 //STATUS to states
 $states = array(
     'open' => array('open'),
-    'resolved' => array('resolved'),
     'closed' => array('closed'));
 
 if($status && isset($states[$status])) {
@@ -552,8 +547,7 @@ if ($results) {
                  <option value="">&mdash; <?php echo __('Any Status');?> &mdash;</option>
                 <?php
                 foreach (TicketStatusList::getStatuses(
-                            array('states' =>
-                                array('open', 'resolved', 'closed'))) as $s) {
+                            array('states' => array('open', 'closed'))) as $s) {
                     echo sprintf('<option data-state="%s" value="%d">%s</option>',
                             $s->getState(), $s->getId(), __($s->getName()));
                 }
diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig
index 30a5aca33dc5932c1e09ede64d418368d54367cb..3c66643467933d668c14629d21d672cb2db1eaf3 100644
--- a/include/upgrader/streams/core.sig
+++ b/include/upgrader/streams/core.sig
@@ -1 +1 @@
-03ff59bf35a58a102e9b32ad33c2839f
+b26f29a6bb5dbb3510b057632182d138
diff --git a/include/upgrader/streams/core/03ff59bf-b26f29a6.cleanup.sql b/include/upgrader/streams/core/03ff59bf-b26f29a6.cleanup.sql
new file mode 100644
index 0000000000000000000000000000000000000000..66bc02c564fc09a580cddd5a7f93dacacbf529ec
--- /dev/null
+++ b/include/upgrader/streams/core/03ff59bf-b26f29a6.cleanup.sql
@@ -0,0 +1,7 @@
+DELETE FROM  `%TABLE_PREFIX%config`
+    WHERE  `key` = 'properties' AND  `namespace` LIKE  'TS.%';
+
+DELETE FROM `%TABLE_PREFIX%ticket_status`
+    WHERE `state` = 'resolved';
+
+OPTIMIZE TABLE `%TABLE_PREFIX%ticket_status`;
diff --git a/include/upgrader/streams/core/03ff59bf-b26f29a6.patch.sql b/include/upgrader/streams/core/03ff59bf-b26f29a6.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..162c635587773da7f8c9fa5f0f5612741461f228
--- /dev/null
+++ b/include/upgrader/streams/core/03ff59bf-b26f29a6.patch.sql
@@ -0,0 +1,46 @@
+/**
+ * @version v1.9.4
+ * @signature 519d98cd885f060e220da7b30a6f78ae
+ * @title Add properties filed and drop 'resolved' state
+ *
+ * This patch drops resolved state and any associated statuses
+ *
+ */
+
+-- Move tickets in resolved state to the default closed status
+SET @statusId = (
+        SELECT id FROM  `%TABLE_PREFIX%ticket_status`
+        WHERE  `state` =  'closed' ORDER BY id ASC LIMIT 1);
+
+UPDATE  `%TABLE_PREFIX%ticket` t1
+    LEFT JOIN  `%TABLE_PREFIX%ticket_status` t2
+        ON ( t2.id = t1.status_id AND t2.state="resolved")
+    SET t1.status_id = @statusId;
+
+-- add properties field
+ALTER TABLE  `%TABLE_PREFIX%ticket_status`
+    ADD  `properties` TEXT NOT NULL AFTER  `sort`,
+    DROP  `notes`;
+
+UPDATE `%TABLE_PREFIX%ticket_status` s
+    INNER JOIN `ost_config` c
+        ON(c.namespace = CONCAT('TS.', s.id) AND c.key='properties')
+    SET s.properties = c.value;
+
+--  add default reopen settings to existing closed state statuses
+UPDATE `%TABLE_PREFIX%ticket_status`
+    SET `properties`= INSERT(`properties`, 2, 0, '"allowreopen":true,"reopenstatus":0,')
+    WHERE `state` = 'closed';
+
+-- change thread body text to 16Mb.
+ALTER TABLE  `%TABLE_PREFIX%ticket_thread`
+    CHANGE  `body`  `body` mediumtext NOT NULL;
+
+-- index ext id
+ALTER TABLE  `%TABLE_PREFIX%note`
+    ADD INDEX (`ext_id`);
+
+-- Set new schema signature
+UPDATE `%TABLE_PREFIX%config`
+    SET `value` = '519d98cd885f060e220da7b30a6f78ae'
+    WHERE `key` = 'schema_signature' AND `namespace` = 'core';
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 9389be034598f57f880b0124b14b8b79c31924be..5e3579d5ee8d48e692522b76ed039b0d09a132ae 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -1948,3 +1948,6 @@ table.custom-info td {
     direction: ltr;
     unicode-bidi: embed;
 }
+.required {
+    font-weight: bold;
+}
diff --git a/scp/js/scp.js b/scp/js/scp.js
index aa446f00ac243208b7510f3f0d2d3ea6cbc39a41..d1493f59036b5e762cdf3ccfefa02e96c35e573b 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -372,7 +372,6 @@ var scp_prep = function() {
                 $('select#staffId').removeAttr('disabled');
                 break;
             case 'open':
-            case 'resolved':
                 $('select#staffId')
                 .attr('disabled','disabled')
                 .find('option:first')
diff --git a/scp/tickets.php b/scp/tickets.php
index 4d6e5c1a5fc7b9ebda27c6d12e94c5f3143832d3..9ff8bb455d847841b947eee3b515765b82f42e4b 100644
--- a/scp/tickets.php
+++ b/scp/tickets.php
@@ -426,13 +426,6 @@ if($thisstaff->showAssignedOnly() && $stats['closed']) {
                         ($_REQUEST['status']=='closed'));
 } else {
 
-    if ($stats['resolved'])
-        $nav->addSubMenu(array('desc' => __('Resolved').' ('.number_format($stats['resolved']).')',
-                               'title'=>__('Resolved Tickets'),
-                               'href'=>'tickets.php?status=resolved',
-                               'iconclass'=>'closedTickets'),
-                            ($_REQUEST['status']=='resolved'));
-
     $nav->addSubMenu(array('desc' => __('Closed').' ('.number_format($stats['closed']).')',
                            'title'=>__('Closed Tickets'),
                            'href'=>'tickets.php?status=closed',
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index 4879e53ce514f2aa1f4ba813b1fd967b3ae3f2cb..430a0b1ea44841ca299526a20fab8578bad4c8e0 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -489,7 +489,8 @@ CREATE TABLE `%TABLE_PREFIX%note` (
   `sort` int(11) unsigned NOT NULL DEFAULT 0,
   `created` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
   `updated` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
-  PRIMARY KEY (`id`)
+  PRIMARY KEY (`id`),
+  KEY `ext_id` (`ext_id`)
 ) DEFAULT CHARSET=utf8;
 
 DROP TABLE IF EXISTS `%TABLE_PREFIX%session`;
@@ -681,7 +682,7 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%ticket_status` (
   `mode` int(11) unsigned NOT NULL DEFAULT '0',
   `flags` int(11) unsigned NOT NULL DEFAULT '0',
   `sort` int(11) unsigned NOT NULL DEFAULT '0',
-  `notes` text NOT NULL,
+  `properties` text NOT NULL,
   `created` datetime NOT NULL,
   `updated` datetime NOT NULL,
   PRIMARY KEY (`id`),
@@ -715,7 +716,7 @@ CREATE TABLE `%TABLE_PREFIX%ticket_thread` (
   `poster` varchar(128) NOT NULL default '',
   `source` varchar(32) NOT NULL default '',
   `title` varchar(255),
-  `body` text NOT NULL,
+  `body` mediumtext NOT NULL,
   `format` varchar(16) NOT NULL default 'html',
   `ip_address` varchar(64) NOT NULL default '',
   `created` datetime NOT NULL,