Skip to content
Snippets Groups Projects
class.forms.php 74.8 KiB
Newer Older
            }
        }
        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) : $value;
Jared Hancock's avatar
Jared Hancock committed
    }

    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 export($value) {
        $config = $this->getConfiguration();
        if (!$value)
            return '';
        elseif ($config['gmt'])
            return Format::userdate('Y-m-d H:i:s', $value);
        else
            return Format::date('Y-m-d H:i:s', $value);
    }

Jared Hancock's avatar
Jared Hancock committed
    function getConfigurationOptions() {
        return array(
            'time' => new BooleanField(array(
                'id'=>1, 'label'=>__('Time'), 'required'=>false, 'default'=>false,
Jared Hancock's avatar
Jared Hancock committed
                'configuration'=>array(
                    'desc'=>__('Show time selection with date picker')))),
Jared Hancock's avatar
Jared Hancock committed
            'gmt' => new BooleanField(array(
                'id'=>2, 'label'=>__('Timezone Aware'), 'required'=>false,
Jared Hancock's avatar
Jared Hancock committed
                'configuration'=>array(
                    'desc'=>__("Show date/time relative to user's timezone")))),
Jared Hancock's avatar
Jared Hancock committed
            'min' => new DatetimeField(array(
                'id'=>3, 'label'=>__('Earliest'), 'required'=>false,
                'hint'=>__('Earliest date selectable'))),
Jared Hancock's avatar
Jared Hancock committed
            'max' => new DatetimeField(array(
                'id'=>4, 'label'=>__('Latest'), 'required'=>false,
                'default'=>null, 'hint'=>__('Latest date selectable'))),
Jared Hancock's avatar
Jared Hancock committed
            'future' => new BooleanField(array(
                'id'=>5, 'label'=>__('Allow Future Dates'), 'required'=>false,
Jared Hancock's avatar
Jared Hancock committed
                'default'=>true, 'configuration'=>array(
                    'desc'=>__('Allow entries into the future' /* Used in the date field */)),
            )),
Jared Hancock's avatar
Jared Hancock committed
        );
    }

    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');
Jared Hancock's avatar
Jared Hancock committed
        elseif ($config['max'] and $value > $config['max'])
            $this->_errors[] = __('Selected date is later than permitted');
Jared Hancock's avatar
Jared Hancock committed
        // 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 hasSpecialSearch() {
        return false;
    }

    function getConfigurationOptions() {
        global $cfg;

        $attachments = new FileUploadField();
        $fileupload_config = $attachments->getConfigurationOptions();
        $fileupload_config['extensions']->set('default', $cfg->getAllowedFileTypes());
        return array(
            'attachments' => new BooleanField(array(
                'label'=>__('Enable Attachments'),
                'default'=>$cfg->allowAttachments(),
                    'desc'=>__('Enables attachments on tickets, regardless of channel'),
                'validators' => function($self, $value) {
                    if (!ini_get('file_uploads'))
                        $self->addError(__('The "file_uploads" directive is disabled in php.ini'));
                }
        + $fileupload_config;

    function isAttachmentsEnabled() {
        $config = $this->getConfiguration();
        return $config['attachments'];
    }
}

class PriorityField extends ChoiceField {
    function getWidget() {
        $widget = parent::getWidget();
        if ($widget->value instanceof Priority)
            $widget->value = $widget->value->getId();
        return $widget;
    }

    function hasIdValue() {
        return true;
    }
    function isChangeable() {
        return $this->getForm()->get('type') != 'T' ||
            $this->get('name') != 'priority';
    }
    function getChoices() {
        global $cfg;
        $this->ht['default'] = $cfg->getDefaultPriorityId();

        $sql = 'SELECT priority_id, priority_desc FROM '.PRIORITY_TABLE
              .' ORDER BY priority_urgency DESC';
        $choices = array();
        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=false) {
        if (is_array($id)) {
            reset($id);
            $id = key($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 searchable($value) {
        // Priority isn't searchable this way
        return null;
    }

    function getConfigurationOptions() {
        return array(
            'prompt' => new TextboxField(array(
                'id'=>2, 'label'=>__('Prompt'), 'required'=>false, 'default'=>'',
                'hint'=>__('Leading text shown before a value is selected'),
                'configuration'=>array('size'=>40, 'length'=>40),
            )),
        );
FormField::addFieldTypes(/*@trans*/ 'Dynamic Fields', function() {
    return array(
        'priority' => array(__('Priority Level'), PriorityField),

class TicketStateField extends ChoiceField {

    static $_states = array(
            'open' => array(
                'name' => /* @trans, @context "ticket state name" */ 'Open',
                'verb' => /* @trans, @context "ticket state action" */ 'Open'
                ),
            'closed' => array(
                'name' => /* @trans, @context "ticket state name" */ 'Closed',
                'verb' => /* @trans, @context "ticket state action" */ 'Close'
    // Private states
    static $_privatestates = array(
            'archived' => array(
                'name' => /* @trans, @context "ticket state name" */ 'Archived',
                'verb' => /* @trans, @context "ticket state action" */ 'Archive'
                ),
            'deleted'  => array(
                'name' => /* @trans, @context "ticket state name" */ 'Deleted',
                'verb' => /* @trans, @context "ticket state action" */ 'Delete'
            );

    function hasIdValue() {
        return true;
    }

    function isChangeable() {
        return false;
    }

    function getChoices() {
        static $_choices;

        if (!isset($_choices)) {
            // Translate and cache the choices
            foreach (static::$_states as $k => $v)
                $_choices[$k] =  _P('ticket state name', $v['name']);
            $this->ht['default'] =  '';
        }

        return $_choices;
    }

    function getChoice($state) {

        if ($state && is_array($state))
            $state = key($state);

        if (isset(static::$_states[$state]))
            return _P('ticket state name', static::$_states[$state]['name']);

        if (isset(static::$_privatestates[$state]))
            return _P('ticket state name', static::$_privatestates[$state]['name']);
        return $state;
    }

    function getConfigurationOptions() {
        return array(
            'prompt' => new TextboxField(array(
                'id'=>2, 'label'=> __('Prompt'), 'required'=>false, 'default'=>'',
                'hint'=> __('Leading text shown before a value is selected'),
                'configuration'=>array('size'=>40, 'length'=>40),
            )),
        );
    }

    static function getVerb($state) {

        if (isset(static::$_states[$state]))
            return _P('ticket state action', static::$_states[$state]['verb']);

        if (isset(static::$_privatestates[$state]))
            return _P('ticket state action', static::$_privatestates[$state]['verb']);
}
FormField::addFieldTypes('Dynamic Fields', function() {
    return array(
        'state' => array('Ticket State', TicketStateField, false),
    );
});

class TicketFlagField extends ChoiceField {

    // Supported flags (TODO: move to configurable custom list)
    static $_flags = array(
            'onhold' => array(
                'flag' => 1,
                'name' => 'Onhold',
                'states' => array('open'),
                ),
            'overdue' => array(
                'flag' => 2,
                'name' => 'Overdue',
                'states' => array('open'),
                ),
            'answered' => array(
                'flag' => 4,
                'name' => 'Answered',
                'states' => array('open'),
                )
            );

    var $_choices;

    function hasIdValue() {
        return true;
    }

    function isChangeable() {
        return true;
    }

    function getChoices() {
        $this->ht['default'] =  '';

        if (!$this->_choices) {
            foreach (static::$_flags as $k => $v)
                $this->_choices[$k] = $v['name'];
        }

        return $this->_choices;
    }

    function getConfigurationOptions() {
        return array(
            'prompt' => new TextboxField(array(
                'id'=>2, 'label'=>'Prompt', 'required'=>false, 'default'=>'',
                'hint'=>'Leading text shown before a value is selected',
                'configuration'=>array('size'=>40, 'length'=>40),
            )),
        );
    }
}

FormField::addFieldTypes('Dynamic Fields', function() {
    return array(
        'flags' => array('Ticket Flags', TicketFlagField, false),
    );
});

class FileUploadField extends FormField {
    static $widget = 'FileUploadWidget';

    protected $attachments;

    static function getFileTypes() {
        static $filetypes;

        if (!isset($filetypes))
            $filetypes = YamlDataParser::load(INCLUDE_DIR . '/config/filetype.yaml');
        return $filetypes;
    }

    function getConfigurationOptions() {
        // Compute size selections
        $sizes = array('262144' => '— '.__('Small').' —');
        $next = 512 << 10;
        $max = strtoupper(ini_get('upload_max_filesize'));
        $limit = (int) $max;
        if (!$limit) $limit = 2 << 20; # 2M default value
        elseif (strpos($max, 'K')) $limit <<= 10;
        elseif (strpos($max, 'M')) $limit <<= 20;
        elseif (strpos($max, 'G')) $limit <<= 30;
        while ($next <= $limit) {
            // Select the closest, larger value (in case the
            // current value is between two)
            $sizes[$next] = Format::file_size($next);
            $next *= 2;
        }
        // Add extra option if top-limit in php.ini doesn't fall
        // at a power of two
        if ($next < $limit * 2)
            $sizes[$limit] = Format::file_size($limit);

        $types = array();
        foreach (self::getFileTypes() as $type=>$info) {
            $types[$type] = $info['description'];
        }

        return array(
            'size' => new ChoiceField(array(
                'label'=>__('Maximum File Size'),
                'hint'=>__('Choose maximum size of a single file uploaded to this field'),
                'default'=>$cfg->getMaxFileSize(),
                'choices'=>$sizes
            )),
            'mimetypes' => new ChoiceField(array(
                'label'=>__('Restrict by File Type'),
                'hint'=>__('Optionally, choose acceptable file types.'),
                'required'=>false,
                'choices'=>$types,
                'configuration'=>array('multiselect'=>true,'prompt'=>__('No restrictions'))
            'extensions' => new TextareaField(array(
                'label'=>__('Additional File Type Filters'),
                'hint'=>__('Optionally, enter comma-separated list of additional file types, by extension. (e.g .doc, .pdf).'),
                'configuration'=>array('html'=>false, 'rows'=>2),
            )),
            'max' => new TextboxField(array(
                'label'=>__('Maximum Files'),
                'hint'=>__('Users cannot upload more than this many files.'),
                'default'=>false,
                'required'=>false,
                'validator'=>'number',
                'configuration'=>array('size'=>8, 'length'=>4, 'placeholder'=>__('No limit')),
    function hasSpecialSearch() {
        return false;
    }

    /**
     * Called from the ajax handler for async uploads via web clients.
     */
    function ajaxUpload($bypass=false) {
        $config = $this->getConfiguration();

        $files = AttachmentFile::format($_FILES['upload'],
            // For numeric fields assume configuration exists
            !is_numeric($this->get('id')));
        if (count($files) != 1)
            Http::response(400, 'Send one file at a time');
        $file = array_shift($files);
        $file['name'] = urldecode($file['name']);

        if (!$bypass && !$this->isValidFileType($file['name'], $file['type']))
            Http::response(415, 'File type is not allowed');

        $config = $this->getConfiguration();
        if (!$bypass && $file['size'] > $config['size'])
            Http::response(413, 'File is too large');

        if (!($id = AttachmentFile::upload($file)))
            Http::response(500, 'Unable to store file: '. $file['error']);
    /**
     * Called from FileUploadWidget::getValue() when manual upload is used
     * for browsers which do not support the HTML5 way of uploading async.
     */
    function uploadFile($file) {
        if (!$this->isValidFileType($file['name'], $file['type']))
            throw new FileUploadError(__('File type is not allowed'));

        $config = $this->getConfiguration();
        if ($file['size'] > $config['size'])
            throw new FileUploadError(__('File size is too large'));

        return AttachmentFile::upload($file);
    }

    /**
     * Called from API and email routines and such to handle attachments
     * sent other than via web upload
     */
    function uploadAttachment(&$file) {
        if (!$this->isValidFileType($file['name'], $file['type']))
            throw new FileUploadError(__('File type is not allowed'));

        if (is_callable($file['data']))
            $file['data'] = $file['data']();
        if (!isset($file['size'])) {
            // bootstrap.php include a compat version of mb_strlen
            if (extension_loaded('mbstring'))
                $file['size'] = mb_strlen($file['data'], '8bit');
            else
                $file['size'] = strlen($file['data']);
        }

        $config = $this->getConfiguration();
        if ($file['size'] > $config['size'])
            throw new FileUploadError(__('File size is too large'));

        if (!$id = AttachmentFile::save($file))
            throw new FileUploadError(__('Unable to save file'));

        return $id;
    }

    function isValidFileType($name, $type=false) {
        $config = $this->getConfiguration();

        // Check MIME type - file ext. shouldn't be solely trusted.
        if ($type && $config['__mimetypes']
                && in_array($type, $config['__mimetypes']))
        // Return true if all file types are allowed (.*)
        if (!$config['__extensions'] || in_array('.*', $config['__extensions']))
        $allowed = $config['__extensions'];
        $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));

        return ($ext && is_array($allowed) && in_array(".$ext", $allowed));
    }

    function getFiles() {
        if (!isset($this->attachments) && ($a = $this->getAnswer())
            && ($e = $a->getEntry()) && ($e->get('id'))
        ) {
            $this->attachments = new GenericAttachments(
                // Combine the field and entry ids to make the key
                sprintf('%u', crc32('E'.$this->get('id').$e->get('id'))),
                'E');
        }
        return $this->attachments ? $this->attachments->getAll() : array();
    }

    function getConfiguration() {
        $config = parent::getConfiguration();
        $_types = self::getFileTypes();
        $mimetypes = array();
        $extensions = array();
        if (isset($config['mimetypes']) && is_array($config['mimetypes'])) {
            foreach ($config['mimetypes'] as $type=>$desc) {
                foreach ($_types[$type]['types'] as $mime=>$exts) {
                    $mimetypes[$mime] = true;
                    if (is_array($exts))
                        foreach ($exts as $ext)
                            $extensions['.'.$ext] = true;
                }
            }
        }
        if (strpos($config['extensions'], '.*') !== false)
            $config['extensions'] = '';

        if (is_string($config['extensions'])) {
            foreach (preg_split('/\s+/', str_replace(',',' ', $config['extensions'])) as $ext) {
                if (!$ext) {
                    continue;
                }
                elseif (strpos($ext, '/')) {
                    $mimetypes[$ext] = true;
                }
                else {
                    if ($ext[0] != '.')
                        $ext = '.' . $ext;
                    // Add this to the MIME types list so it can be exported to
                    // the @accept attribute
                    if (!isset($extensions[$ext]))
                        $mimetypes[$ext] = true;

                    $extensions[$ext] = true;
                }
            $config['__extensions'] = array_keys($extensions);
        }
        elseif (is_array($config['extensions'])) {
            $config['__extensions'] = $config['extensions'];
        }

        // 'mimetypes' is the array represented from the user interface,
        // '__mimetypes' is a complete list of supported MIME types.
        $config['__mimetypes'] = array_keys($mimetypes);
        return $config;
    }

    // When the field is saved to database, encode the ID listing as a json
    // array. Then, inspect the difference between the files actually
    // attached to this field
    function to_database($value) {
        $this->getFiles();
        if (isset($this->attachments)) {
            $ids = array();
            // Handle deletes
            foreach ($this->attachments->getAll() as $f) {
                if (!in_array($f['id'], $value))
                    $this->attachments->delete($f['id']);
                else
                    $ids[] = $f['id'];
            }
            // Handle new files
            foreach ($value as $id) {
                if (!in_array($id, $ids))
                    $this->attachments->upload($id);
            }
        }
        return JsonDataEncoder::encode($value);
    }

    function parse($value) {
        // Values in the database should be integer file-ids
        return array_map(function($e) { return (int) $e; },
            $value ?: array());
    }

    function to_php($value) {
        return JsonDataParser::decode($value);
    }

    function display($value) {
        $links = array();
        foreach ($this->getFiles() as $f) {
            $hash = strtolower($f['key']
                . md5($f['id'].session_id().strtolower($f['key'])));
            $links[] = sprintf('<a class="no-pjax" href="file.php?h=%s">%s</a>',
                $hash, Format::htmlchars($f['name']));
        }
        return implode('<br/>', $links);
    }

    function toString($value) {
        $files = array();
        foreach ($this->getFiles() as $f) {
            $files[] = $f['name'];
        }
        return implode(', ', $files);
    }
Jared Hancock's avatar
Jared Hancock committed
class Widget {
    static $media = null;
Jared Hancock's avatar
Jared Hancock committed

    function __construct($field) {
        $this->field = $field;
        $this->name = $field->getFormName();
        $this->id = '_' . $this->name;
    }

    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) && isset($this->field->value))
            $this->value = $this->field->value;
Jared Hancock's avatar
Jared Hancock committed
    }

    function getValue() {
        $data = $this->field->getSource();
        // Search for HTML form name first
        if (isset($data[$this->name]))
            return $data[$this->name];
        elseif (isset($data[$this->field->get('name')]))
            return $data[$this->field->get('name')];
        return null;

    /**
     * getJsValueGetter
     *
     * Used with the dependent fields feature, this function should return a
     * single javascript expression which can be used in a larger expression
     * (<> == true, where <> is the result of this function). The %s token
     * will be replaced with a jQuery variable representing this widget.
     */
    function getJsValueGetter() {
        return '%s.val()';
    }
Jared Hancock's avatar
Jared Hancock committed
}

class TextboxWidget extends Widget {
Jared Hancock's avatar
Jared Hancock committed
    static $input_type = 'text';

    function render($mode=false) {
Jared Hancock's avatar
Jared Hancock committed
        $config = $this->field->getConfiguration();
        if (isset($config['size']))
            $size = "size=\"{$config['size']}\"";
        if (isset($config['length']) && $config['length'])
Jared Hancock's avatar
Jared Hancock committed
            $maxlength = "maxlength=\"{$config['length']}\"";
        if (isset($config['classes']))
            $classes = 'class="'.$config['classes'].'"';
        if (isset($config['autocomplete']))
            $autocomplete = 'autocomplete="'.($config['autocomplete']?'on':'off').'"';
        if (isset($config['disabled']))
            $disabled = 'disabled="disabled"';
Jared Hancock's avatar
Jared Hancock committed
        ?>
        <span style="display:inline-block">
Jared Hancock's avatar
Jared Hancock committed
        <input type="<?php echo static::$input_type; ?>"
            id="<?php echo $this->id; ?>"
            <?php echo implode(' ', array_filter(array(
                $size, $maxlength, $classes, $autocomplete, $disabled)))
                .' placeholder="'.$config['placeholder'].'"'; ?>
Jared Hancock's avatar
Jared Hancock committed
            name="<?php echo $this->name; ?>"
            value="<?php echo Format::htmlchars($this->value); ?>"/>
        </span>
        <?php
    }
}

Jared Hancock's avatar
Jared Hancock committed
class PasswordWidget extends TextboxWidget {
    static $input_type = 'password';

    function parseValue() {
        // Show empty box unless failed POST
        if ($_SERVER['REQUEST_METHOD'] == 'POST'
                && $this->field->getForm()->isValid())
            parent::parseValue();
        else
            $this->value = '';
    }
}

Jared Hancock's avatar
Jared Hancock committed
class TextareaWidget extends Widget {
    function render($mode=false) {
Jared Hancock's avatar
Jared Hancock committed
        $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']) && $config['length'])
Jared Hancock's avatar
Jared Hancock committed
            $maxlength = "maxlength=\"{$config['length']}\"";
        if (isset($config['html']) && $config['html']) {
            $class = 'class="richtext no-bar small"';
            $this->value = Format::viewableImages($this->value);
        }
        <span style="display:inline-block;width:100%">
        <textarea <?php echo $rows." ".$cols." ".$maxlength." ".$class
                .' placeholder="'.$config['placeholder'].'"'; ?>
            id="<?php echo $this->id; ?>"
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($mode=false) {
        $config = $this->field->getConfiguration();
Jared Hancock's avatar
Jared Hancock committed
        list($phone, $ext) = explode("X", $this->value);
        ?>
        <input id="<?php echo $this->id; ?>" type="text" name="<?php echo $this->name; ?>" value="<?php
        echo Format::htmlchars($phone); ?>"/><?php
        // Allow display of extension field even if disabled if the phone
        // number being edited has an extension
        if ($ext || $config['ext']) { ?> <?php echo __('Ext'); ?>:
            <input type="text" name="<?php
            echo $this->name; ?>-ext" value="<?php echo Format::htmlchars($ext);
                ?>" size="5"/>
Jared Hancock's avatar
Jared Hancock committed
    }

    function getValue() {
        $data = $this->field->getSource();
        $base = parent::getValue();
        if ($base === null)
            return $base;
        $ext = $data["{$this->name}-ext"];
        // NOTE: 'X' is significant. Don't change it
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 {
    static $media = array(
        'css' => array(
            '/css/jquery.multiselect.css',
        ),
    );

    function render($mode=false) {
            if (!($val = (string) $this->field))
                $val = sprintf('<span class="faded">%s</span>', __('None'));
Jared Hancock's avatar
Jared Hancock committed
        $config = $this->field->getConfiguration();
        if ($mode == 'search') {
            $config['multiselect'] = true;
        }

Jared Hancock's avatar
Jared Hancock committed
        // Determine the value for the default (the one listed if nothing is
        // selected)
Peter Rotich's avatar
Peter Rotich committed
        $choices = $this->field->getChoices(true);
        $prompt = $config['prompt'] ?: __('Select');
        $have_def = false;
        // We don't consider the 'default' when rendering in 'search' mode
        if (!strcasecmp($mode, 'search')) {
            $def_val = $prompt;
        } else {
            $def_key = $this->field->get('default');
            if (!$def_key && $config['default'])
                $def_key = $config['default'];
            $have_def = isset($choices[$def_key]);
            $def_val = $have_def ? $choices[$def_key] : $prompt;
        $values = $this->value;
        if (!is_array($values)) {
            $values = array($values => $this->field->getChoice($values));
        }
Peter Rotich's avatar
Peter Rotich committed

        if ($values === null)
            $values = $have_def ? array($def_key => $choices[$def_key]) : array();

        ?>
        <select name="<?php echo $this->name; ?>[]"
            id="<?php echo $this->id; ?>"
            data-prompt="<?php echo $prompt; ?>"
            <?php if ($config['multiselect'])
                echo ' multiple="multiple" class="multiselect"'; ?>>
            <?php if (!$have_def && !$config['multiselect']) { ?>
Jared Hancock's avatar
Jared Hancock committed
            <option value="<?php echo $def_key; ?>">&mdash; <?php
                echo $def_val; ?> &mdash;</option>
            <?php }
Peter Rotich's avatar
Peter Rotich committed
            foreach ($choices as $key => $name) {
Jared Hancock's avatar
Jared Hancock committed
                if (!$have_def && $key == $def_key)
                    continue; ?>
                <option value="<?php echo $key; ?>" <?php
Peter Rotich's avatar
Peter Rotich committed
                    if (isset($values[$key])) echo 'selected="selected"';
Jared Hancock's avatar
Jared Hancock committed
                ?>><?php echo $name; ?></option>
            <?php } ?>
        </select>
        <?php
        if ($config['multiselect']) {
         ?>
        <script type="text/javascript">
        $(function() {
            $("#<?php echo $this->id; ?>")
            .multiselect({'noneSelectedText':'<?php echo $prompt; ?>'});
        });
        </script>
       <?php
        }

    function getValue() {
        $value = parent::getValue();

        if (!$value) return null;

        // Assume multiselect
        $values = array();
        $choices = $this->field->getChoices();
        if (is_array($value)) {
            foreach($value as $k => $v) {
                if (isset($choices[$v]))
                    $values[$v] = $choices[$v];
            }
        }
        return $values;
    }

    function getJsValueGetter() {
        return '%s.find(":selected").val()';
    }
Jared Hancock's avatar
Jared Hancock committed
}

class CheckboxWidget extends Widget {
    function __construct($field) {
        parent::__construct($field);
        $this->name = '_field-checkboxes';
    }

    function render($mode=false) {
Jared Hancock's avatar
Jared Hancock committed
        $config = $this->field->getConfiguration();
        if (!isset($this->value))
            $this->value = $this->field->get('default');
        <input id="<?php echo $this->id; ?>" style="vertical-align:top;"
            type="checkbox" name="<?php echo $this->name; ?>[]" <?php
Jared Hancock's avatar
Jared Hancock committed
            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::viewableImages($config['desc']); ?></em>
Jared Hancock's avatar
Jared Hancock committed
        <?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();
    }

    function getJsValueGetter() {
        return '%s.is(":checked")';
Jared Hancock's avatar
Jared Hancock committed
}

class DatetimePickerWidget extends Widget {
    function render($mode=false) {
Jared Hancock's avatar
Jared Hancock committed
        $config = $this->field->getConfiguration();
        if ($this->value) {
            $this->value = is_int($this->value) ? $this->value :
                strtotime($this->value);
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 = Format::date($cfg->getDateFormat(), $this->value);
Jared Hancock's avatar
Jared Hancock committed
        }
        ?>
        <input type="text" name="<?php echo $this->name; ?>"
            id="<?php echo $this->id; ?>"
Jared Hancock's avatar
Jared Hancock committed
            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',
                    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();
        $config = $this->field->getConfiguration();
        if ($datetime = parent::getValue()) {
            $datetime = is_int($datetime) ? $datetime :
                strtotime($datetime);
            if ($datetime && isset($data[$this->name . ':time'])) {
                list($hr, $min) = explode(':', $data[$this->name . ':time']);
                $datetime += $hr * 3600 + $min * 60;
            }
            if ($datetime && $config['gmt'])
                $datetime -= (int) (3600 * $_SESSION['TZ_OFFSET'] +
                    ($_SESSION['TZ_DST'] ? date('I',$datetime) : 0));
        }
Jared Hancock's avatar
Jared Hancock committed
        return $datetime;
    }
}

class SectionBreakWidget extends Widget {
    function render($mode=false) {
        ?><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;