diff --git a/include/ajax.forms.php b/include/ajax.forms.php
index 9ca601e33020d9c0f3ea1e669bbc4d405217df5e..c25570e095754f8e4237de353c817aae8259269e 100644
--- a/include/ajax.forms.php
+++ b/include/ajax.forms.php
@@ -32,7 +32,8 @@ class DynamicFormsAjaxAPI extends AjaxController {
             if (!$form->hasAnyVisibleFields())
                 continue;
             ob_start();
-            $form->getForm($_SESSION[':form-data'])->render(!$client);
+            $form->getForm($_SESSION[':form-data'])->render(!$client, false,
+                    array('mode' => 'create'));
             $html .= ob_get_clean();
             ob_start();
             print $form->getMedia();
diff --git a/include/ajax.orgs.php b/include/ajax.orgs.php
index 9c2a2b207c11ba72e8b6c7fa259670d0b4b6a0d7..d3ffc8b9dec2b994ae95efd133104a4860d308dc 100644
--- a/include/ajax.orgs.php
+++ b/include/ajax.orgs.php
@@ -93,7 +93,7 @@ class OrgsAjaxAPI extends AjaxController {
 
         $errors = array();
         if($org->update($_POST, $errors))
-             Http::response(201, $org->to_json());
+             Http::response(201, $org->to_json(), 'application/json');
 
         $forms = $org->getForms();
 
@@ -162,7 +162,7 @@ class OrgsAjaxAPI extends AjaxController {
             }
 
             if (!$info['error'] && $user && $user->setOrganization($org))
-                Http::response(201, $user->to_json());
+                Http::response(201, $user->to_json(), 'application/json');
             elseif (!$info['error'])
                 $info['error'] = sprintf('%s - %s', __('Unable to add user to the organization'), __('Please try again!'));
 
@@ -230,7 +230,7 @@ class OrgsAjaxAPI extends AjaxController {
         if ($_POST) {
             $form = OrganizationForm::getDefaultForm()->getForm($_POST);
             if (($org = Organization::fromForm($form)))
-                Http::response(201, $org->to_json());
+                Http::response(201, $org->to_json(), 'application/json');
 
             $info = array('error' =>sprintf('%s - %s', __('Error adding organization'), __('Please try again!')));
         }
@@ -274,7 +274,7 @@ class OrgsAjaxAPI extends AjaxController {
             $info += array('title' => __('Organization Lookup'));
 
         if ($_POST && ($org = Organization::lookup($_POST['orgid']))) {
-            Http::response(201, $org->to_json());
+            Http::response(201, $org->to_json(), 'application/json');
         }
 
         ob_start();
diff --git a/include/ajax.users.php b/include/ajax.users.php
index 328b93cf0e1d1167797ad29fcda7a0d6a1b8da8b..459c4654286a5ad2bac46bcf1aba222caaf2f380 100644
--- a/include/ajax.users.php
+++ b/include/ajax.users.php
@@ -168,7 +168,7 @@ class UsersAjaxAPI extends AjaxController {
 
         $errors = array();
         if ($user->updateInfo($_POST, $errors, true) && !$errors)
-             Http::response(201, $user->to_json());
+             Http::response(201, $user->to_json(),  'application/json');
 
         $forms = $user->getForms();
         include(STAFFINC_DIR . 'templates/user.tmpl.php');
@@ -271,7 +271,7 @@ class UsersAjaxAPI extends AjaxController {
     function getUser($id=false) {
 
         if(($user=User::lookup(($id) ? $id : $_REQUEST['id'])))
-           Http::response(201, $user->to_json());
+           Http::response(201, $user->to_json(), 'application/json');
 
         $info = array('error' => sprintf(__('%s: Unknown or invalid ID.'), _N('end user', 'end users', 1)));
 
@@ -297,7 +297,7 @@ class UsersAjaxAPI extends AjaxController {
             $info['title'] = __('Add New User');
             $form = UserForm::getUserForm()->getForm($_POST);
             if (($user = User::fromForm($form)))
-                Http::response(201, $user->to_json());
+                Http::response(201, $user->to_json(), 'application/json');
 
             $info['error'] = sprintf('%s - %s', __('Error adding user'), __('Please try again!'));
         }
@@ -433,7 +433,7 @@ class UsersAjaxAPI extends AjaxController {
             }
 
             if ($org && $user->setOrganization($org))
-                Http::response(201, $org->to_json());
+                Http::response(201, $org->to_json(), 'application/json');
             elseif (! $info['error'])
                 $info['error'] = __('Unable to add user to organization.')
                     .' '.__('Correct any errors below and try again.');
diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index 9e5ece6717585e697b13df71265dfec22bcaff05..4cb03f56efdcc2452a0e55314252e8b587073440 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -1098,16 +1098,18 @@ class DynamicFormEntry extends VerySimpleModel {
         return !$this->_errors;
     }
 
-    function isValidForClient() {
-        $filter = function($f) {
-            return $f->isVisibleToUsers();
+    function isValidForClient($update=false) {
+        $filter = function($f) use($update) {
+            return $update ? $f->isEditableToUsers() :
+                $f->isVisibleToUsers();
         };
         return $this->isValid($filter);
     }
 
-    function isValidForStaff() {
-        $filter = function($f) {
-            return $f->isVisibleToStaff();
+    function isValidForStaff($update=false) {
+        $filter = function($f) use($update) {
+            return $update ? $f->isEditableToStaff() :
+                $f->isVisibleToStaff();
         };
         return $this->isValid($filter);
     }
@@ -1237,6 +1239,15 @@ class DynamicFormEntry extends VerySimpleModel {
         }
     }
 
+    /**
+     * Save the form entry and all associated answers.
+     *
+     */
+
+    function save($refetch=false) {
+        return $this->saveAnswers(null, $refetch);
+    }
+
     /**
      * Save the form entry and all associated answers.
      *
@@ -1244,7 +1255,8 @@ class DynamicFormEntry extends VerySimpleModel {
      * (mixed) FALSE if updated failed, otherwise the number of dirty answers
      * which were save is returned (which may be ZERO).
      */
-    function save($refetch=false) {
+
+    function saveAnswers($isEditable=null, $refetch=false) {
         if (count($this->dirty))
             $this->set('updated', new SqlFunction('NOW'));
 
@@ -1254,12 +1266,12 @@ class DynamicFormEntry extends VerySimpleModel {
         $dirty = 0;
         foreach ($this->getAnswers() as $a) {
             $field = $a->getField();
-
             // Don't save answers for presentation-only fields or fields
-            // which are stored elsewhere
-            if (!$field->hasData() || !$field->isStorable()
-                || $field->isPresentationOnly()
-            ) {
+            // which are stored elsewhere or those which are not editable
+            if (!$field->hasData()
+                    || !$field->isStorable()
+                    || $field->isPresentationOnly()
+                    || ($isEditable && !$isEditable($field))) {
                 continue;
             }
             // Set the entry here so that $field->getClean() can use the
diff --git a/include/class.forms.php b/include/class.forms.php
index c03b22dea807b37223d021a44d5916b598d15109..3976cd8894b6d8f342766fb4165d74ff4ebf524b 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -43,7 +43,7 @@ class Form {
             $this->id = $options['id'];
 
         // Use POST data if source was not specified
-        $this->_source = ($source) ? $source : $_POST;
+        $this->_source = $source ?: $_POST;
     }
 
     function getFormId() {
@@ -89,6 +89,28 @@ class Form {
         return $this->getField($name);
     }
 
+    function hasAnyEnabledFields() {
+        return $this->hasAnyVisibleFields(false);
+    }
+
+    function hasAnyVisibleFields($user=false) {
+        $visible = 0;
+        $isstaff = $user instanceof Staff;
+        foreach ($this->getFields() as $F) {
+            if (!$user) {
+                // Assume hasAnyEnabledFields
+                if ($F->isEnabled())
+                    $visible++;
+            } elseif($isstaff) {
+                if ($F->isVisibleToStaff())
+                    $visible++;
+            } elseif ($F->isVisibleToUsers()) {
+                $visible++;
+            }
+        }
+        return $visible > 0;
+    }
+
     function getTitle() { return $this->title; }
     function getInstructions() { return $this->instructions; }
     function getSource() { return $this->_source; }
@@ -836,7 +858,7 @@ class FormField {
      * Returns an HTML friendly value for the data in the field.
      */
     function display($value) {
-        return Format::htmlchars($this->toString($value));
+        return Format::htmlchars($this->toString($value ?: $this->value));
     }
 
     /**
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 37c80c73c669fa1b9f59dbbebfed6eb563b3fc3d..a3f33f0521c2bb510b7e6a24fd07145c6b14b577 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -3300,22 +3300,25 @@ implements RestrictedAccess, Threadable, Searchable {
         if (!$this->save())
             return false;
 
-	$vars['note'] = ThreadEntryBody::clean($vars['note']);
+        $vars['note'] = ThreadEntryBody::clean($vars['note']);
         if ($vars['note'])
             $this->logNote(_S('Ticket Updated'), $vars['note'], $thisstaff);
 
         // Update dynamic meta-data
-        foreach ($forms as $f) {
-            if ($C = $f->getChanges())
+        foreach ($forms as $form) {
+            if ($C = $form->getChanges())
                 $changes['fields'] = ($changes['fields'] ?: array()) + $C;
             // Drop deleted forms
-            $idx = array_search($f->getId(), $vars['forms']);
+            $idx = array_search($form->getId(), $vars['forms']);
             if ($idx === false) {
-                $f->delete();
+                $form->delete();
             }
             else {
-                $f->set('sort', $idx);
-                $f->save();
+                $form->set('sort', $idx);
+                $form->saveAnswers(function($f) {
+                        return $f->isVisibleToStaff()
+                        && $f->isEditableToStaff(); }
+                        );
             }
         }
 
@@ -3358,7 +3361,10 @@ implements RestrictedAccess, Threadable, Searchable {
                     __($field->getLabel()));
         else {
             if ($field->answer) {
-                if (!$field->save())
+                if (!$field->isEditableToStaff())
+                    $errors['field'] = sprintf(__('%s can not be edited'),
+                            __($field->getLabel()));
+                elseif (!$field->save())
                     $errors['field'] =  __('Unable to update field');
                 $changes['fields'] = array($field->getId() => $changes);
             } else {
diff --git a/include/class.user.php b/include/class.user.php
index 8d6bd1a7bb76f9197d1b6b1844271aec337121d2..36e1df54848c17b7e8de08268135820cb0079ad0 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -359,7 +359,7 @@ implements TemplateVariable, Searchable {
                 'email' => (string) $this->getEmail(),
                 'phone' => (string) $this->getPhoneNumber());
 
-        return JsonDataEncoder::encode($info);
+        return Format::json_encode($info);
     }
 
     function __toString() {
@@ -451,19 +451,20 @@ implements TemplateVariable, Searchable {
         return $vars;
     }
 
-    function getForms($data=null) {
+    function getForms($data=null, $cb=null) {
 
         if (!isset($this->_forms)) {
             $this->_forms = array();
+            $cb = $cb ?: function ($f) use($data) { return ($data); };
             foreach ($this->getDynamicData() as $entry) {
                 $entry->addMissingFields();
-                if(!$data
-                        && ($form = $entry->getDynamicForm())
+                if(($form = $entry->getDynamicForm())
                         && $form->get('type') == 'U' ) {
+
                     foreach ($entry->getFields() as $f) {
-                        if ($f->get('name') == 'name')
+                        if ($f->get('name') == 'name' && !$cb($f))
                             $f->value = $this->getFullName();
-                        elseif ($f->get('name') == 'email')
+                        elseif ($f->get('name') == 'email' && !$cb($f))
                             $f->value = $this->getEmail();
                     }
                 }
@@ -529,17 +530,23 @@ implements TemplateVariable, Searchable {
 
     function updateInfo($vars, &$errors, $staff=false) {
 
+
+        $isEditable = function ($f) use($staff) {
+            return ($staff ? $f->isEditableToStaff() :
+                    $f->isEditableToUsers());
+        };
         $valid = true;
-        $forms = $this->getForms($vars);
+        $forms = $this->getForms($vars, $isEditable);
         foreach ($forms as $entry) {
             $entry->setSource($vars);
-            if ($staff && !$entry->isValidForStaff())
+            if ($staff && !$entry->isValidForStaff(true))
                 $valid = false;
-            elseif (!$staff && !$entry->isValidForClient())
+            elseif (!$staff && !$entry->isValidForClient(true))
                 $valid = false;
             elseif ($entry->getDynamicForm()->get('type') == 'U'
                     && ($f=$entry->getField('email'))
-                    &&  $f->getClean()
+                    && $isEditable($f)
+                    && $f->getClean()
                     && ($u=User::lookup(array('emails__address'=>$f->getClean())))
                     && $u->id != $this->getId()) {
                 $valid = false;
@@ -558,7 +565,7 @@ implements TemplateVariable, Searchable {
         foreach ($forms as $entry) {
             if ($entry->getDynamicForm()->get('type') == 'U') {
                 //  Name field
-                if (($name = $entry->getField('name'))) {
+                if (($name = $entry->getField('name')) && $isEditable($name) ) {
                     $name = $name->getClean();
                     if (is_array($name))
                         $name = implode(', ', $name);
@@ -566,14 +573,15 @@ implements TemplateVariable, Searchable {
                 }
 
                 // Email address field
-                if (($email = $entry->getField('email'))) {
+                if (($email = $entry->getField('email'))
+                        && $isEditable($email)) {
                     $this->default_email->address = $email->getClean();
                     $this->default_email->save();
                 }
             }
 
-            // DynamicFormEntry::save returns the number of answers updated
-            if ($entry->save()) {
+            // DynamicFormEntry::saveAnswers returns the number of answers updated
+            if ($entry->saveAnswers($isEditable)) {
                 $this->updated = SqlFunction::NOW();
             }
         }
diff --git a/include/client/open.inc.php b/include/client/open.inc.php
index 9900e2fe272e71cdc45afda0723f62f660d1504c..021de566acdb8dda91ff475a13b942e161cd650e 100644
--- a/include/client/open.inc.php
+++ b/include/client/open.inc.php
@@ -42,7 +42,7 @@ if ($info['topicId'] && ($topic=Topic::lookup($info['topicId']))) {
         if (!$thisclient) {
             $uform = UserForm::getUserForm()->getForm($_POST);
             if ($_POST) $uform->isValid();
-            $uform->render(false);
+            $uform->render(false, false, array('mode' => 'create'));
         }
         else { ?>
             <tr><td colspan="2"><hr /></td></tr>
diff --git a/include/client/templates/dynamic-form.tmpl.php b/include/client/templates/dynamic-form.tmpl.php
index cfde56a64e9d00c27adc7ed06290c51cb0b69346..e7e017a5999e4b84705ecf0c59de4a4a8a195045 100644
--- a/include/client/templates/dynamic-form.tmpl.php
+++ b/include/client/templates/dynamic-form.tmpl.php
@@ -1,8 +1,11 @@
 <?php
-    // Form headline and deck with a horizontal divider above and an extra
-    // space below.
-    // XXX: Would be nice to handle the decoration with a CSS class
-    ?>
+// Return if no visible fields
+global $thisclient;
+if (!$form->hasAnyVisibleFields($thisclient))
+    return;
+
+$isCreate = (isset($options['mode']) && $options['mode'] == 'create');
+?>
     <tr><td colspan="2"><hr />
     <div class="form-header" style="margin-bottom:0.5em">
     <h3><?php echo Format::htmlchars($form->getTitle()); ?></h3>
@@ -12,13 +15,11 @@
     <?php
     // Form fields, each with corresponding errors follows. Fields marked
     // 'private' are not included in the output for clients
-    global $thisclient;
     foreach ($form->getFields() as $field) {
-        if (isset($options['mode']) && $options['mode'] == 'create') {
+        if ($isCreate) {
             if (!$field->isVisibleToUsers() && !$field->isRequiredForUsers())
                 continue;
-        }
-        elseif (!$field->isVisibleToUsers() && !$field->isEditableToUsers()) {
+        } elseif (!$field->isVisibleToUsers()) {
             continue;
         }
         ?>
@@ -28,7 +29,8 @@
                 <label for="<?php echo $field->getFormName(); ?>"><span class="<?php
                     if ($field->isRequiredForUsers()) echo 'required'; ?>">
                 <?php echo Format::htmlchars($field->getLocal('label')); ?>
-            <?php if ($field->isRequiredForUsers()) { ?>
+            <?php if ($field->isRequiredForUsers() &&
+                    ($field->isEditableToUsers() || $isCreate)) { ?>
                 <span class="error">*</span>
             <?php }
             ?></span><?php
@@ -40,12 +42,22 @@
             <br/>
             <?php
             }
-            $field->render(array('client'=>true));
-            ?></label><?php
-            foreach ($field->errors() as $e) { ?>
-                <div class="error"><?php echo $e; ?></div>
-            <?php }
-            $field->renderExtras(array('client'=>true));
+            if ($field->isEditableToUsers() || $isCreate) {
+                $field->render(array('client'=>true));
+                ?></label><?php
+                foreach ($field->errors() as $e) { ?>
+                    <div class="error"><?php echo $e; ?></div>
+                <?php }
+                $field->renderExtras(array('client'=>true));
+            } else {
+                $val = '';
+                if ($field->value)
+                    $val = $field->display($field->value);
+                elseif (($a=$field->getAnswer()))
+                    $val = $a->display();
+
+                echo sprintf('%s </label>', $val);
+            }
             ?>
             </td>
         </tr>
diff --git a/include/staff/templates/dynamic-form.tmpl.php b/include/staff/templates/dynamic-form.tmpl.php
index ef18c8bd01c5a988f063d80caea823da0f857ca1..00abf34cbb93d08e8d73c1e36ed6beed2e3ef61a 100644
--- a/include/staff/templates/dynamic-form.tmpl.php
+++ b/include/staff/templates/dynamic-form.tmpl.php
@@ -1,6 +1,8 @@
 <?php
-// If the form was removed using the trashcan option, and there was some
-// other validation error, don't render the deleted form the second time
+global $thisstaff;
+
+$isCreate = (isset($options['mode']) && $options['mode'] == 'create');
+
 if (isset($options['entry']) && $options['mode'] == 'edit'
     && $_POST
     && ($_POST['forms'] && !in_array($options['entry']->getId(), $_POST['forms']))
@@ -42,8 +44,6 @@ if (isset($options['entry']) && $options['mode'] == 'edit') { ?>
         try {
             if (!$field->isEnabled())
                 continue;
-            if ($options['mode'] == 'edit' && !$field->isEditableToStaff())
-                continue;
         }
         catch (Exception $e) {
             // Not connected to a DynamicFormField
@@ -60,41 +60,52 @@ if (isset($options['entry']) && $options['mode'] == 'edit') { ?>
                 <?php echo Format::htmlchars($field->getLocal('label')); ?>:</td>
                 <td><div style="position:relative"><?php
             }
-            $field->render($options); ?>
-            <?php if (!$field->isBlockLevel() && $field->isRequiredForStaff()) { ?>
-                <span class="error">*</span>
-            <?php
-            }
-            if ($field->isStorable() && ($a = $field->getAnswer()) && $a->isDeleted()) {
-                ?><a class="action-button float-right danger overlay" title="Delete this data"
-                    href="#delete-answer"
-                    onclick="javascript:if (confirm('<?php echo __('You sure?'); ?>'))
-                        $.ajax({
-                            url: 'ajax.php/form/answer/'
-                                +$(this).data('entryId') + '/' + $(this).data('fieldId'),
-                            type: 'delete',
-                            success: $.proxy(function() {
-                                $(this).closest('tr').fadeOut();
-                            }, this)
-                        });"
-                    data-field-id="<?php echo $field->getAnswer()->get('field_id');
-                ?>" data-entry-id="<?php echo $field->getAnswer()->get('entry_id');
-                ?>"> <i class="icon-trash"></i> </a></div><?php
-            }
-            if ($a && !$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::viewableImages($field->getLocal('hint')); ?></em>
-            <?php
-            }
-            foreach ($field->errors() as $e) { ?>
-                <div class="error"><?php echo Format::htmlchars($e); ?></div>
-            <?php } ?>
+
+            if ($field->isEditableToStaff() || $isCreate) {
+                $field->render($options); ?>
+                <?php if (!$field->isBlockLevel() && $field->isRequiredForStaff()) { ?>
+                    <span class="error">*</span>
+                <?php
+                }
+                if ($field->isStorable() && ($a = $field->getAnswer()) && $a->isDeleted()) {
+                    ?><a class="action-button float-right danger overlay" title="Delete this data"
+                        href="#delete-answer"
+                        onclick="javascript:if (confirm('<?php echo __('You sure?'); ?>'))
+                            $.ajax({
+                                url: 'ajax.php/form/answer/'
+                                    +$(this).data('entryId') + '/' + $(this).data('fieldId'),
+                                type: 'delete',
+                                success: $.proxy(function() {
+                                    $(this).closest('tr').fadeOut();
+                                }, this)
+                            });"
+                        data-field-id="<?php echo $field->getAnswer()->get('field_id');
+                    ?>" data-entry-id="<?php echo $field->getAnswer()->get('entry_id');
+                    ?>"> <i class="icon-trash"></i> </a></div><?php
+                }
+                if ($a && !$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::viewableImages($field->getLocal('hint')); ?></em>
+                <?php
+                }
+                foreach ($field->errors() as $e) { ?>
+                    <div class="error"><?php echo Format::htmlchars($e); ?></div>
+                <?php }
+            } else {
+                $val = '';
+                if ($field->value)
+                    $val = $field->display($field->value);
+                elseif (($a= $field->getAnswer()))
+                    $val = $a->display();
+
+                echo $val;
+            }?>
             </div></td>
         </tr>
     <?php }
diff --git a/include/staff/templates/user-lookup.tmpl.php b/include/staff/templates/user-lookup.tmpl.php
index cfeeaddf65b5ce7640cef5158eac44a642e5c202..f46fd7c99e9e4689183998212220730b6485973f 100644
--- a/include/staff/templates/user-lookup.tmpl.php
+++ b/include/staff/templates/user-lookup.tmpl.php
@@ -83,8 +83,8 @@ if ($user) { ?>
 <form method="post" class="user" action="<?php echo $info['action'] ?: '#users/lookup/form'; ?>">
     <table width="100%" class="fixed">
     <?php
-        if(!$form) $form = UserForm::getInstance();
-        $form->render(true, __('Create New User')); ?>
+        $form = $form ?: UserForm::getInstance();
+        $form->render(true, __('Create New User'), array('mode' => 'create')); ?>
     </table>
     <hr>
     <p class="full-width">
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index 9fd5835b562c73b6ec5ce5e7e9f8b42b5cfc93f5..b1edfb999d04744e673705617233d0b32c3028e3 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -598,7 +598,9 @@ foreach (DynamicFormEntry::forTicket($ticket->getId()) as $form) {
     )));
     $displayed = array();
     foreach($answers as $a) {
-        $displayed[] = array($a->getLocal('label'), $a->display() ?: '<span class="faded">&mdash;' . __('Empty') . '&mdash; </span>', $a->getLocal('id'), ($a->getField() instanceof FileUploadField));
+        if (!$a->getField()->isVisibleToStaff())
+            continue;
+        $displayed[] = $a;
     }
     if (count($displayed) == 0)
         continue;
@@ -609,13 +611,18 @@ foreach (DynamicFormEntry::forTicket($ticket->getId()) as $form) {
     </thead>
     <tbody>
 <?php
-    foreach ($displayed as $stuff) {
-        list($label, $v, $id, $isFile) = $stuff;
+    foreach ($displayed as $a) {
+        $id =  $a->getLocal('id');
+        $label = $a->getLocal('label');
+        $v = $a->display() ?: '<span class="faded">&mdash;' . __('Empty') .  '&mdash; </span>';
+        $field = $a->getField();
+        $isFile = ($field instanceof FileUploadField);
 ?>
         <tr>
             <td width="200"><?php echo Format::htmlchars($label); ?>:</td>
             <td>
-            <?php if ($role->hasPerm(Ticket::PERM_EDIT)) {
+            <?php if ($role->hasPerm(Ticket::PERM_EDIT)
+                    && $field->isEditableToStaff()) {
                     $isEmpty = strpos($v, '&mdash;');
                     if ($isFile && !$isEmpty)
                         echo $v.'<br>'; ?>
@@ -633,7 +640,8 @@ foreach (DynamicFormEntry::forTicket($ticket->getId()) as $form) {
                       echo $v;
                   ?>
               </a>
-            <?php } else {
+            <?php
+            } else {
                 echo $v;
             } ?>
             </td>
diff --git a/scp/js/scp.js b/scp/js/scp.js
index c15d2330fdeeb6c8825ad5a68d11f82db0b3ba0b..025db554c60208d11e7754b1fe758a86f68478c7 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -835,8 +835,9 @@ $.confirm = function(message, title, options) {
 };
 
 $.userLookup = function (url, cb) {
-    $.dialog(url, 201, function (xhr) {
-        var user = $.parseJSON(xhr.responseText);
+    $.dialog(url, 201, function (xhr, user) {
+        if ($.type(user) == 'string')
+            user = $.parseJSON(user);
         if (cb) return cb(user);
     }, {
         onshow: function() { $('#user-search').focus(); }
@@ -844,8 +845,9 @@ $.userLookup = function (url, cb) {
 };
 
 $.orgLookup = function (url, cb) {
-    $.dialog(url, 201, function (xhr) {
-        var org = $.parseJSON(xhr.responseText);
+    $.dialog(url, 201, function (xhr, org) {
+        if ($.type(org) == 'string')
+            org = $.parseJSON(user);
         if (cb) cb(org);
     }, {
         onshow: function() { $('#org-search').focus(); }
diff --git a/tickets.php b/tickets.php
index 560b16f3dc963008e05aa8c610607b82992fee95..93c7da3964d2460a8aadc2045b158c82cc959190 100644
--- a/tickets.php
+++ b/tickets.php
@@ -53,14 +53,17 @@ if ($_POST && is_object($ticket) && $ticket->getId()) {
             foreach ($forms as $form) {
                 $form->filterFields(function($f) { return !$f->isStorable(); });
                 $form->setSource($_POST);
-                if (!$form->isValid())
+                if (!$form->isValidForClient(true))
                     $errors = array_merge($errors, $form->errors());
             }
         }
         if (!$errors) {
-            foreach ($forms as $f) {
-                $changes += $f->getChanges();
-                $f->save();
+            foreach ($forms as $form) {
+                $changes += $form->getChanges();
+                $form->saveAnswers(function ($f) {
+                        return $f->isVisibleToUsers()
+                         && $f->isEditableToUsers(); });
+
             }
             if ($changes) {
               $user = User::lookup($thisclient->getId());
@@ -127,9 +130,9 @@ if($ticket && $ticket->checkUserAccess($thisclient)) {
         $inc = 'edit.inc.php';
         if (!$forms) $forms=DynamicFormEntry::forTicket($ticket->getId());
         // Auto add new fields to the entries
-        foreach ($forms as $f) {
-            $f->filterFields(function($f) { return !$f->isStorable(); });
-            $f->addMissingFields();
+        foreach ($forms as $form) {
+            $form->filterFields(function($f) { return !$f->isStorable(); });
+            $form->addMissingFields();
         }
     }
     else