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/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 4bed6b6fdb00f4273dd06264ccdaf20bd9be1b54..3620cc1e87535dd3c3ead353676009d2554077d3 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_STORABLE         = 0x0002;
+    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,43 @@ 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 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);
     }
 
     /**
diff --git a/include/class.forms.php b/include/class.forms.php
index 7cebfb42fabed8cb04bd9b7f6b8376d1e6c4ef49..3410477d781b8d56b95883e426a0e5b9f41521f4 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -1448,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..33400acbd1d1093cee4855b1827d0918cb821a36 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -791,6 +791,15 @@ 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 addCollaborator($user, $vars, &$errors) {
 
@@ -2221,7 +2230,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 +2465,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 +2780,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 d8759c8fc57500485f266f28221a88f194ce2e33..85c8fc00ab553dbe1b842e174a166e44314e92f0 100644
--- a/include/i18n/en_US/form.yaml
+++ b/include/i18n/en_US/form.yaml
@@ -16,10 +16,15 @@
 #               will be used to retrieve the data from the field.
 #   hint:       Help text shown with the field
 #   flags:      Bit mask for settings & options
-#   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
+#     # 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
@@ -35,10 +40,8 @@
     - type: text # notrans
       name: email # notrans
       label: Email Address
-      required: true
       sort: 1
-      flags: 3
-      edit_mask: 15
+      flags: 0x777A3
       configuration:
         size: 40
         length: 64
@@ -46,25 +49,21 @@
     - type: text # notrans
       name: name # notrans
       label: Full Name
-      required: true
       sort: 2
-      flags: 3
-      edit_mask: 15
+      flags: 0x777A3
       configuration:
         size: 40
         length: 64
     - type: phone # notrans
       name: phone # notrans
       label: Phone Number
-      required: false
       sort: 3
-      flags: 1
+      flags: 0x3301
     - type: memo # notrans
       name: notes
       label: Internal Notes
-      required: false
-      private: true
       sort: 4
+      flags: 0x3001
       configuration:
         rows: 4
         cols: 40
@@ -82,10 +81,8 @@
       type: text # notrans
       name: subject # notrans
       label: Issue Summary
-      required: true
-      edit_mask: 15
       sort: 1
-      flags: 1
+      flags: 0x77721
       configuration:
         size: 40
         length: 50
@@ -94,18 +91,13 @@
       name: message # notrans
       label: Issue Details
       hint: Details on the reason(s) for opening the ticket.
-      required: true
-      edit_mask: 15
       sort: 2
-      flags: 3
+      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
@@ -115,10 +107,8 @@
     - type: text # notrans
       name: name # notrans
       label: Company Name
-      required: true
       sort: 1
-      flags: 1
-      edit_mask: 3
+      flags: 0x471A1
       configuration:
         size: 40
         length: 64
@@ -126,24 +116,22 @@
       name: website # notrans
       label: Website
       sort: 2
-      flags: 1
+      flags: 0x3101
       configuration:
         size: 40
         length: 64
     - type: phone # notrans
       name: phone # notrans
       label: Phone Number
-      required: false
       sort: 3
-      flags: 1
+      flags: 0x3101
       configuration:
         ext: false
     - type: memo # notrans
       name: address
       label: Address
-      required: false
       sort: 4
-      flags: 1
+      flags: 0x3101
       configuration:
         rows: 2
         cols: 40
@@ -157,19 +145,16 @@
     - type: text # notrans
       name: name # notrans
       label: Name
-      required: true
       sort: 1
-      flags: 3
-      edit_mask: 15
+      flags: 0x777A3
       configuration:
         size: 40
         length: 64
     - type: memo
       name: address
       label: Address
-      required: false
       sort: 2
-      flags: 1
+      flags: 0x3301
       configuration:
         rows: 2
         cols: 40
@@ -178,24 +163,21 @@
     - type: phone
       name: phone
       label: Phone
-      required: false
       sort: 3
-      flags: 1
+      flags: 0x3301
     - type: text
       name: website
       label: Website
-      required: false
       sort: 4
-      flags: 1
+      flags: 0x3301
       configuration:
         size: 40
         length: 0
     - type: memo # notrans
       name: notes
       label: Internal Notes
-      required: false
       sort: 5
-      flags: 1
+      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..4efb94a218fc3d789693511306f2bee093020fbb 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()) 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
             }
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