diff --git a/account.php b/account.php
index 97c8b5efdf6ee5812f5bf66979ba10b0915a4dcd..81f542eb2b46ebf8d953d196ddbb083aa3a54aac 100644
--- a/account.php
+++ b/account.php
@@ -58,7 +58,7 @@ elseif ($_POST) {
         $user_form->getField('email')->value = $thisclient->getEmail();
     }
 
-    if (!$user_form->isValid(function($f) { return !$f->get('private'); }))
+    if (!$user_form->isValid(function($f) { return !$f->isVisibleToUsers(); }))
         $errors['err'] = __('Incomplete client information');
     elseif (!$_POST['backend'] && !$_POST['passwd1'])
         $errors['passwd1'] = __('New password is required');
diff --git a/include/Spyc.php b/include/Spyc.php
index 1c0fc31a8c90db69aeaefe4291590fe3c4aea96d..e92a4becea0016682299529c3fe290a44cdd21d2 100644
--- a/include/Spyc.php
+++ b/include/Spyc.php
@@ -617,6 +617,8 @@ class Spyc {
 
     if (is_numeric($value)) {
       if ($value === '0') return 0;
+      if (stripos($value, '0x') === 0)
+        $value = hexdec($value);
       if (rtrim ($value, 0) === $value)
         $value = (float)$value;
       return $value;
diff --git a/include/ajax.forms.php b/include/ajax.forms.php
index e53b91348a4d47b3a0b0bc44b001051f292f22d4..6c35f1bb2fe2055d220c89c583261a189223ddc0 100644
--- a/include/ajax.forms.php
+++ b/include/ajax.forms.php
@@ -48,7 +48,31 @@ class DynamicFormsAjaxAPI extends AjaxController {
     }
 
     function saveFieldConfiguration($field_id) {
-        $field = DynamicFormField::lookup($field_id);
+        if (!($field = DynamicFormField::lookup($field_id)))
+            Http::response(404, 'No such field');
+
+        $DFF = 'DynamicFormField';
+
+        // Capture flags which should remain unchanged
+        $p_mask = $DFF::MASK_MASK_ALL;
+        if ($field->isPrivacyForced()) {
+            $p_mask |= $DFF::FLAG_CLIENT_VIEW | $DFF::FLAG_AGENT_VIEW;
+        }
+        if ($field->isRequirementForced()) {
+            $p_mask |= $DFF::FLAG_CLIENT_REQUIRED | $DFF::FLAG_AGENT_REQUIRED;
+        }
+        if ($field->hasFlag($DFF::FLAG_MASK_DISABLE)) {
+            $p_mask |= $DFF::FLAG_ENABLED;
+        }
+
+        // Capture current state of immutable flags
+        $preserve = $field->flags & $p_mask;
+
+        // Set admin-configured flag states
+        $flags = array_reduce($_POST['flags'],
+            function($a, $b) { return $a | $b; }, 0);
+        $field->flags = $flags | $preserve;
+
         if (!$field->setConfiguration()) {
             include STAFFINC_DIR . 'templates/dynamic-field-config.tmpl.php';
             return;
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index d5d12bea93f519e3b443038fbcdda61f35615566..d8fccf5dc451aa81a673e56627f00e7c82e25854 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -609,7 +609,7 @@ class TicketsAjaxAPI extends AjaxController {
 
         $state = strtolower($status->getState());
 
-        if (!$errors && $ticket->setStatus($status, $_REQUEST['comments'])) {
+        if (!$errors && $ticket->setStatus($status, $_REQUEST['comments'], $errors)) {
 
             if ($state == 'deleted') {
                 $msg = sprintf('%s %s',
@@ -723,13 +723,15 @@ class TicketsAjaxAPI extends AjaxController {
                 if (($ticket=Ticket::lookup($tid))
                         && $ticket->getStatusId() != $status->getId()
                         && $ticket->checkStaffAccess($thisstaff)
-                        && $ticket->setStatus($status, $comments))
+                        && $ticket->setStatus($status, $comments, $errors))
                     $i++;
             }
 
-            if (!$i)
-                $errors['err'] = sprintf(__('Unable to change status for %s'),
+            if (!$i) {
+                $errors['err'] = $errors['err']
+                    ?: sprintf(__('Unable to change status for %s'),
                         _N('the selected ticket', 'any of the selected tickets', $count));
+            }
             else {
                 // Assume success
                 if ($i==$count) {
diff --git a/include/class.auth.php b/include/class.auth.php
index fb055a5c379a706682a48d38b0e5a54e5622ab6d..e6fed10c5089badd4597d9ab220964b717964281 100644
--- a/include/class.auth.php
+++ b/include/class.auth.php
@@ -130,7 +130,7 @@ class ClientCreateRequest {
         if ($bk->supportsInteractiveAuthentication())
             // User can only be authenticated against this backend
             $defaults['backend'] = $bk::$id;
-        if ($this_form->isValid(function($f) { return !$f->get('private'); })
+        if ($this_form->isValid(function($f) { return !$f->isVisibleToUsers(); })
                 && ($U = User::fromVars($this_form->getClean()))
                 && ($acct = ClientAccount::createForUser($U, $defaults))
                 // Confirm and save the account
diff --git a/include/class.config.php b/include/class.config.php
index d47d4144f3e8c084f502a5d9c00f78edabe01648..164171a920a964493fbd24ebe969f814709c65ee 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -156,7 +156,6 @@ class OsticketConfig extends Config {
         'auto_claim_tickets'=>  true,
         'system_language' =>    'en_US',
         'default_storage_bk' => 'D',
-        'allow_client_updates' => false,
         'message_autoresponder_collabs' => true,
         'add_email_collabs' => true,
         'clients_only' => false,
@@ -350,10 +349,6 @@ class OsticketConfig extends Config {
         return $this->get('enable_html_thread');
     }
 
-    function allowClientUpdates() {
-        return $this->get('allow_client_updates');
-    }
-
     function getClientTimeout() {
         return $this->getClientSessionTimeout();
     }
diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index 1ff552bd0d80edd5d56242312e1cc8ff4dab00e2..b582110b0100c933334ed03592e3d2c8f6afddeb 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -423,14 +423,27 @@ class DynamicFormField extends VerySimpleModel {
 
     var $_field;
 
-    const REQUIRE_NOBODY = 0;
-    const REQUIRE_EVERYONE = 1;
-    const REQUIRE_ENDUSER = 2;
-    const REQUIRE_AGENT = 3;
+    const FLAG_ENABLED          = 0x0001;
+    const FLAG_EXT_STORED       = 0x0002; // Value stored outside of form_entry_value
+    const FLAG_CLOSE_REQUIRED   = 0x0004;
 
-    const VISIBLE_EVERYONE = 0;
-    const VISIBLE_AGENTONLY = 1;
-    const VISIBLE_ENDUSERONLY = 2;
+    const FLAG_MASK_CHANGE      = 0x0010;
+    const FLAG_MASK_DELETE      = 0x0020;
+    const FLAG_MASK_EDIT        = 0x0040;
+    const FLAG_MASK_DISABLE     = 0x0080;
+    const FLAG_MASK_REQUIRE     = 0x10000;
+    const FLAG_MASK_VIEW        = 0x20000;
+    const FLAG_MASK_NAME        = 0x40000;
+
+    const MASK_MASK_ALL         = 0x700F0;
+
+    const FLAG_CLIENT_VIEW      = 0x0100;
+    const FLAG_CLIENT_EDIT      = 0x0200;
+    const FLAG_CLIENT_REQUIRED  = 0x0400;
+
+    const FLAG_AGENT_VIEW       = 0x1000;
+    const FLAG_AGENT_EDIT       = 0x2000;
+    const FLAG_AGENT_REQUIRED   = 0x4000;
 
     // Multiple inheritance -- delegate to FormField
     function __call($what, $args) {
@@ -481,24 +494,61 @@ class DynamicFormField extends VerySimpleModel {
     }
 
     function isDeletable() {
-        return (($this->get('edit_mask') & 1) == 0);
+        return !$this->hasFlag(self::FLAG_MASK_DELETE);
     }
     function isNameForced() {
-        return $this->get('edit_mask') & 2;
+        return $this->hasFlag(self::FLAG_MASK_NAME);
     }
     function isPrivacyForced() {
-        return $this->get('edit_mask') & 4;
+        return $this->hasFlag(self::FLAG_MASK_VIEW);
     }
     function isRequirementForced() {
-        return $this->get('edit_mask') & 8;
+        return $this->hasFlag(self::FLAG_MASK_REQUIRE);
     }
 
     function  isChangeable() {
-        return (($this->get('edit_mask') & 16) == 0);
+        return $this->hasFlag(self::FLAG_MASK_CHANGE);
     }
 
     function  isEditable() {
-        return (($this->get('edit_mask') & 32) == 0);
+        return $this->hasFlag(self::FLAG_MASK_EDIT);
+    }
+
+    function hasFlag($flag) {
+        return ($this->flags & $flag) != 0;
+    }
+
+    function getVisibilityDescription() {
+        $F = $this->flags;
+
+        if (!$this->hasFlag(self::FLAG_ENABLED))
+            return __('Disabled');
+
+        $impl = $this->getImpl();
+
+        $hints = array();
+        $VIEW = self::FLAG_CLIENT_VIEW | self::FLAG_AGENT_VIEW;
+        if (($F & $VIEW) == 0) {
+            $hints[] = __('Hidden');
+        }
+        elseif (~$F & self::FLAG_CLIENT_VIEW) {
+            $hints[] = __('Internal');
+        }
+        elseif (~$F & self::FLAG_AGENT_VIEW) {
+            $hints[] = __('For EndUsers Only');
+        }
+        if ($impl->hasData()) {
+            if (~$F & (self::FLAG_CLIENT_REQUIRED | self::FLAG_AGENT_REQUIRED)) {
+                $hints[] = __('Optional');
+            }
+            else {
+                $hints[] = __('Required');
+            }
+            if (!($F & (self::FLAG_CLIENT_EDIT | self::FLAG_AGENT_EDIT))) {
+                $hints[] = __('Immutable');
+            }
+        }
+        return implode(', ', $hints);
     }
     function getTranslateTag($subtag) {
         return _H(sprintf('field.%s.%s', $subtag, $this->id));
@@ -512,19 +562,28 @@ class DynamicFormField extends VerySimpleModel {
     function allRequirementModes() {
         return array(
             'a' => array('desc' => __('Optional'),
-                'private' => self::VISIBLE_EVERYONE, 'required' => self::REQUIRE_NOBODY),
+                'flags' => self::FLAG_CLIENT_VIEW | self::FLAG_AGENT_VIEW
+                    | self::FLAG_CLIENT_EDIT | self::FLAG_AGENT_EDIT),
             'b' => array('desc' => __('Required'),
-                'private' => self::VISIBLE_EVERYONE, 'required' => self::REQUIRE_EVERYONE),
+                'flags' => self::FLAG_CLIENT_VIEW | self::FLAG_AGENT_VIEW
+                    | self::FLAG_CLIENT_EDIT | self::FLAG_AGENT_EDIT
+                    | self::FLAG_CLIENT_REQUIRED | self::FLAG_AGENT_REQUIRED),
             'c' => array('desc' => __('Required for EndUsers'),
-                'private' => self::VISIBLE_EVERYONE, 'required' => self::REQUIRE_ENDUSER),
+                'flags' => self::FLAG_CLIENT_VIEW | self::FLAG_AGENT_VIEW
+                    | self::FLAG_CLIENT_EDIT | self::FLAG_AGENT_EDIT
+                    | self::FLAG_CLIENT_REQUIRED),
             'd' => array('desc' => __('Required for Agents'),
-                'private' => self::VISIBLE_EVERYONE, 'required' => self::REQUIRE_AGENT),
+                'flags' => self::FLAG_CLIENT_VIEW | self::FLAG_AGENT_VIEW
+                    | self::FLAG_CLIENT_EDIT | self::FLAG_AGENT_EDIT
+                    | self::FLAG_AGENT_REQUIRED),
             'e' => array('desc' => __('Internal, Optional'),
-                'private' => self::VISIBLE_AGENTONLY, 'required' => self::REQUIRE_NOBODY),
+                'flags' => self::FLAG_AGENT_VIEW | self::FLAG_AGENT_EDIT),
             'f' => array('desc' => __('Internal, Required'),
-                'private' => self::VISIBLE_AGENTONLY, 'required' => self::REQUIRE_EVERYONE),
+                'flags' => self::FLAG_AGENT_VIEW | self::FLAG_AGENT_EDIT
+                    | self::FLAG_AGENT_REQUIRED),
             'g' => array('desc' => __('For EndUsers Only'),
-                'private' => self::VISIBLE_ENDUSERONLY, 'required' => self::REQUIRE_ENDUSER),
+                'flags' => self::FLAG_CLIENT_VIEW | self::FLAG_CLIENT_EDIT
+                    | self::FLAG_CLIENT_REQUIRED),
         );
     }
 
@@ -533,7 +592,7 @@ class DynamicFormField extends VerySimpleModel {
         if ($this->isPrivacyForced()) {
             // Required to be internal
             foreach ($modes as $m=>$info) {
-                if ($info['private'] != $this->get('private'))
+                if ($info['flags'] & (self::FLAG_CLIENT_VIEW | self::FLAG_AGENT_VIEW))
                     unset($modes[$m]);
             }
         }
@@ -541,47 +600,46 @@ class DynamicFormField extends VerySimpleModel {
         if ($this->isRequirementForced()) {
             // Required to be required
             foreach ($modes as $m=>$info) {
-                if ($info['required'] != $this->get('required'))
+                if ($info['flags'] & (self::FLAG_CLIENT_REQUIRED | self::FLAG_AGENT_REQUIRED))
                     unset($modes[$m]);
             }
         }
         return $modes;
     }
 
-    function getRequirementMode() {
-        foreach ($this->getAllRequirementModes() as $m=>$info) {
-            if ($this->get('private') == $info['private']
-                    && $this->get('required') == $info['required'])
-                return $m;
-        }
-        return false;
-    }
-
     function setRequirementMode($mode) {
         $modes = $this->getAllRequirementModes();
         if (!isset($modes[$mode]))
             return false;
 
         $info = $modes[$mode];
-        $this->set('required', $info['required']);
-        $this->set('private', $info['private']);
+        $this->set('flags', $info['flags']);
     }
 
     function isRequiredForStaff() {
-        return in_array($this->get('required'),
-            array(self::REQUIRE_EVERYONE, self::REQUIRE_AGENT));
+        return $this->hasFlag(self::FLAG_AGENT_REQUIRED);
     }
     function isRequiredForUsers() {
-        return in_array($this->get('required'),
-            array(self::REQUIRE_EVERYONE, self::REQUIRE_ENDUSER));
+        return $this->hasFlag(self::FLAG_CLIENT_REQUIRED);
+    }
+    function isRequiredForClose() {
+        return $this->hasFlag(self::FLAG_CLOSE_REQUIRED);
+    }
+    function isEditableToStaff() {
+        return $this->hasFlag(self::FLAG_ENABLED)
+            && $this->hasFlag(self::FLAG_AGENT_EDIT);
     }
     function isVisibleToStaff() {
-        return in_array($this->get('private'),
-            array(self::VISIBLE_EVERYONE, self::VISIBLE_AGENTONLY));
+        return $this->hasFlag(self::FLAG_ENABLED)
+            && $this->hasFlag(self::FLAG_AGENT_VIEW);
+    }
+    function isEditableToUsers() {
+        return $this->hasFlag(self::FLAG_ENABLED)
+            && $this->hasFlag(self::FLAG_CLIENT_EDIT);
     }
     function isVisibleToUsers() {
-        return in_array($this->get('private'),
-            array(self::VISIBLE_EVERYONE, self::VISIBLE_ENDUSERONLY));
+        return $this->hasFlag(self::FLAG_ENABLED)
+            && $this->hasFlag(self::FLAG_CLIENT_VIEW);
     }
 
     /**
@@ -689,10 +747,12 @@ class DynamicFormEntry extends VerySimpleModel {
                 return $ans;
         return null;
     }
+
     function setAnswer($name, $value, $id=false) {
         foreach ($this->getAnswers() as $ans) {
-            if ($ans->getField()->get('name') == $name) {
-                $ans->getField()->reset();
+            $f = $ans->getField();
+            if ($f->isStorable() && $f->get('name') == $name) {
+                $f->reset();
                 $ans->set('value', $value);
                 if ($id !== false)
                     $ans->set('value_id', $id);
@@ -907,18 +967,8 @@ class DynamicFormEntry extends VerySimpleModel {
                 $this->_fields[] = $fImpl;
                 $this->_form = null;
 
-                // Omit fields without data
-                // 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')))
-                    continue;
-
-                if ($this->object_type == 'O'
-                        && in_array($field->get('name'), array('name')))
-                    continue;
-
-                if (!$field->hasData())
+                // Omit fields without data and non-storable fields.
+                if (!$field->hasData() || !$field->isStorable())
                     continue;
 
                 $a->save();
@@ -937,15 +987,10 @@ class DynamicFormEntry extends VerySimpleModel {
             $this->set('updated', new SqlFunction('NOW'));
         parent::save();
         foreach ($this->getFields() as $field) {
-            $a = $field->getAnswer();
-            if ($this->object_type == 'U'
-                    && in_array($field->get('name'), array('name','email')))
-                continue;
-
-            if ($this->object_type == 'O'
-                    && in_array($field->get('name'), array('name')))
+            if (!$field->isStorable())
                 continue;
 
+            $a = $field->getAnswer();
             // Set the entry ID here so that $field->getClean() can use the
             // entry-id if necessary
             $a->set('entry_id', $this->get('id'));
diff --git a/include/class.forms.php b/include/class.forms.php
index e744f172cc41cd4b3cd073f659da8b2c1b888540..c3d61ea25454a428252841834fbaa5c14ba1d822 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -397,6 +397,18 @@ class FormField {
         return (($this->get('edit_mask') & 32) == 0);
     }
 
+    /**
+     * isStorable
+     *
+     * Indicate if this field data is storable locally (default).Some field's data
+     * might beed to be stored elsewhere for optimization reasons at the
+     * application level.
+     *
+     */
+
+    function isStorable() {
+        return (($this->get('flags') & DynamicFormField::FLAG_EXT_STORED) == 0);
+    }
 
     /**
      * parse
@@ -1436,6 +1448,11 @@ class ThreadEntryField extends FormField {
         if ($cfg->getAllowedFileTypes())
             $fileupload_config['extensions']->set('default', $cfg->getAllowedFileTypes());
 
+        foreach ($fileupload_config as $C) {
+            $C->set('visibility', new VisibilityConstraint(new Q(array(
+                'attachments__eq'=>true,
+            )), VisibilityConstraint::HIDDEN));
+        }
         return array(
             'attachments' => new BooleanField(array(
                 'label'=>__('Enable Attachments'),
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 7caac30d9b936543bcf5df2ff6ed503cc01fc9c9..5bdec7646de19b257870924cdfdcdf90b8bddb6f 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -791,6 +791,35 @@ class Ticket {
         return $this->recipients;
     }
 
+    function hasClientEditableFields() {
+        $forms = DynamicFormEntry::forTicket($this->getId());
+        foreach ($forms as $form) {
+            foreach ($form->getFields() as $field) {
+                if ($field->isEditableToUsers())
+                    return true;
+            }
+        }
+    }
+
+    function getMissingRequiredFields() {
+        $returnArray = array();
+        $forms=DynamicFormEntry::forTicket($this->getId());
+        foreach ($forms as $form) {
+            foreach ($form->getFields() as $field) {
+                if ($field->isRequiredForClose()) {
+                    if (!($field->answer->get('value'))) {
+                        array_push($returnArray, $field->get('label'));
+                    }
+                }
+            }
+        }
+        return $returnArray;
+    }
+
+    function getMissingRequiredField() {
+        $fields = $this->getMissingRequiredFields();
+        return $fields[0];
+    }
 
     function addCollaborator($user, $vars, &$errors) {
 
@@ -948,7 +977,7 @@ class Ticket {
     }
 
     //Status helper.
-    function setStatus($status, $comments='') {
+    function setStatus($status, $comments='', &$errors=array()) {
         global $thisstaff;
 
         if ($status && is_numeric($status))
@@ -970,6 +999,12 @@ class Ticket {
         $ecb = null;
         switch($status->getState()) {
             case 'closed':
+                if ($this->getMissingRequiredFields()) {
+                    $errors['err'] = sprintf(__(
+                        'This ticket is missing data on %s one or more required fields %s and cannot be closed'),
+                    '', '');
+                    return false;
+                }
                 $sql.=', closed=NOW(), lastupdate=NOW(), duedate=NULL ';
                 if ($thisstaff)
                     $sql.=', staff_id='.db_input($thisstaff->getId());
@@ -2221,7 +2256,7 @@ class Ticket {
             if (!in_array($form->getId(), $vars['forms']))
                 continue;
             $form->setSource($_POST);
-            if (!$form->isValid())
+            if (!$form->isValidForStaff())
                 $errors = array_merge($errors, $form->errors());
         }
 
@@ -2456,10 +2491,9 @@ class Ticket {
                 case 'staff':
                     // Required 'Contact Information' fields aren't required
                     // when staff open tickets
-                    return $type != 'user'
-                        || in_array($f->get('name'), array('name','email'));
+                    return $f->isVisibleToStaff();
                 case 'web':
-                    return !$f->get('private');
+                    return $f->isVisibleToUsers();
                 default:
                     return true;
                 }
@@ -2772,6 +2806,13 @@ class Ticket {
         /* -------------------- POST CREATE ------------------------ */
 
         // Save the (common) dynamic form
+        // Ensure we have a subject
+        $subject = $form->getAnswer('subject');
+        if ($subject && !$subject->getValue()) {
+            if ($topic) {
+                $form->setAnswer('subject', $topic->getFullName());
+            }
+        }
         $form->setTicketId($id);
         $form->save();
 
diff --git a/include/client/templates/dynamic-form.tmpl.php b/include/client/templates/dynamic-form.tmpl.php
index 66b957a49100562520e1bb11a765a27d13120d1d..0672b2263829dea6aa804a6345c2c2489ff84c61 100644
--- a/include/client/templates/dynamic-form.tmpl.php
+++ b/include/client/templates/dynamic-form.tmpl.php
@@ -5,7 +5,7 @@
     ?>
     <tr><td colspan="2"><hr />
     <div class="form-header" style="margin-bottom:0.5em">
-    <?php print ($form instanceof DynamicFormEntry) 
+    <?php print ($form instanceof DynamicFormEntry)
         ? $form->getForm()->getMedia() : $form->getMedia(); ?>
     <h3><?php echo Format::htmlchars($form->getTitle()); ?></h3>
     <em><?php echo Format::htmlchars($form->getInstructions()); ?></em>
@@ -16,19 +16,18 @@
     // 'private' are not included in the output for clients
     global $thisclient;
     foreach ($form->getFields() as $field) {
-        if (!$field->isVisibleToUsers())
+        if (!$field->isEditableToUsers())
             continue;
         ?>
         <tr>
             <td colspan="2" style="padding-top:8px;">
             <?php if (!$field->isBlockLevel()) { ?>
                 <label for="<?php echo $field->getFormName(); ?>"><span class="<?php
-                    if ($field->get('required')) echo 'required'; ?>">
+                    if ($field->isRequiredForUsers()) echo 'required'; ?>">
                 <?php echo Format::htmlchars($field->getLocal('label')); ?>
-            <?php if ($field->get('required')) { ?>
+            <?php if ($field->isRequiredForUsers()) { ?>
                 <span class="error">*</span>
-            <?php
-                }
+            <?php }
             ?></span><?php
                 if ($field->get('hint')) { ?>
                     <br /><em style="color:gray;display:inline-block"><?php
@@ -41,8 +40,7 @@
             $field->render('client');
             ?></label><?php
             foreach ($field->errors() as $e) { ?>
-                <br />
-                <font class="error"><?php echo $e; ?></font>
+                <div class="error"><?php echo $e; ?></div>
             <?php }
             $field->renderExtras('client');
             ?>
diff --git a/include/client/view.inc.php b/include/client/view.inc.php
index cd0b2ccd3dd859e1de52acc8df6cf4249c08e57b..6fb76a9d969208436d36b1f072133a24fd368e9c 100644
--- a/include/client/view.inc.php
+++ b/include/client/view.inc.php
@@ -32,12 +32,12 @@ if ($thisclient && $thisclient->isGuest()
         <td colspan="2" width="100%">
             <h1>
                 <?php echo sprintf(__('Ticket #%s'), $ticket->getNumber()); ?> &nbsp;
-                <a href="tickets.php?id=<?php echo $ticket->getId(); ?>" title="Reload"><span class="Icon refresh">&nbsp;</span></a>
-<?php if ($cfg->allowClientUpdates()
+                <a href="tickets.php?id=<?php echo $ticket->getId(); ?>" title="<?php echo __('Reload'); ?>"><span class="Icon refresh">&nbsp;</span></a>
+<?php if ($ticket->hasClientEditableFields()
         // Only ticket owners can edit the ticket details (and other forms)
         && $thisclient->getId() == $ticket->getUserId()) { ?>
                 <a class="action-button pull-right" href="tickets.php?a=edit&id=<?php
-                     echo $ticket->getId(); ?>"><i class="icon-edit"></i> Edit</a>
+                     echo $ticket->getId(); ?>"><i class="icon-edit"></i> <?php echo __('Edit'); ?></a>
 <?php } ?>
             </h1>
         </td>
@@ -88,7 +88,7 @@ foreach (DynamicFormEntry::forTicket($ticket->getId()) as $idx=>$form) {
     <?php foreach ($answers as $answer) {
         if (in_array($answer->getField()->get('name'), array('name', 'email', 'subject')))
             continue;
-        elseif ($answer->getField()->get('private'))
+        elseif (!$answer->getField()->isVisibleToUsers())
             continue;
         ?>
         <tr>
diff --git a/include/i18n/en_US/form.yaml b/include/i18n/en_US/form.yaml
index b5bde9db9af53ae3189a77080db5085e649184cf..85c8fc00ab553dbe1b842e174a166e44314e92f0 100644
--- a/include/i18n/en_US/form.yaml
+++ b/include/i18n/en_US/form.yaml
@@ -15,10 +15,16 @@
 #               useful for page and email templates, where %{ ticket.<name> }
 #               will be used to retrieve the data from the field.
 #   hint:       Help text shown with the field
-#   edit_mask:  Mask out edits to the field (1=>delete, 2=>change name,
-#                   4=>privacy setting, 8=>requirement setting)
-#   private:    True if the field should be hidden from the client
-#   required:   True if entry for the field is required
+#   flags:      Bit mask for settings & options
+#     # From class DynamicFormField
+#     const FLAG_MASK_CHANGE      = 0x0010;     # Type cannot change
+#     const FLAG_MASK_DELETE      = 0x0020;     # Cannot be deleted
+#     const FLAG_MASK_EDIT        = 0x0040;     # Data cannot be edited
+#     const FLAG_MASK_DISABLE     = 0x0080;     # Field cannot be disabled
+#     const FLAG_MASK_REQUIRE     = 0x10000;    # Requirement cannot be changed
+#     const FLAG_MASK_VIEW        = 0x20000;    # View settings cannot be changed
+#     const FLAG_MASK_NAME        = 0x40000;    # Name cannot be changed
+#
 #   configuration: Field-specific configuration
 #     size:     (text) width of the field
 #     length:   (text) maximum size of the data in the field
@@ -34,9 +40,8 @@
     - type: text # notrans
       name: email # notrans
       label: Email Address
-      required: true
       sort: 1
-      edit_mask: 15
+      flags: 0x777A3
       configuration:
         size: 40
         length: 64
@@ -44,23 +49,21 @@
     - type: text # notrans
       name: name # notrans
       label: Full Name
-      required: true
       sort: 2
-      edit_mask: 15
+      flags: 0x777A3
       configuration:
         size: 40
         length: 64
     - type: phone # notrans
       name: phone # notrans
       label: Phone Number
-      required: false
       sort: 3
+      flags: 0x3301
     - type: memo # notrans
       name: notes
       label: Internal Notes
-      required: false
-      private: true
       sort: 4
+      flags: 0x3001
       configuration:
         rows: 4
         cols: 40
@@ -78,9 +81,8 @@
       type: text # notrans
       name: subject # notrans
       label: Issue Summary
-      required: true
-      edit_mask: 15
       sort: 1
+      flags: 0x77721
       configuration:
         size: 40
         length: 50
@@ -89,17 +91,14 @@
       name: message # notrans
       label: Issue Details
       hint: Details on the reason(s) for opening the ticket.
-      required: true
-      edit_mask: 15
       sort: 2
+      flags: 0x75523
     - id: 22
       type: priority # notrans
       name: priority # notrans
       label: Priority Level
-      required: false
-      private: true
-      edit_mask: 3
-      sort: 3
+      sort: 0x430A3
+      flags: 1
 - type: C # notrans
   title: Company Information
   instructions: Details available in email templates
@@ -108,9 +107,8 @@
     - type: text # notrans
       name: name # notrans
       label: Company Name
-      required: true
       sort: 1
-      edit_mask: 3
+      flags: 0x471A1
       configuration:
         size: 40
         length: 64
@@ -118,21 +116,22 @@
       name: website # notrans
       label: Website
       sort: 2
+      flags: 0x3101
       configuration:
         size: 40
         length: 64
     - type: phone # notrans
       name: phone # notrans
       label: Phone Number
-      required: false
       sort: 3
+      flags: 0x3101
       configuration:
         ext: false
     - type: memo # notrans
       name: address
       label: Address
-      required: false
       sort: 4
+      flags: 0x3101
       configuration:
         rows: 2
         cols: 40
@@ -146,17 +145,16 @@
     - type: text # notrans
       name: name # notrans
       label: Name
-      required: true
       sort: 1
-      edit_mask: 15
+      flags: 0x777A3
       configuration:
         size: 40
         length: 64
     - type: memo
       name: address
       label: Address
-      required: false
       sort: 2
+      flags: 0x3301
       configuration:
         rows: 2
         cols: 40
@@ -165,21 +163,21 @@
     - type: phone
       name: phone
       label: Phone
-      required: false
       sort: 3
+      flags: 0x3301
     - type: text
       name: website
       label: Website
-      required: false
       sort: 4
+      flags: 0x3301
       configuration:
         size: 40
         length: 0
     - type: memo # notrans
       name: notes
       label: Internal Notes
-      required: false
       sort: 5
+      flags: 0x3001
       configuration:
         rows: 4
         cols: 40
diff --git a/include/i18n/en_US/list.yaml b/include/i18n/en_US/list.yaml
index 613ca4a2f83db4a46048c70069a9569c95791a86..2dbd2e7a4145fddd34ca064bf50bc7da19a49341 100644
--- a/include/i18n/en_US/list.yaml
+++ b/include/i18n/en_US/list.yaml
@@ -37,17 +37,15 @@
       - type: state # notrans
         name: state # notrans
         label: State
-        required: true
         sort: 1
-        edit_mask: 63
+        flags: 0x770F1
         configuration:
             prompt: State of a ticket
       - type: memo # notrans
         name: description # notrans
         label: Description
-        required: false
         sort: 3
-        edit_mask: 15
+        flags: 0x73021
         configuration:
             rows: 2
             cols: 40
diff --git a/include/staff/dynamic-form.inc.php b/include/staff/dynamic-form.inc.php
index b79a4320d6f190d529cd0e258ad937e3b362581c..bf68e752f874f376c1183e4dde7971b85232565b 100644
--- a/include/staff/dynamic-form.inc.php
+++ b/include/staff/dynamic-form.inc.php
@@ -83,17 +83,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
         $uform = UserForm::objects()->all();
         $ftypes = FormField::allTypes();
         foreach ($uform[0]->getFields() as $f) {
-            if ($f->get('private')) continue;
+            if (!$f->isVisibleToUsers()) continue;
         ?>
         <tr>
             <td></td>
             <td><?php echo $f->get('label'); ?></td>
             <td><?php $t=FormField::getFieldType($f->get('type')); echo __($t[0]); ?></td>
-            <td><?php
-                $rmode = $f->getRequirementMode();
-                $modes = $f->getAllRequirementModes();
-                echo $modes[$rmode]['desc'];
-            ?></td>
+            <td><?php echo $f->getVisibilityDescription(); ?></td>
             <td><?php echo $f->get('name'); ?></td>
             <td><input type="checkbox" disabled="disabled"/></td></tr>
 
@@ -127,7 +123,6 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
         $id = $f->get('id');
         $deletable = !$f->isDeletable() ? 'disabled="disabled"' : '';
         $force_name = $f->isNameForced() ? 'disabled="disabled"' : '';
-        $rmode = $f->getRequirementMode();
         $fi = $f->getImpl();
         $ferrors = $f->errors(); ?>
         <tr>
@@ -162,12 +157,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                     "><i class="icon-edit"></i> <?php echo __('Config'); ?></a>
             <?php } ?></td>
             <td>
-                <select name="visibility-<?php echo $id; ?>">
-<?php foreach ($f->getAllRequirementModes() as $m=>$I) { ?>
-    <option value="<?php echo $m; ?>" <?php if ($rmode == $m)
-         echo 'selected="selected"'; ?>><?php echo $I['desc']; ?></option>
-<?php } ?>
-                <select>
+                <?php echo $f->getVisibilityDescription(); ?>
             </td>
             <td>
                 <input type="text" size="20" name="name-<?php echo $id; ?>"
diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php
index d64c52ed12a4f7e4148598e75d292a93430cbe59..603f43dfa3a7695772a79db0d5fe8688f9e76bd6 100644
--- a/include/staff/settings-tickets.inc.php
+++ b/include/staff/settings-tickets.inc.php
@@ -200,14 +200,6 @@ if(!($maxfileuploads=ini_get('max_file_uploads')))
                 <i class="help-tip icon-question-sign" href="#enable_html_ticket_thread"></i>
             </td>
         </tr>
-        <tr>
-            <td><?php echo __('Allow Client Updates'); ?>:</td>
-            <td>
-                <input type="checkbox" name="allow_client_updates" <?php
-                echo $config['allow_client_updates']?'checked="checked"':''; ?>>
-                <?php echo __('Allow clients to update ticket details via the web portal'); ?>
-            </td>
-        </tr>
         <tr>
             <th colspan="2">
                 <em><b><?php echo __('Attachments');?></b>:  <?php echo __('Size and maximum uploads setting mainly apply to web tickets.');?></em>
diff --git a/include/staff/templates/dynamic-field-config.tmpl.php b/include/staff/templates/dynamic-field-config.tmpl.php
index d9db95352e1c1de6fcaf655d2f3bfb94e0b6d458..65932bf656f9960ed7f80c540316646dd979d881 100644
--- a/include/staff/templates/dynamic-field-config.tmpl.php
+++ b/include/staff/templates/dynamic-field-config.tmpl.php
@@ -3,6 +3,123 @@
     <hr/>
     <form method="post" action="#form/field-config/<?php
             echo $field->get('id'); ?>">
+<ul class="tabs">
+    <li><a href="#config" class="active"><i class="icon-cogs"></i> Field Setup</a></li>
+    <li><a href="#visibility"><i class="icon-beaker"></i> Settings</a></li>
+</ul>
+
+<div class="tab_content" id="visibility" style="display:none">
+    <div>
+    <div class="span4">
+        <div style="margin-bottom:5px"><strong>Enabled</strong>
+        <i class="help-tip icon-question-sign"
+            data-title="Enabled"
+            data-content="This field can be disabled which will remove it
+            from the form for new entries, but will preserve the data on all
+            current entries."></i>
+        </div>
+    </div>
+    <div class="span6">
+    <input type="checkbox" name="flags[]" value="<?php
+            echo DynamicFormField::FLAG_ENABLED; ?>" <?php
+            if ($field->hasFlag(DynamicFormField::FLAG_ENABLED)) echo 'checked="checked"';
+            if ($field->hasFlag(DynamicFormField::FLAG_MASK_DISABLE)) echo ' disabled="disabled"';
+        ?>> Enabled<br/>
+    </div>
+    <hr class="faded"/>
+
+    <div class="span4">
+        <div style="margin-bottom:5px"><strong>Visible</strong>
+        <i class="help-tip icon-question-sign"
+            data-title="Visible"
+            data-content="Making fields <em>visible</em> allows agents and
+            endusers to view and create information in this field."></i>
+        </div>
+    </div>
+    <div class="span3">
+        <input type="checkbox" name="flags[]" value="<?php
+            echo DynamicFormField::FLAG_CLIENT_VIEW; ?>" <?php
+            if ($field->hasFlag(DynamicFormField::FLAG_CLIENT_VIEW)) echo 'checked="checked"';
+            if ($field->isPrivacyForced()) echo ' disabled="disabled"';
+        ?>> For Clients<br/>
+    </div>
+    <div class="span3">
+        <input type="checkbox" name="flags[]" value="<?php
+            echo DynamicFormField::FLAG_AGENT_VIEW; ?>" <?php
+            if ($field->hasFlag(DynamicFormField::FLAG_AGENT_VIEW)) echo 'checked="checked"';
+            if ($field->isPrivacyForced()) echo ' disabled="disabled"';
+        ?>> For Agents<br/>
+    </div>
+
+<?php if ($field->getImpl()->hasData()) { ?>
+    <hr class="faded"/>
+
+    <div class="span4">
+        <div style="margin-bottom:5px"><strong>Required</strong>
+        <i class="help-tip icon-question-sign"
+            data-title="Required"
+            data-content="New entries cannot be created unless all
+            <em>required</em> fields have valid data."></i>
+        </div>
+    </div>
+    <div class="span3">
+        <input type="checkbox" name="flags[]" value="<?php
+            echo DynamicFormField::FLAG_CLIENT_REQUIRED; ?>" <?php
+            if ($field->hasFlag(DynamicFormField::FLAG_CLIENT_REQUIRED)) echo 'checked="checked"';
+            if ($field->isRequirementForced()) echo ' disabled="disabled"';
+        ?>> For Clients<br/>
+    </div>
+    <div class="span3">
+        <input type="checkbox" name="flags[]" value="<?php
+            echo DynamicFormField::FLAG_AGENT_REQUIRED; ?>" <?php
+            if ($field->hasFlag(DynamicFormField::FLAG_AGENT_REQUIRED)) echo 'checked="checked"';
+            if ($field->isRequirementForced()) echo ' disabled="disabled"';
+        ?>> For Agents<br/>
+    </div>
+    <hr class="faded"/>
+
+    <div class="span4">
+        <div style="margin-bottom:5px"><strong>Editable</strong>
+        <i class="help-tip icon-question-sign"
+            data-content="Fields marked editable allow agents and endusers to update the
+            content of this field after the form entry has been created."
+            data-title="Editable"></i>
+        </div>
+    </div>
+
+    <div class="span3">
+        <input type="checkbox" name="flags[]" value="<?php
+            echo DynamicFormField::FLAG_CLIENT_EDIT; ?>" <?php
+            if ($field->hasFlag(DynamicFormField::FLAG_CLIENT_EDIT)) echo 'checked="checked"';
+        ?>> For Clients<br/>
+    </div>
+    <div class="span3">
+        <input type="checkbox" name="flags[]" value="<?php
+            echo DynamicFormField::FLAG_AGENT_EDIT; ?>" <?php
+            if ($field->hasFlag(DynamicFormField::FLAG_AGENT_EDIT)) echo 'checked="checked"';
+        ?>> For Agents<br/>
+    </div>
+    <hr class="faded"/>
+
+    <div class="span4">
+        <div style="margin-bottom:5px"><strong>Data Integrity</strong>
+        <i class="help-tip icon-question-sign"
+            data-title="Required to close a ticket"
+            data-content="Optionally, this field can prevent closing a
+            ticket until it has valid data."></i>
+        </div>
+    </div>
+    <div class="span6">
+        <input type="checkbox" name="flags[]" value="<?php
+            echo DynamicFormField::FLAG_CLOSE_REQUIRED; ?>" <?php
+            if ($field->hasFlag(DynamicFormField::FLAG_CLOSE_REQUIRED)) echo 'checked="checked"';
+        ?>> Required to close a ticket<br/>
+    </div>
+<?php } ?>
+    </div>
+</div>
+
+<div class="tab_content" id="config">
         <?php
         echo csrf_token();
         $form = $field->getConfigurationForm();
@@ -50,6 +167,7 @@
             echo Format::htmlchars($field->get('hint')); ?></textarea>
         </div>
         </div>
+</div>
         <hr>
         <p class="full-width">
             <span class="buttons pull-left">
@@ -62,7 +180,41 @@
          </p>
     </form>
     <div class="clear"></div>
+
 <script type="text/javascript">
    // Make translatable fields translatable
    $('input[data-translate-tag], textarea[data-translate-tag]').translatable();
 </script>
+
+<style type="text/css">
+.span3 {
+    width: 22.25%;
+    margin: 0 1%;
+    display: inline-block;
+    vertical-align: top;
+}
+.span4 {
+    width: 30.25%;
+    margin: 0 1%;
+    display: inline-block;
+    vertical-align: top;
+}
+.span6 {
+    width: 47.25%;
+    margin: 0 1%;
+    display: inline-block;
+    vertical-align: top;
+}
+.span12 {
+    width: 97%;
+    margin: 0 1%;
+    display: inline-block;
+    vertical-align: top;
+}
+.dialog input, .dialog select {
+    margin: 2px;
+}
+hr.faded {
+    opacity: 0.3;
+}
+</style>
diff --git a/include/staff/templates/dynamic-form.tmpl.php b/include/staff/templates/dynamic-form.tmpl.php
index f0db379b8459e9735babff88c969e0a4ad249277..8d725dece67078da3e8fbba1b7578fc84e32d4ab 100644
--- a/include/staff/templates/dynamic-form.tmpl.php
+++ b/include/staff/templates/dynamic-form.tmpl.php
@@ -38,7 +38,7 @@ if (isset($options['entry']) && $options['mode'] == 'edit') { ?>
     }
     foreach ($form->getFields() as $field) {
         try {
-            if (!$field->isVisibleToStaff())
+            if (!$field->isEditableToStaff())
                 continue;
         }
         catch (Exception $e) {
@@ -50,14 +50,14 @@ if (isset($options['entry']) && $options['mode'] == 'edit') { ?>
                 <?php
             }
             else { ?>
-                <td class="multi-line <?php if ($field->get('required')) echo 'required';
+                <td class="multi-line <?php if ($field->isRequiredForStaff() || $field->isRequiredForClose()) echo 'required';
                 ?>" style="min-width:120px;" <?php if ($options['width'])
                     echo "width=\"{$options['width']}\""; ?>>
                 <?php echo Format::htmlchars($field->getLocal('label')); ?>:</td>
                 <td><div style="position:relative"><?php
             }
             $field->render(); ?>
-            <?php if (!$field->isBlockLevel() && $field->get('required')) { ?>
+            <?php if (!$field->isBlockLevel() && $field->isRequiredForStaff()) { ?>
                 <span class="error">*</span>
             <?php
             }
@@ -77,6 +77,12 @@ if (isset($options['entry']) && $options['mode'] == 'edit') { ?>
                 ?>" data-entry-id="<?php echo $field->getAnswer()->get('entry_id');
                 ?>"> <i class="icon-trash"></i> </a></div><?php
             }
+            if (!$a->getValue() && $field->isRequiredForClose()) {
+?><i class="icon-warning-sign help-tip warning"
+    data-title="<?php echo __('Required to close ticket'); ?>"
+    data-content="<?php echo __('Data is required in this field in order to close the related ticket'); ?>"
+/></i><?php
+            }
             if ($field->get('hint') && !$field->isBlockLevel()) { ?>
                 <br /><em style="color:gray;display:inline-block"><?php
                     echo Format::htmlchars($field->getLocal('hint')); ?></em>
diff --git a/include/staff/templates/status-options.tmpl.php b/include/staff/templates/status-options.tmpl.php
index cdcaa395bec9a424a61f6bf62f9b4a29f0ef58b2..c82696e6ff2e052dd84294afb6586233ae7fa5b8 100644
--- a/include/staff/templates/status-options.tmpl.php
+++ b/include/staff/templates/status-options.tmpl.php
@@ -12,6 +12,23 @@ $actions= array(
             'action' => 'reopen'
             ),
         );
+
+$states = array('open');
+if ($thisstaff->canCloseTickets() && (!$ticket || !$ticket->getMissingRequiredFields()))
+    $states = array_merge($states, array('closed'));
+
+$statusId = $ticket ? $ticket->getStatusId() : 0;
+$nextStatuses = array();
+foreach (TicketStatusList::getStatuses(
+            array('states' => $states)) as $status) {
+    if (!isset($actions[$status->getState()])
+            || $statusId == $status->getId())
+        continue;
+    $nextStatuses[] = $status;
+}
+
+if (!$nextStatuses)
+    return;
 ?>
 
 <span
@@ -26,18 +43,7 @@ $actions= array(
 <div id="action-dropdown-statuses"
     class="action-dropdown anchor-right">
     <ul>
-    <?php
-    $states = array('open');
-    if ($thisstaff->canCloseTickets())
-        $states = array_merge($states, array('closed'));
-
-    $statusId = $ticket ? $ticket->getStatusId() : 0;
-    foreach (TicketStatusList::getStatuses(
-                array('states' => $states))->all() as $status) {
-        if (!isset($actions[$status->getState()])
-                || $statusId == $status->getId())
-            continue;
-        ?>
+<?php foreach ($nextStatuses as $status) { ?>
         <li>
             <a class="no-pjax <?php
                 echo $ticket? 'ticket-action' : 'tickets-action'; ?>"
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index 74f819e0d70663c78b1c3d74d4d09e3ae03b1730..207d573c5da4c312a1f7ba9448167cfb2a7dec72 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -605,15 +605,23 @@ print $response_form->getField('attachments')->render();
                 </td>
             </tr>
             <tr>
-                <td width="120">
+                <td width="120" style="vertical-align:top">
                     <label><strong><?php echo __('Ticket Status');?>:</strong></label>
                 </td>
                 <td>
+                    <?php
+                    if ($outstanding = $ticket->getMissingRequiredFields()) { ?>
+                    <div class="warning-banner"><?php echo sprintf(__(
+                        'This ticket is missing data on %s one or more required fields %s and cannot be closed'),
+                        "<a href=\"tickets.php?id={$ticket->getId()}&a=edit\">",
+                        '</a>'
+                    ); ?></div>
+<?php               } ?>
                     <select name="reply_status_id">
                     <?php
                     $statusId = $info['reply_status_id'] ?: $ticket->getStatusId();
                     $states = array('open');
-                    if ($thisstaff->canCloseTickets())
+                    if ($thisstaff->canCloseTickets() && !$outstanding)
                         $states = array_merge($states, array('closed'));
 
                     foreach (TicketStatusList::getStatuses(
diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig
index 595363a70fc0d9b2206f9b3054e4aca2deaa66e6..776ffc839a61c0b88c0387008deb5ea18c453bce 100644
--- a/include/upgrader/streams/core.sig
+++ b/include/upgrader/streams/core.sig
@@ -1 +1 @@
-7c218d81e84b304c1436326c26ace09d
+1ee831c854fe9f35115a3e672916bb91
diff --git a/include/upgrader/streams/core/b26f29a6-7c218d81.cleanup.sql b/include/upgrader/streams/core/b26f29a6-1ee831c8.cleanup.sql
similarity index 86%
rename from include/upgrader/streams/core/b26f29a6-7c218d81.cleanup.sql
rename to include/upgrader/streams/core/b26f29a6-1ee831c8.cleanup.sql
index 38a11ec1a9dff14d79cdfb20ef5c229b8cafd6c0..f56a5cb2c0d24deac939bc08b185cbdcc53119d1 100644
--- a/include/upgrader/streams/core/b26f29a6-7c218d81.cleanup.sql
+++ b/include/upgrader/streams/core/b26f29a6-1ee831c8.cleanup.sql
@@ -1,6 +1,6 @@
 /**
- * @signature d7480e1c31a1f20d6954ecbb342722d3
- * @version v1.9.5
+ * @signature 1ee831c854fe9f35115a3e672916bb91
+ * @version v1.9.6
  * @title Make editable content translatable
  *
  * This patch adds support for translatable administratively editable
diff --git a/include/upgrader/streams/core/b26f29a6-7c218d81.patch.sql b/include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql
similarity index 78%
rename from include/upgrader/streams/core/b26f29a6-7c218d81.patch.sql
rename to include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql
index 4d98136aa645bd97aaf5f32a775cb7e378ddbafe..52d6856b725f2ec369144174172e330bceb9f2b0 100644
--- a/include/upgrader/streams/core/b26f29a6-7c218d81.patch.sql
+++ b/include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql
@@ -1,5 +1,5 @@
 /**
- * @signature 7c218d81e84b304c1436326c26ace09d
+ * @signature 1ee831c854fe9f35115a3e672916bb91
  * @version v1.9.6
  * @title Make editable content translatable and add queues
  *
@@ -172,7 +172,41 @@ CREATE TABLE `%TABLE_PREFIX%queue` (
   primary key (`id`)
 ) DEFAULT CHARSET=utf8;
 
+-- Add flags field to form field
+ALTER TABLE  `%TABLE_PREFIX%form_field`
+    ADD  `flags` INT UNSIGNED NOT NULL DEFAULT  '1' AFTER  `form_id`;
+
+-- Flag field stored in the system elsewhere as nonstorable locally.
+UPDATE `%TABLE_PREFIX%form_field` A1 JOIN `%TABLE_PREFIX%form` A2 ON(A2.id=A1.form_id)
+    SET A1.`flags` = 3
+    WHERE A2.`type` = 'U' AND A1.`name` IN('name','email');
+
+UPDATE `%TABLE_PREFIX%form_field` A1 JOIN `%TABLE_PREFIX%form` A2 ON(A2.id=A1.form_id)
+    SET A1.`flags`=3
+    WHERE A2.`type`='O' AND A1.`name` IN('name');
+
+set @client_edit = (
+    select value from `%TABLE_PREFIX%config` where `key` = 'allow_client_updates');
+
+-- Transfer previous visibility and requirement settings to new flag field
+UPDATE `%TABLE_PREFIX%form_field` SET `flags` = `flags` |
+     CASE WHEN `private` = 0 and @client_edit = 1 THEN CONV(3300, 16, 10)
+          WHEN `private` = 0 and @client_edit = 0 THEN CONV(3100, 16, 10)
+          WHEN `private` = 1 THEN CONV(3000, 16, 10)
+          WHEN `private` = 2 and @client_edit = 1 THEN CONV(300, 16, 10) END
+          WHEN `private` = 2 and @client_edit = 0 THEN CONV(100, 16, 10) END
+   | CASE WHEN `required` = 0 THEN 0
+          WHEN `required` = 1 THEN CONV(4400, 16, 10)
+          WHEN `required` = 2 THEN CONV(400, 16, 10)
+          WHEN `required` = 3 THEN CONV(4000, 16, 10) END
+   | IF(`edit_mask` & 1, CONV(20, 16, 10), 0)
+   | IF(`edit_mask` & 2, CONV(40000, 16, 10), 0)
+   | IF(`edit_mask` & 4, CONV(10000, 16, 10), 0)
+   | IF(`edit_mask` & 8, CONV(20000, 16, 10), 0)
+   | IF(`edit_mask` & 16, CONV(10, 16, 10), 0)
+   | IF(`edit_mask` & 32, CONV(40, 16, 10), 0);
+
 -- Finished with patch
 UPDATE `%TABLE_PREFIX%config`
-    SET `value` = '7c218d81e84b304c1436326c26ace09d'
+    SET `value` = '1ee831c854fe9f35115a3e672916bb91'
     WHERE `key` = 'schema_signature' AND `namespace` = 'core';
diff --git a/include/upgrader/streams/core/b26f29a6-7c218d81.task.php b/include/upgrader/streams/core/b26f29a6-1ee831c8.task.php
similarity index 100%
rename from include/upgrader/streams/core/b26f29a6-7c218d81.task.php
rename to include/upgrader/streams/core/b26f29a6-1ee831c8.task.php
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 6bbdcc8abcd61d9e67e4f5fbf7a19ec4aff505a0..b97e1cc2bcfdc37b4a840095165b790d043311dc 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -2018,7 +2018,8 @@ button a:hover {
     font-style: italic;
 }
 
-.form_table tr:hover i.help-tip {
+.form_table tr:hover i.help-tip,
+.form_table tr i.help-tip.warning {
     opacity: 1;
     color: #ffc20f;
 }
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index b10118f2fb5dc49d4f42b74df3e0e5b01b147bc4..f32ff6d74962ad72ed72886ef9fcefb145d1e039 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -127,6 +127,7 @@ DROP TABLE IF EXISTS `%TABLE_PREFIX%form_field`;
 CREATE TABLE `%TABLE_PREFIX%form_field` (
     `id` int(11) unsigned NOT NULL auto_increment,
     `form_id` int(11) unsigned NOT NULL,
+    `flags` int(10) unsigned DEFAULT 1,
     `type` varchar(255) NOT NULL DEFAULT 'text',
     `label` varchar(255) NOT NULL,
     `required` tinyint(1) NOT NULL DEFAULT 0,
diff --git a/tickets.php b/tickets.php
index 5b15b0015870db4509d07bc3e7f8c7a3ea5b8a1f..2161b67b2658326dcc29a388286a96be077b7e59 100644
--- a/tickets.php
+++ b/tickets.php
@@ -47,8 +47,6 @@ if($_POST && is_object($ticket) && $ticket->getId()):
         if(!$ticket->checkUserAccess($thisclient) //double check perm again!
                 || $thisclient->getId() != $ticket->getUserId())
             $errors['err']=__('Access Denied. Possibly invalid ticket ID');
-        elseif (!$cfg || !$cfg->allowClientUpdates())
-            $errors['err']=__('Access Denied. Client updates are currently disabled');
         else {
             $forms=DynamicFormEntry::forTicket($ticket->getId());
             foreach ($forms as $form) {
@@ -107,7 +105,7 @@ endif;
 $nav->setActiveNav('tickets');
 if($ticket && $ticket->checkUserAccess($thisclient)) {
     if (isset($_REQUEST['a']) && $_REQUEST['a'] == 'edit'
-            && $cfg->allowClientUpdates()) {
+            && $ticket->hasClientEditableFields()) {
         $inc = 'edit.inc.php';
         if (!$forms) $forms=DynamicFormEntry::forTicket($ticket->getId());
         // Auto add new fields to the entries