Skip to content
Snippets Groups Projects
class.forms.php 32.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Jared Hancock's avatar
    Jared Hancock committed
    <?php
    /*********************************************************************
        class.forms.php
    
        osTicket forms framework
    
        Jared Hancock <jared@osticket.com>
        Copyright (c)  2006-2013 osTicket
        http://www.osticket.com
    
        Released under the GNU General Public License WITHOUT ANY WARRANTY.
        See LICENSE.TXT for details.
    
        vim: expandtab sw=4 ts=4 sts=4:
    **********************************************************************/
    
    /**
     * Form template, used for designing the custom form and for entering custom
     * data for a ticket
     */
    class Form {
        var $fields = array();
    
        var $title = 'Unnamed';
    
    Jared Hancock's avatar
    Jared Hancock committed
        var $instructions = '';
    
    
        var $_source = false;
    
    Jared Hancock's avatar
    Jared Hancock committed
        function Form() {
            call_user_func_array(array($this, '__construct'), func_get_args());
        }
    
        function __construct($fields=array(), $source=null, $options=array()) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            $this->fields = $fields;
    
            foreach ($fields as $f)
                $f->setForm($this);
            if (isset($options['title']))
                $this->title = $options['title'];
            if (isset($options['instructions']))
                $this->instructions = $options['instructions'];
            // Use POST data if source was not specified
            $this->_source = ($source) ? $source : $_POST;
    
        function data($source) {
            foreach ($this->fields as $name=>$f)
                if (isset($source[$name]))
                    $f->value = $source[$name];
        }
    
    Jared Hancock's avatar
    Jared Hancock committed
    
        function getFields() {
            return $this->fields;
        }
        function getTitle() { return $this->title; }
        function getInstructions() { return $this->instructions; }
    
        function getSource() { return $this->_source; }
    
        /**
         * Validate the form and indicate if there no errors.
         *
         * Parameters:
         * $filter - (callback) function to receive each field and return
         *      boolean true if the field's errors are significant
         */
        function isValid($include=false) {
    
            if (!is_array($this->_errors)) {
                $this->_errors = array();
                $this->getClean();
                foreach ($this->getFields() as $field)
    
                    if ($field->errors() && (!$include || $include($field)))
    
                        $this->_errors[$field->get('id')] = $field->errors();
            }
            return !$this->_errors;
        }
    
        function getClean() {
            if (!$this->_clean) {
                $this->_clean = array();
    
                foreach ($this->getFields() as $key=>$field) {
                    $this->_clean[$key] = $this->_clean[$field->get('name')]
                        = $field->getClean();
                }
    
            }
            return $this->_clean;
        }
    
        function errors() {
            return $this->_errors;
    
        function render($staff=true, $title=false, $instructions=false) {
            if ($title)
                $this->title = $title;
            if ($instructions)
                $this->instructions = $instructions;
            $form = $this;
            if ($staff)
                include(STAFFINC_DIR . 'templates/dynamic-form.tmpl.php');
            else
                include(CLIENTINC_DIR . 'templates/dynamic-form.tmpl.php');
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    }
    
    require_once(INCLUDE_DIR . "class.json.php");
    
    class FormField {
    
    Jared Hancock's avatar
    Jared Hancock committed
        var $ht = array(
            'label' => 'Unlabeled',
            'required' => false,
            'default' => false,
            'configuration' => array(),
        );
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        var $_cform;
    
        var $_clean;
        var $_errors = array();
    
        var $parent;
    
        var $presentation_only = false;
    
    Jared Hancock's avatar
    Jared Hancock committed
    
        static $types = array(
    
            'Basic Fields' => array(
                'text'  => array('Short Answer', TextboxField),
                'memo' => array('Long Answer', TextareaField),
                'thread' => array('Thread Entry', ThreadEntryField, false),
                'datetime' => array('Date and Time', DatetimeField),
                'phone' => array('Phone Number', PhoneField),
                'bool' => array('Checkbox', BooleanField),
                'choices' => array('Choices', ChoiceField),
                'break' => array('Section Break', SectionBreakField),
            ),
    
    Jared Hancock's avatar
    Jared Hancock committed
        );
        static $more_types = array();
    
        function __construct($options=array()) {
            static $uid = 100;
            $this->ht = array_merge($this->ht, $options);
            if (!isset($this->ht['id']))
                $this->ht['id'] = $uid++;
        }
    
    
        static function addFieldTypes($group, $callable) {
            static::$more_types[$group] = $callable;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        static function allTypes() {
            if (static::$more_types) {
    
                foreach (static::$more_types as $group=>$c)
                    static::$types[$group] = call_user_func($c);
    
    Jared Hancock's avatar
    Jared Hancock committed
                static::$more_types = array();
            }
            return static::$types;
        }
    
    
        static function getFieldType($type) {
            foreach (static::allTypes() as $group=>$types)
                if (isset($types[$type]))
                    return $types[$type];
        }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        function get($what) {
            return $this->ht[$what];
        }
    
        /**
         * getClean
         *
         * Validates and cleans inputs from POST request. This is performed on a
         * field instance, after a DynamicFormSet / DynamicFormSection is
         * submitted via POST, in order to kick off parsing and validation of
         * user-entered data.
         */
        function getClean() {
    
            if (!isset($this->_clean)) {
    
                $this->_clean = (isset($this->value))
                    ? $this->value : $this->parse($this->getWidget()->value);
    
                $this->validateEntry($this->_clean);
            }
            return $this->_clean;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function errors() {
    
            return $this->_errors;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function isValidEntry() {
            $this->validateEntry();
            return count($this->_errors) == 0;
        }
    
        /**
         * validateEntry
         *
         * Validates user entry on an instance of the field on a dynamic form.
         * This is called when an instance of this field (like a TextboxField)
         * receives data from the user and that value should be validated.
         *
         * Parameters:
         * $value - (string) input from the user
         */
        function validateEntry($value) {
    
            if (!$value && count($this->_errors))
                return;
    
    
    Jared Hancock's avatar
    Jared Hancock committed
            # Validates a user-input into an instance of this field on a dynamic
            # form
    
            if ($this->get('required') && !$value && $this->hasData())
                $this->_errors[] = sprintf('%s is a required field', $this->getLabel());
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        /**
         * parse
         *
         * Used to transform user-submitted data to a PHP value. This value is
         * not yet considered valid. The ::validateEntry() method will be called
         * on the value to determine if the entry is valid. Therefore, if the
         * data is clearly invalid, return something like NULL that can easily
         * be deemed invalid in ::validateEntry(), however, can still produce a
         * useful error message indicating what is wrong with the input.
         */
        function parse($value) {
    
            return trim($value);
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        /**
         * to_php
         *
         * Transforms the data from the value stored in the database to a PHP
         * value. The ::to_database() method is used to produce the database
         * valse, so this method is the compliment to ::to_database().
         *
         * Parameters:
         * $value - (string or null) database representation of the field's
         *      content
         */
        function to_php($value) {
            return $value;
        }
    
        /**
         * to_database
         *
         * Determines the value to be stored in the database. The database
         * backend for all fields is a text field, so this method should return
         * a text value or NULL to represent the value of the field. The
         * ::to_php() method will convert this value back to PHP.
         *
         * Paremeters:
         * $value - PHP value of the field's content
         */
        function to_database($value) {
            return $value;
        }
    
        /**
         * toString
         *
         * Converts the PHP value created in ::parse() or ::to_php() to a
         * pretty-printed value to show to the user. This is especially useful
         * for something like dates which are stored considerably different in
         * the database from their respective human-friendly versions.
         * Furthermore, this method allows for internationalization and
         * localization.
         *
         * Parametes:
         * $value - PHP value of the field's content
         */
        function toString($value) {
            return $value;
        }
    
        function getLabel() { return $this->get('label'); }
    
        /**
         * getImpl
         *
         * Magic method that will return an implementation instance of this
         * field based on the simple text value of the 'type' value of this
         * field instance. The list of registered fields is determined by the
         * global get_dynamic_field_types() function. The data from this model
         * will be used to initialize the returned instance.
         *
         * For instance, if the value of this field is 'text', a TextField
         * instance will be returned.
         */
    
        function getImpl($parent=null) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            // Allow registration with ::addFieldTypes and delayed calling
    
            $type = static::getFieldType($this->get('type'));
            $clazz = $type[1];
    
    Jared Hancock's avatar
    Jared Hancock committed
            $inst = new $clazz($this->ht);
            $inst->parent = $parent;
    
            $inst->setForm($this->_form);
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $inst;
    
        function __call($what, $args) {
            // XXX: Throw exception if $this->parent is not set
    
            if (!$this->parent)
                throw new Exception($what.': Call to undefined function');
    
    Jared Hancock's avatar
    Jared Hancock committed
            // BEWARE: DynamicFormField has a __call() which will create a new
            //      FormField instance and invoke __call() on it or bounce
            //      immediately back
    
            return call_user_func_array(
                array($this->parent, $what), $args);
        }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        function getAnswer() { return $this->answer; }
    
        function getFormName() {
    
            if (is_numeric($this->get('id')))
    
                return substr(md5(
                    session_id() . '-field-id-'.$this->get('id')), -16);
    
            else
                return $this->get('id');
    
        function setForm($form) {
            $this->_form = $form;
        }
        function getForm() {
            return $this->_form;
        }
        /**
         * Returns the data source for this field. If created from a form, the
         * data source from the form is returned. Otherwise, if the request is a
         * POST, then _POST is returned.
         */
        function getSource() {
            if ($this->_form)
                return $this->_form->getSource();
            elseif ($_SERVER['REQUEST_METHOD'] == 'POST')
                return $_POST;
            else
                return array();
        }
    
    
        function render($mode=null) {
            $this->getWidget()->render($mode);
        }
    
        function renderExtras($mode=null) {
            return;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getConfigurationOptions() {
            return array();
        }
    
        /**
         * getConfiguration
         *
         * Loads configuration information from database into hashtable format.
         * Also, the defaults from ::getConfigurationOptions() are integrated
         * into the database-backed options, so that if options have not yet
         * been set or a new option has been added and not saved for this field,
         * the default value will be reflected in the returned configuration.
         */
        function getConfiguration() {
            if (!$this->_config) {
                $this->_config = $this->get('configuration');
                if (is_string($this->_config))
                    $this->_config = JsonDataParser::parse($this->_config);
                elseif (!$this->_config)
                    $this->_config = array();
                foreach ($this->getConfigurationOptions() as $name=>$field)
                    if (!isset($this->_config[$name]))
                        $this->_config[$name] = $field->get('default');
            }
            return $this->_config;
        }
    
    
        /**
         * If the [Config] button should be shown to allow for the configuration
         * of this field
         */
    
    Jared Hancock's avatar
    Jared Hancock committed
        function isConfigurable() {
            return true;
        }
    
    
        /**
         * Field type is changeable in the admin interface
         */
        function isChangeable() {
            return true;
        }
    
        /**
         * Field does not contain data that should be saved to the database. Ie.
         * non data fields like section headers
         */
        function hasData() {
            return true;
        }
    
        /**
         * Returns true if the field/widget should be rendered as an entire
         * block in the target form.
         */
        function isBlockLevel() {
            return false;
        }
    
        /**
         * Fields should not be saved with the dynamic data. It is assumed that
         * some static processing will store the data elsewhere.
         */
        function isPresentationOnly() {
    
            return $this->presentation_only;
    
    Jared Hancock's avatar
    Jared Hancock committed
        function getConfigurationForm() {
            if (!$this->_cform) {
    
                $type = static::getFieldType($this->get('type'));
                $clazz = $type[1];
    
    Jared Hancock's avatar
    Jared Hancock committed
                $T = new $clazz();
                $this->_cform = $T->getConfigurationOptions();
            }
            return $this->_cform;
        }
    
    
        function getWidget() {
            if (!static::$widget)
                throw new Exception('Widget not defined for this field');
    
            if (!isset($this->_widget)) {
    
                $this->_widget = new static::$widget($this);
    
                $this->_widget->parseValue();
            }
            return $this->_widget;
    
    Jared Hancock's avatar
    Jared Hancock committed
    }
    
    class TextboxField extends FormField {
    
        static $widget = 'TextboxWidget';
    
    Jared Hancock's avatar
    Jared Hancock committed
    
        function getConfigurationOptions() {
            return array(
                'size'  =>  new TextboxField(array(
                    'id'=>1, 'label'=>'Size', 'required'=>false, 'default'=>16,
                        'validator' => 'number')),
                'length' => new TextboxField(array(
                    'id'=>2, 'label'=>'Max Length', 'required'=>false, 'default'=>30,
                        'validator' => 'number')),
                'validator' => new ChoiceField(array(
                    'id'=>3, 'label'=>'Validator', 'required'=>false, 'default'=>'',
                    'choices' => array('phone'=>'Phone Number','email'=>'Email Address',
                        'ip'=>'IP Address', 'number'=>'Number', ''=>'None'))),
    
                'validator-error' => new TextboxField(array(
                    'id'=>4, 'label'=>'Validation Error', 'default'=>'',
    
                    'configuration'=>array('size'=>40, 'length'=>60),
    
                    'hint'=>'Message shown to user if the input does not match the validator')),
    
    Jared Hancock's avatar
    Jared Hancock committed
            );
        }
    
        function validateEntry($value) {
            parent::validateEntry($value);
    
            $config = $this->getConfiguration();
    
    Jared Hancock's avatar
    Jared Hancock committed
            $validators = array(
                '' =>       null,
                'email' =>  array(array('Validator', 'is_email'),
                    'Enter a valid email address'),
                'phone' =>  array(array('Validator', 'is_phone'),
                    'Enter a valid phone number'),
                'ip' =>     array(array('Validator', 'is_ip'),
                    'Enter a valid IP address'),
                'number' => array('is_numeric', 'Enter a number')
            );
            // Support configuration forms, as well as GUI-based form fields
            $valid = $this->get('validator');
            if (!$valid) {
                $valid = $config['validator'];
            }
    
            if (!$value || !isset($validators[$valid]))
                return;
    
    Jared Hancock's avatar
    Jared Hancock committed
            $func = $validators[$valid];
    
            $error = $func[1];
            if ($config['validator-error'])
                $error = $config['validator-error'];
    
    Jared Hancock's avatar
    Jared Hancock committed
            if (is_array($func) && is_callable($func[0]))
                if (!call_user_func($func[0], $value))
    
                    $this->_errors[] = $error;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    }
    
    class TextareaField extends FormField {
    
        static $widget = 'TextareaWidget';
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        function getConfigurationOptions() {
            return array(
                'cols'  =>  new TextboxField(array(
                    'id'=>1, 'label'=>'Width (chars)', 'required'=>true, 'default'=>40)),
                'rows'  =>  new TextboxField(array(
                    'id'=>2, 'label'=>'Height (rows)', 'required'=>false, 'default'=>4)),
                'length' => new TextboxField(array(
    
                    'id'=>3, 'label'=>'Max Length', 'required'=>false, 'default'=>30)),
                'html' => new BooleanField(array(
                    'id'=>4, 'label'=>'HTML', 'required'=>false, 'default'=>true,
                    'configuration'=>array('desc'=>'Allow HTML input in this box'))),
    
    Jared Hancock's avatar
    Jared Hancock committed
            );
        }
    }
    
    class PhoneField extends FormField {
    
        static $widget = 'PhoneNumberWidget';
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        function validateEntry($value) {
            parent::validateEntry($value);
            # Run validator against $this->value for email type
            list($phone, $ext) = explode("X", $value, 2);
            if ($phone && !Validator::is_phone($phone))
                $this->_errors[] = "Enter a valid phone number";
            if ($ext) {
                if (!is_numeric($ext))
                    $this->_errors[] = "Enter a valide phone extension";
                elseif (!$phone)
                    $this->_errors[] = "Enter a phone number for the extension";
            }
        }
    
        function toString($value) {
            list($phone, $ext) = explode("X", $value, 2);
            $phone=Format::phone($phone);
            if ($ext)
                $phone.=" x$ext";
            return $phone;
        }
    }
    
    class BooleanField extends FormField {
    
        static $widget = 'CheckboxWidget';
    
    Jared Hancock's avatar
    Jared Hancock committed
    
        function getConfigurationOptions() {
            return array(
                'desc' => new TextareaField(array(
                    'id'=>1, 'label'=>'Description', 'required'=>false, 'default'=>'',
                    'hint'=>'Text shown inline with the widget',
                    'configuration'=>array('rows'=>2)))
            );
        }
    
        function to_database($value) {
            return ($value) ? '1' : '0';
        }
    
    
        function parse($value) {
            return $this->to_php($value);
        }
    
    Jared Hancock's avatar
    Jared Hancock committed
        function to_php($value) {
    
            return $value ? true : false;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function toString($value) {
            return ($value) ? 'Yes' : 'No';
        }
    }
    
    class ChoiceField extends FormField {
    
        static $widget = 'ChoicesWidget';
    
    Jared Hancock's avatar
    Jared Hancock committed
    
        function getConfigurationOptions() {
            return array(
                'choices'  =>  new TextareaField(array(
                    'id'=>1, 'label'=>'Choices', 'required'=>false, 'default'=>'')),
            );
        }
    
        function parse($value) {
            if (is_numeric($value))
                return $value;
            foreach ($this->getChoices() as $k=>$v)
                if (strcasecmp($value, $v) === 0)
                    return $k;
        }
    
    
        function toString($value) {
            $choices = $this->getChoices();
            if (isset($choices[$value]))
                return $choices[$value];
            else
                return $choices[$this->get('default')];
        }
    
        function getChoices() {
            if ($this->_choices === null) {
                // Allow choices to be set in this->ht (for configurationOptions)
                $this->_choices = $this->get('choices');
                if (!$this->_choices) {
                    $this->_choices = array();
                    $config = $this->getConfiguration();
                    $choices = explode("\n", $config['choices']);
                    foreach ($choices as $choice) {
                        // Allow choices to be key: value
                        list($key, $val) = explode(':', $choice);
                        if ($val == null)
                            $val = $key;
                        $this->_choices[trim($key)] = trim($val);
                    }
                }
            }
            return $this->_choices;
         }
    
    Jared Hancock's avatar
    Jared Hancock committed
    }
    
    class DatetimeField extends FormField {
    
        static $widget = 'DatetimePickerWidget';
    
    Jared Hancock's avatar
    Jared Hancock committed
    
        function to_database($value) {
            // Store time in gmt time, unix epoch format
            return (string) $value;
        }
    
        function to_php($value) {
            if (!$value)
                return $value;
            else
                return (int) $value;
        }
    
        function parse($value) {
            if (!$value) return null;
            $config = $this->getConfiguration();
            return ($config['gmt']) ? Misc::db2gmtime($value) : strtotime($value);
        }
    
        function toString($value) {
            global $cfg;
            $config = $this->getConfiguration();
            $format = ($config['time'])
                ? $cfg->getDateTimeFormat() : $cfg->getDateFormat();
            if ($config['gmt'])
                // Return time local to user's timezone
                return Format::userdate($format, $value);
            else
                return Format::date($format, $value);
        }
    
        function getConfigurationOptions() {
            return array(
                'time' => new BooleanField(array(
                    'id'=>1, 'label'=>'Time', 'required'=>false, 'default'=>false,
                    'configuration'=>array(
                        'desc'=>'Show time selection with date picker'))),
                'gmt' => new BooleanField(array(
                    'id'=>2, 'label'=>'Timezone Aware', 'required'=>false,
                    'configuration'=>array(
                        'desc'=>"Show date/time relative to user's timezone"))),
                'min' => new DatetimeField(array(
                    'id'=>3, 'label'=>'Earliest', 'required'=>false,
                    'hint'=>'Earliest date selectable')),
                'max' => new DatetimeField(array(
                    'id'=>4, 'label'=>'Latest', 'required'=>false,
                    'default'=>null)),
                'future' => new BooleanField(array(
                    'id'=>5, 'label'=>'Allow Future Dates', 'required'=>false,
                    'default'=>true, 'configuration'=>array(
                        'desc'=>'Allow entries into the future'))),
            );
        }
    
        function validateEntry($value) {
            $config = $this->getConfiguration();
            parent::validateEntry($value);
            if (!$value) return;
            if ($config['min'] and $value < $config['min'])
                $this->_errors[] = 'Selected date is earlier than permitted';
            elseif ($config['max'] and $value > $config['max'])
                $this->_errors[] = 'Selected date is later than permitted';
            // strtotime returns -1 on error for PHP < 5.1.0 and false thereafter
            elseif ($value === -1 or $value === false)
                $this->_errors[] = 'Enter a valid date';
        }
    }
    
    
    /**
     * This is kind-of a special field that doesn't have any data. It's used as
     * a field to provide a horizontal section break in the display of a form
     */
    class SectionBreakField extends FormField {
    
        static $widget = 'SectionBreakWidget';
    
    
        function hasData() {
            return false;
        }
    
        function isBlockLevel() {
            return true;
        }
    }
    
    class ThreadEntryField extends FormField {
    
        static $widget = 'ThreadEntryWidget';
    
    
        function isChangeable() {
            return false;
        }
        function isBlockLevel() {
            return true;
        }
        function isPresentationOnly() {
            return true;
        }
        function renderExtras($mode=null) {
            if ($mode == 'client')
    
                // TODO: Pass errors arrar into showAttachments
    
                $this->getWidget()->showAttachments();
        }
    }
    
    class PriorityField extends ChoiceField {
        function getWidget() {
            $widget = parent::getWidget();
            if ($widget->value instanceof Priority)
                $widget->value = $widget->value->getId();
            return $widget;
        }
    
        function getChoices() {
            $this->ht['default'] = 0;
    
            $sql = 'SELECT priority_id, priority_desc FROM '.PRIORITY_TABLE
                  .' ORDER BY priority_urgency DESC';
            $choices = array(0 => '&mdash; Default &mdash;');
            if (!($res = db_query($sql)))
                return $choices;
    
            while ($row = db_fetch_row($res))
                $choices[$row[0]] = $row[1];
            return $choices;
        }
    
        function parse($id) {
            return $this->to_php(null, $id);
        }
    
        function to_php($value, $id) {
            return Priority::lookup($id);
        }
    
        function to_database($prio) {
            return ($prio instanceof Priority)
                ? array($prio->getDesc(), $prio->getId())
                : $prio;
        }
    
        function toString($value) {
            return ($value instanceof Priority) ? $value->getDesc() : $value;
        }
    
        function getConfigurationOptions() {
            return array();
        }
    }
    FormField::addFieldTypes('Built-in Lists', function() {
        return array(
            'priority' => array('Priority Level', PriorityField),
        );
    });
    
    
    Jared Hancock's avatar
    Jared Hancock committed
    class Widget {
    
        function __construct($field) {
            $this->field = $field;
            $this->name = $field->getFormName();
    
        }
    
        function parseValue() {
    
            $this->value = $this->getValue();
    
            if (!isset($this->value) && is_object($this->field->getAnswer()))
                $this->value = $this->field->getAnswer()->getValue();
            if (!isset($this->value) && $this->field->value)
                $this->value = $this->field->value;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    
        function getValue() {
    
            $data = $this->field->getSource();
            if (!isset($data[$this->name]))
                return null;
            return $data[$this->name];
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    }
    
    class TextboxWidget extends Widget {
        function render() {
            $config = $this->field->getConfiguration();
            if (isset($config['size']))
                $size = "size=\"{$config['size']}\"";
            if (isset($config['length']))
                $maxlength = "maxlength=\"{$config['length']}\"";
    
            if (isset($config['classes']))
    
                $classes = 'class="'.$config['classes'].'"';
    
            if (isset($config['autocomplete']))
                $autocomplete = 'autocomplete="'.($config['autocomplete']?'on':'off').'"';
    
    Jared Hancock's avatar
    Jared Hancock committed
            ?>
            <span style="display:inline-block">
            <input type="text" id="<?php echo $this->name; ?>"
                <?php echo $size . " " . $maxlength; ?>
    
                <?php echo $classes.' '.$autocomplete; ?>
    
    Jared Hancock's avatar
    Jared Hancock committed
                name="<?php echo $this->name; ?>"
                value="<?php echo Format::htmlchars($this->value); ?>"/>
            </span>
            <?php
        }
    }
    
    class TextareaWidget extends Widget {
        function render() {
            $config = $this->field->getConfiguration();
    
            $class = $cols = $rows = $maxlength = "";
    
    Jared Hancock's avatar
    Jared Hancock committed
            if (isset($config['rows']))
                $rows = "rows=\"{$config['rows']}\"";
            if (isset($config['cols']))
                $cols = "cols=\"{$config['cols']}\"";
            if (isset($config['length']))
                $maxlength = "maxlength=\"{$config['length']}\"";
    
            if (isset($config['html']) && $config['html'])
                $class = 'class="richtext no-bar small"';
    
            <span style="display:inline-block;width:100%">
    
            <textarea <?php echo $rows." ".$cols." ".$maxlength." ".$class; ?>
    
    Jared Hancock's avatar
    Jared Hancock committed
                name="<?php echo $this->name; ?>"><?php
                    echo Format::htmlchars($this->value);
                ?></textarea>
            </span>
            <?php
        }
    }
    
    class PhoneNumberWidget extends Widget {
        function render() {
            list($phone, $ext) = explode("X", $this->value);
            ?>
            <input type="text" name="<?php echo $this->name; ?>" value="<?php
                echo $phone; ?>"/> Ext: <input type="text" name="<?php
                echo $this->name; ?>-ext" value="<?php echo $ext; ?>" size="5"/>
            <?php
        }
    
        function getValue() {
    
            $data = $this->field->getSource();
            $base = parent::getValue();
            if ($base === null)
                return $base;
            $ext = $data["{$this->name}-ext"];
    
    Jared Hancock's avatar
    Jared Hancock committed
            if ($ext) $ext = 'X'.$ext;
    
            return $base . $ext;
    
    Jared Hancock's avatar
    Jared Hancock committed
        }
    }
    
    class ChoicesWidget extends Widget {
        function render() {
            $config = $this->field->getConfiguration();
            // Determine the value for the default (the one listed if nothing is
            // selected)
            $def_key = $this->field->get('default');
    
            $choices = $this->field->getChoices();
    
    Jared Hancock's avatar
    Jared Hancock committed
            $have_def = isset($choices[$def_key]);
            if (!$have_def)
                $def_val = 'Select '.$this->field->get('label');
            else
                $def_val = $choices[$def_key];
            ?> <span style="display:inline-block">
            <select name="<?php echo $this->name; ?>">
                <?php if (!$have_def) { ?>
                <option value="<?php echo $def_key; ?>">&mdash; <?php
                    echo $def_val; ?> &mdash;</option>
                <?php }
                foreach ($choices as $key=>$name) {
                    if (!$have_def && $key == $def_key)
                        continue; ?>
    
                    <option value="<?php echo $key; ?>" <?php
                        if ($this->value == $key) echo 'selected="selected"';
    
    Jared Hancock's avatar
    Jared Hancock committed
                    ?>><?php echo $name; ?></option>
                <?php } ?>
            </select>
            </span>
            <?php
        }
    }
    
    class CheckboxWidget extends Widget {
        function __construct($field) {
            parent::__construct($field);
            $this->name = '_field-checkboxes';
        }
    
        function render() {
            $config = $this->field->getConfiguration();
            ?>
            <input type="checkbox" name="<?php echo $this->name; ?>[]" <?php
                if ($this->value) echo 'checked="checked"'; ?> value="<?php
                echo $this->field->get('id'); ?>"/>
            <?php
            if ($config['desc']) { ?>
                <em style="display:inline-block"><?php
                    echo Format::htmlchars($config['desc']); ?></em>
            <?php }
        }
    
        function getValue() {
    
            $data = $this->field->getSource();
            if (count($data))
                return @in_array($this->field->get('id'), $data[$this->name]);
    
    Jared Hancock's avatar
    Jared Hancock committed
            return parent::getValue();
        }
    }
    
    class DatetimePickerWidget extends Widget {
        function render() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            $config = $this->field->getConfiguration();
            if ($this->value) {
                $this->value = (is_int($this->value) ? $this->value :
    
                    DateTime::createFromFormat($cfg->getDateFormat(), $this->value)
                    ->format('U'));
    
    Jared Hancock's avatar
    Jared Hancock committed
                if ($config['gmt'])
                    $this->value += 3600 *
    
                        $_SESSION['TZ_OFFSET']+($_SESSION['TZ_DST']?date('I',$this->value):0);
    
    Jared Hancock's avatar
    Jared Hancock committed
    
                list($hr, $min) = explode(':', date('H:i', $this->value));
    
                $this->value = date($cfg->getDateFormat(), $this->value);
    
    Jared Hancock's avatar
    Jared Hancock committed
            }
            ?>
            <input type="text" name="<?php echo $this->name; ?>"
                value="<?php echo Format::htmlchars($this->value); ?>" size="12"
    
                autocomplete="off" class="dp" />
    
    Jared Hancock's avatar
    Jared Hancock committed
            <script type="text/javascript">
                $(function() {
                    $('input[name="<?php echo $this->name; ?>"]').datepicker({
                        <?php
                        if ($config['min'])
                            echo "minDate: new Date({$config['min']}000),";
                        if ($config['max'])
                            echo "maxDate: new Date({$config['max']}000),";
                        elseif (!$config['future'])
                            echo "maxDate: new Date().getTime(),";
                        ?>
                        numberOfMonths: 2,
                        showButtonPanel: true,
                        buttonImage: './images/cal.png',
    
                        showOn:'both',
                        dateFormat: $.translate_format(<?php echo $cfg->getDateFormat(); ?>),
    
    Jared Hancock's avatar
    Jared Hancock committed
                    });
                });
            </script>
            <?php
            if ($config['time'])
                // TODO: Add time picker -- requires time picker or selection with
                //       Misc::timeDropdown
                echo '&nbsp;' . Misc::timeDropdown($hr, $min, $this->name . ':time');
        }
    
        /**
         * Function: getValue
         * Combines the datepicker date value and the time dropdown selected
         * time value into a single date and time string value.
         */
        function getValue() {
    
            $data = $this->field->getSource();
    
    Jared Hancock's avatar
    Jared Hancock committed
            $datetime = parent::getValue();
    
            if ($datetime && isset($data[$this->name . ':time']))
                $datetime .= ' ' . $data[$this->name . ':time'];
    
    Jared Hancock's avatar
    Jared Hancock committed
            return $datetime;
        }
    }
    
    
    class SectionBreakWidget extends Widget {
        function render() {
            ?><div class="form-header section-break"><h3><?php
            echo Format::htmlchars($this->field->get('label'));
            ?></h3><em><?php echo Format::htmlchars($this->field->get('hint'));
            ?></em></div>
            <?php
        }
    }
    
    class ThreadEntryWidget extends Widget {
    
        function render($client=null) {
            global $cfg;
    
            ?><div style="margin-bottom:0.5em;margin-top:0.5em"><strong><?php
            echo Format::htmlchars($this->field->get('label'));
            ?></strong>:</div>
    
            <textarea name="<?php echo $this->field->get('name'); ?>"
    
                placeholder="<?php echo Format::htmlchars($this->field->get('hint')); ?>"
                <?php if (!$client) { ?>
                    data-draft-namespace="ticket.staff"
                <?php } else { ?>
                    data-draft-namespace="ticket.client"
                    data-draft-object-id="<?php echo substr(session_id(), -12); ?>"
                <?php } ?>
                class="richtext draft draft-delete ifhtml"
    
                cols="21" rows="8" style="width:80%;"><?php echo
                $this->value; ?></textarea>
        <?php
        }
    
    
        function showAttachments($errors=array()) {
    
            global $cfg, $thisclient;