diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index b8b0407356abab13001ba16987a13660b512e403..039189a2788f365212629b66674c73b5da7a14fe 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -411,6 +411,15 @@ class DynamicFormField extends VerySimpleModel {
 
     var $_field;
 
+    const REQUIRE_NOBODY = 0;
+    const REQUIRE_EVERYONE = 1;
+    const REQUIRE_ENDUSER = 2;
+    const REQUIRE_AGENT = 3;
+
+    const VISIBLE_EVERYONE = 0;
+    const VISIBLE_AGENTONLY = 1;
+    const VISIBLE_ENDUSERONLY = 2;
+
     // Multiple inheritance -- delegate to FormField
     function __call($what, $args) {
         return call_user_func_array(
@@ -480,6 +489,81 @@ class DynamicFormField extends VerySimpleModel {
         return (($this->get('edit_mask') & 32) == 0);
     }
 
+    function allRequirementModes() {
+        return array(
+            'a' => array('desc' => __('Optional'),
+                'private' => self::VISIBLE_EVERYONE, 'required' => self::REQUIRE_NOBODY),
+            'b' => array('desc' => __('Required'),
+                'private' => self::VISIBLE_EVERYONE, 'required' => self::REQUIRE_EVERYONE),
+            'c' => array('desc' => __('Required for EndUsers'),
+                'private' => self::VISIBLE_EVERYONE, 'required' => self::REQUIRE_ENDUSER),
+            'd' => array('desc' => __('Required for Agents'),
+                'private' => self::VISIBLE_EVERYONE, 'required' => self::REQUIRE_AGENT),
+            'e' => array('desc' => __('Internal, Optional'),
+                'private' => self::VISIBLE_AGENTONLY, 'required' => self::REQUIRE_NOBODY),
+            'f' => array('desc' => __('Internal, Required'),
+                'private' => self::VISIBLE_AGENTONLY, 'required' => self::REQUIRE_EVERYONE),
+            'g' => array('desc' => __('For EndUsers Only'),
+                'private' => self::VISIBLE_ENDUSERONLY, 'required' => self::REQUIRE_ENDUSER),
+        );
+    }
+
+    function getAllRequirementModes() {
+        $modes = static::allRequirementModes();
+        if ($this->isPrivacyForced()) {
+            // Required to be internal
+            foreach ($modes as $m=>$info) {
+                if ($info['private'] != $this->get('private'))
+                    unset($modes[$m]);
+            }
+        }
+
+        if ($this->isRequirementForced()) {
+            // Required to be required
+            foreach ($modes as $m=>$info) {
+                if ($info['required'] != $this->get('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']);
+    }
+
+    function isRequiredForStaff() {
+        return in_array($this->get('required'),
+            array(self::REQUIRE_EVERYONE, self::REQUIRE_AGENT));
+    }
+    function isRequiredForUsers() {
+        return in_array($this->get('required'),
+            array(self::REQUIRE_EVERYONE, self::REQUIRE_ENDUSER));
+    }
+    function isVisibleToStaff() {
+        return in_array($this->get('private'),
+            array(self::VISIBLE_EVERYONE, self::VISIBLE_AGENTONLY));
+    }
+    function isVisibleToUsers() {
+        return in_array($this->get('private'),
+            array(self::VISIBLE_EVERYONE, self::VISIBLE_ENDUSERONLY));
+    }
+
     /**
      * Used when updating the form via the admin panel. This represents
      * validation on the form field template, not data entered into a form
@@ -494,9 +578,13 @@ class DynamicFormField extends VerySimpleModel {
         if ($this->get('required') && !$this->get('name'))
             $this->addError(
                 __("Variable name is required for required fields"
-                /* `required` is a flag on fields */
+                /* `required` is a visibility setting fields */
                 /* `variable` is used for automation. Internally it's called `name` */
                 ), "name");
+        if (preg_match('/[.{}\'"`; ]/u', $this->get('name')))
+            $this->addError(__(
+                'Invalid character in variable name. Please use letters and numbers only.'
+            ), 'name');
         return count($this->errors()) == 0;
     }
 
@@ -652,11 +740,16 @@ class DynamicFormEntry extends VerySimpleModel {
     }
 
     function isValidForClient() {
-
         $filter = function($f) {
-            return !$f->get('private');
+            return !$f->isRequiredForUsers();
         };
+        return $this->isValid($filter);
+    }
 
+    function isValidForStaff() {
+        $filter = function($f) {
+            return !$f->isRequiredForStaff();
+        };
         return $this->isValid($filter);
     }
 
diff --git a/include/class.forms.php b/include/class.forms.php
index 7e9b9409efd14f3b0e9591d25e210b2bf21a741f..090ecbb2686fc0f00200abecac1b380f3e23cb4c 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -86,7 +86,7 @@ class Form {
         if (!$this->_clean) {
             $this->_clean = array();
             foreach ($this->getFields() as $key=>$field) {
-                if (!$field->hasData())
+                if ($field->isPresentationOnly())
                     continue;
                 $this->_clean[$key] = $this->_clean[$field->get('name')]
                     = $field->getClean();
@@ -179,6 +179,7 @@ class FormField {
             'choices' => array( /* @trans */ 'Choices', 'ChoiceField'),
             'files' => array(   /* @trans */ 'File Upload', 'FileUploadField'),
             'break' => array(   /* @trans */ 'Section Break', 'SectionBreakField'),
+            'info' => array(    /* @trans */ 'Information', 'FreeTextField'),
         ),
     );
     static $more_types = array();
@@ -1735,7 +1736,9 @@ class TextareaWidget extends Widget {
         if (isset($config['length']) && $config['length'])
             $maxlength = "maxlength=\"{$config['length']}\"";
         if (isset($config['html']) && $config['html']) {
-            $class = 'class="richtext no-bar small"';
+            $class = array('richtext', 'no-bar');
+            $class[] = @$config['size'] ?: 'small';
+            $class = sprintf('class="%s"', implode(' ', $class));
             $this->value = Format::viewableImages($this->value);
         }
         ?>
@@ -2125,6 +2128,42 @@ class FileUploadWidget extends Widget {
 
 class FileUploadError extends Exception {}
 
+class FreeTextField extends FormField {
+    static $widget = 'FreeTextWidget';
+
+    function getConfigurationOptions() {
+        return array(
+            'content' => new TextareaField(array(
+                'configuration' => array('html' => true, 'size'=>'large'),
+                'label'=>__('Content'), 'required'=>true, 'default'=>'',
+                'hint'=>__('Free text shown in the form, such as a disclaimer'),
+            )),
+        );
+    }
+
+    function hasData() {
+        return false;
+    }
+
+    function isBlockLevel() {
+        return true;
+    }
+}
+
+class FreeTextWidget extends Widget {
+    function render($mode=false) {
+        $config = $this->field->getConfiguration();
+        ?><div class=""><h3><?php
+            echo Format::htmlchars($this->field->get('label'));
+        ?></h3><em><?php
+            echo Format::htmlchars($this->field->get('hint'));
+        ?></em><div><?php
+            echo Format::viewableImages($config['content']); ?></div>
+        </div>
+        <?php
+    }
+}
+
 class VisibilityConstraint {
 
     const HIDDEN =      0x0001;
diff --git a/include/class.user.php b/include/class.user.php
index 426ac8eacc4a0eeef6a57194fc952eac4a291c01..1a7317fbaf9ff600d55648c5fe4bf3df165d9cd0 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -194,12 +194,16 @@ class User extends UserModel {
     }
 
     static function fromForm($form) {
+        global $thisstaff;
 
         if(!$form) return null;
 
         //Validate the form
         $valid = true;
-        if (!$form->isValid())
+        $filter = function($f) use ($thisstaff) {
+            return !isset($thisstaff) || $f->isRequiredForStaff();
+        };
+        if (!$form->isValid($filter))
             $valid  = false;
 
         //Make sure the email is not in-use
diff --git a/include/client/templates/dynamic-form.tmpl.php b/include/client/templates/dynamic-form.tmpl.php
index 57f1aa2951aed93e9e50cf6d6868c7e9f7c15041..12b3944075e71ad3788be4be515fadb5478a56e8 100644
--- a/include/client/templates/dynamic-form.tmpl.php
+++ b/include/client/templates/dynamic-form.tmpl.php
@@ -16,7 +16,7 @@
     // 'private' are not included in the output for clients
     global $thisclient;
     foreach ($form->getFields() as $field) {
-        if ($field->get('private'))
+        if (!$field->isVisibleToUsers())
             continue;
         ?>
         <tr>
diff --git a/include/i18n/en_US/help/tips/forms.yaml b/include/i18n/en_US/help/tips/forms.yaml
index 020bb06911a5cf1594a05f79092d9a6d41144221..2731df424f35bedeee82458aa6f3582da22c0f0a 100644
--- a/include/i18n/en_US/help/tips/forms.yaml
+++ b/include/i18n/en_US/help/tips/forms.yaml
@@ -48,21 +48,29 @@ field_type:
       - title: Custom Lists
         href: /scp/lists.php
 
-field_internal:
+field_visibility:
     title: Field Visibility
     content: >
-        Fields marked internal are hidden from your clients. Use internal
-        fields to track things which only your staff need to access.
-
-field_required:
-    title: Data Requirement
-    content: >
-        Forms that have required fields must have valid data before the form
-        can be saved. If checked, forms cannot be submitted or saved until all
-        required fields are satisfied.<br>
-        <br>
-        Internal fields can only be required of staff members, since they
-        are hidden from clients.
+        Choose a visibility and requirement option for this field.
+        <table border="1" cellpadding="2px" cellspacing="0" style="margin-top:7px"
+            ><tbody style="vertical-align:top;">
+            <tr><th>Setting</th>
+                <th>Result</th></tr>
+            <tr><td>Optional</td>
+                <td>Agents and EndUsers can see the field, but neither is required to answer.</td></tr>
+            <tr><td>Required</td>
+                <td>Agents and EndUsers can see the field, and both are required to answer</td></tr>
+            <tr><td>Required for EndUsers</td>
+                <td>Agents and EndUsers can see the field, only EndUsers are required to answer</td></tr>
+            <tr><td>Required for Agents</td>
+                <td>Agents and EndUsers can see the field, only Agents are required to answer</td></tr>
+            <tr><td>Internal, Optional</td>
+                <td>Only Agents can see the field, but no answer is required.</td></tr>
+            <tr><td>Internal, Required</td>
+                <td>Only Agents can see the field, and an answer is required.</td></tr>
+            <tr><td>For EndUsers Only</td>
+                <td>Only EndUsers can see the field, and an answer is required.</td></tr>
+        </tbody></table>
 
 field_variable:
     title: Field Automation
diff --git a/include/staff/dynamic-form.inc.php b/include/staff/dynamic-form.inc.php
index e0bf6a4552b68b761ff0d78efb3fad9502acf4fa..db879cc5f4690b226bc99370382be28b11fd62ec 100644
--- a/include/staff/dynamic-form.inc.php
+++ b/include/staff/dynamic-form.inc.php
@@ -69,8 +69,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
             <th></th>
             <th><?php echo __('Label'); ?></th>
             <th><?php echo __('Type'); ?></th>
-            <th><?php echo __('Internal'); ?></th>
-            <th><?php echo __('Required'); ?></th>
+            <th><?php echo __('Visibility'); ?></th>
             <th><?php echo __('Variable'); ?></th>
             <th><?php echo __('Delete'); ?></th>
         </tr>
@@ -86,9 +85,11 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
             <td></td>
             <td><?php echo $f->get('label'); ?></td>
             <td><?php $t=FormField::getFieldType($f->get('type')); echo __($t[0]); ?></td>
-            <td><input type="checkbox" disabled="disabled"/></td>
-            <td><input type="checkbox" disabled="disabled"
-                <?php echo $f->get('required') ? 'checked="checked"' : ''; ?>/></td>
+            <td><?php
+                $rmode = $f->getRequirementMode();
+                $modes = $f->getAllRequirementModes();
+                echo $modes[$rmode]['desc'];
+            ?></td>
             <td><?php echo $f->get('name'); ?></td>
             <td><input type="checkbox" disabled="disabled"/></td></tr>
 
@@ -109,10 +110,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 <i class="help-tip icon-question-sign" href="#field_label"></i></th>
             <th nowrap><?php echo __('Type'); ?>
                 <i class="help-tip icon-question-sign" href="#field_type"></i></th>
-            <th nowrap><?php echo __('Internal'); ?>
-                <i class="help-tip icon-question-sign" href="#field_internal"></i></th>
-            <th nowrap><?php echo __('Required'); ?>
-                <i class="help-tip icon-question-sign" href="#field_required"></i></th>
+            <th nowrap><?php echo __('Visibility'); ?>
+                <i class="help-tip icon-question-sign" href="#field_visibility"></i></th>
             <th nowrap><?php echo __('Variable'); ?>
                 <i class="help-tip icon-question-sign" href="#field_variable"></i></th>
             <th nowrap><?php echo __('Delete'); ?>
@@ -124,8 +123,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
         $id = $f->get('id');
         $deletable = !$f->isDeletable() ? 'disabled="disabled"' : '';
         $force_name = $f->isNameForced() ? 'disabled="disabled"' : '';
-        $force_privacy = $f->isPrivacyForced() ? 'disabled="disabled"' : '';
-        $force_required = $f->isRequirementForced() ? 'disabled="disabled"' : '';
+        $rmode = $f->getRequirementMode();
         $fi = $f->getImpl();
         $ferrors = $f->errors(); ?>
         <tr>
@@ -157,17 +155,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                         $.dialog($(this).attr('href').substr(1), [201]);
                         return false;
                     "><i class="icon-edit"></i> <?php echo __('Config'); ?></a>
-            <?php } ?>
-            <div class="error" style="white-space:normal"><?php
-                if ($ferrors['type']) echo $ferrors['type'];
-            ?></div>
-            </td>
-            <td><input type="checkbox" name="private-<?php echo $id; ?>"
-                <?php if ($f->get('private')) echo 'checked="checked"'; ?>
-                <?php echo $force_privacy ?>/></td>
-            <td><input type="checkbox" name="required-<?php echo $id; ?>"
-                <?php if ($f->get('required')) echo 'checked="checked"'; ?>
-                <?php echo $force_required ?>/>
+            <?php } ?></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>
             </td>
             <td>
                 <input type="text" size="20" name="name-<?php echo $id; ?>"
@@ -206,12 +201,15 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 </optgroup>
                 <?php } ?>
             </select></td>
-            <td><input type="checkbox" name="private-new-<?php echo $i; ?>"
-            <?php if ($info["private-new-$i"]
-                || (!$_POST && $form && $form->get('type') == 'U'))
-                    echo 'checked="checked"'; ?>/></td>
-            <td><input type="checkbox" name="required-new-<?php echo $i; ?>"
-                <?php if ($info["required-new-$i"]) echo 'checked="checked"'; ?>/></td>
+            <td>
+                <select name="visibility-new-<?php echo $i; ?>">
+<?php
+    $rmode = $info['visibility-new-'.$i];
+    foreach (DynamicFormField::allRequirementModes() as $m=>$I) { ?>
+    <option value="<?php echo $m; ?>" <?php if ($rmode == $m)
+         echo 'selected="selected"'; ?>><?php echo $I['desc']; ?></option>
+<?php } ?>
+                <select>
             <td><input type="text" size="20" name="name-new-<?php echo $i; ?>"
                 value="<?php echo $info["name-new-$i"]; ?>"/>
                 <font class="error"><?php
diff --git a/include/staff/templates/dynamic-form.tmpl.php b/include/staff/templates/dynamic-form.tmpl.php
index 79ebe981bad0798a851d2e61285be0f38613c5d3..611306d1ba831a32f54a1cca0a1d2bfe7b44b7dd 100644
--- a/include/staff/templates/dynamic-form.tmpl.php
+++ b/include/staff/templates/dynamic-form.tmpl.php
@@ -39,6 +39,13 @@ if (isset($options['entry']) && $options['mode'] == 'edit') { ?>
     <?php
     }
     foreach ($form->getFields() as $field) {
+        try {
+            if (!$field->isVisibleToStaff())
+                continue;
+        }
+        catch (Exception $e) {
+            // Not connected to a DynamicFormField
+        }
         ?>
         <tr><?php if ($field->isBlockLevel()) { ?>
                 <td colspan="2">
diff --git a/js/redactor-osticket.js b/js/redactor-osticket.js
index 810fa7f9e98945d6252112e40ad2fe699debe6ed..cded2e1f3ffdc7d425428155eec3cf2a0815777d 100644
--- a/js/redactor-osticket.js
+++ b/js/redactor-osticket.js
@@ -214,7 +214,12 @@ $(function() {
     },
     redact = $.redact = function(el, options) {
         var el = $(el),
-            options = $.extend({
+            sizes = {'small': 75, 'medium': 150, 'large': 225},
+            selectedSize = sizes['medium'];
+        $.each(sizes, function(k, v) {
+            if (el.hasClass(k)) selectedSize = v;
+        });
+        var options = $.extend({
                 'air': el.hasClass('no-bar'),
                 'airButtons': ['formatting', '|', 'bold', 'italic', 'underline', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'image'],
                 'buttons': ['html', '|', 'formatting', '|', 'bold',
@@ -223,7 +228,7 @@ $(function() {
                     'file', 'table', 'link', '|', 'alignment', '|',
                     'horizontalrule'],
                 'autoresize': !el.hasClass('no-bar'),
-                'minHeight': el.hasClass('small') ? 75 : 150,
+                'minHeight': selectedSize,
                 'focus': false,
                 'plugins': [],
                 'imageGetJson': 'ajax.php/draft/images/browse',
diff --git a/scp/forms.php b/scp/forms.php
index dae8ecaf71849f4e3558f2d34f3c181ddad593e1..8b315061435edc03f5f8f4a824e5bff0088bafb4 100644
--- a/scp/forms.php
+++ b/scp/forms.php
@@ -38,10 +38,8 @@ if($_POST) {
                 if (isset($_POST["name-$id"]) && !$field->isNameForced())
                     $field->set('name', $_POST["name-$id"]);
                 # TODO: make sure all help topics still have all required fields
-                if (!$field->isRequirementForced())
-                    $field->set('required', $_POST["required-$id"] == 'on' ?  1 : 0);
-                if (!$field->isPrivacyForced())
-                    $field->set('private', $_POST["private-$id"] == 'on' ?  1 : 0);
+                $field->setRequirementMode($_POST["visibility-$id"]);
+
                 foreach (array('sort','label') as $f) {
                     if (isset($_POST["$f-$id"])) {
                         $field->set($f, $_POST["$f-$id"]);
@@ -49,8 +47,6 @@ if($_POST) {
                 }
                 if (in_array($field->get('name'), $names))
                     $field->addError(__('Field variable name is not unique'), 'name');
-                if (preg_match('/[.{}\'"`; ]/u', $field->get('name')))
-                    $field->addError(__('Invalid character in variable name. Please use letters and numbers only.'), 'name');
                 // Subject (Issue Summary) must always have data
                 if ($form->get('type') == 'T' && $field->get('name') == 'subject') {
                     if (($f = $field->getField(false)->getImpl()) && !$f->hasData())
@@ -115,12 +111,15 @@ if($_POST) {
                 'label'=>$_POST["label-new-$i"],
                 'type'=>$_POST["type-new-$i"],
                 'name'=>$_POST["name-new-$i"],
-                'private'=>$_POST["private-new-$i"] == 'on' ? 1 : 0,
-                'required'=>$_POST["required-new-$i"] == 'on' ? 1 : 0
             ));
+            $field->setRequirementMode($_POST["visibility-new-$i"]);
             $field->setForm($form);
-            if ($field->isValid())
+            if (in_array($field->get('name'), $names))
+                $field->addError(__('Field variable name is not unique'), 'name');
+            if ($field->isValid()) {
                 $form_fields[] = $field;
+                $names[] = $field->get('name');
+            }
             else
                 $errors["new-$i"] = $field->errors();
         }