Skip to content
Snippets Groups Projects
class.forms.php 174 KiB
Newer Older
  • Learn to ignore specific revisions
  • Peter Rotich's avatar
    Peter Rotich committed
    class SLAField extends ChoiceField {
        function getWidget($widgetClass=false) {
            $widget = parent::getWidget($widgetClass);
            if ($widget->value instanceof SLA)
                $widget->value = $widget->value->getId();
            return $widget;
        }
    
        function hasIdValue() {
            return true;
        }
    
        function getChoices($verbose=false) {
            global $cfg;
    
            $choices = array();
            if (($depts = SLA::getSLAs()))
                foreach ($depts as $id => $name)
                    $choices[$id] = $name;
    
            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 $id;
        }
    
        function to_database($sla) {
            return ($sla instanceof SLA)
    
                ? array($sla->getName(), $sla->getId())
    
    Peter Rotich's avatar
    Peter Rotich committed
                : $sla;
        }
    
        function toString($value) {
            return (string) $value;
        }
    
        function searchable($value) {
            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),
                )),
            );
        }
    }
    
    
    class AssigneeField extends ChoiceField {
    
    Peter Rotich's avatar
    Peter Rotich committed
        var $_choices = null;
    
    Peter Rotich's avatar
    Peter Rotich committed
        var $_criteria = null;
    
    
        function getWidget($widgetClass=false) {
            $widget = parent::getWidget($widgetClass);
    
            if (is_object($widget->value))
                $widget->value = $widget->value->getId();
            return $widget;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getCriteria() {
    
            if (!isset($this->_criteria)) {
                $this->_criteria = array('available' => true);
                if (($c=parent::getCriteria()))
                    $this->_criteria = array_merge($this->_criteria, $c);
            }
    
            return $this->_criteria;
        }
    
    
        function hasIdValue() {
            return true;
        }
    
    
        function setChoices($choices) {
            $this->_choices = $choices;
        }
    
    
        function getChoices($verbose=false) {
    
            global $cfg;
    
    Peter Rotich's avatar
    Peter Rotich committed
    
    
            if (!isset($this->_choices)) {
    
    Peter Rotich's avatar
    Peter Rotich committed
                $config = $this->getConfiguration();
                $choices = array(
                        __('Agents') => new ArrayObject(),
                        __('Teams') => new ArrayObject());
                $A = current($choices);
                $criteria = $this->getCriteria();
                $agents = array();
                if (($dept=$config['dept']) && $dept->assignMembersOnly()) {
    
                    if (($members = $dept->getAvailableMembers()))
    
    Peter Rotich's avatar
    Peter Rotich committed
                        foreach ($members as $member)
                            $agents[$member->getId()] = $member;
                } else {
                    $agents = Staff::getStaffMembers($criteria);
                }
    
    
                foreach ($agents as $id => $name)
    
    Peter Rotich's avatar
    Peter Rotich committed
                next($choices);
                $T = current($choices);
    
                if (($teams = Team::getActiveTeams()))
    
    Peter Rotich's avatar
    Peter Rotich committed
                    foreach ($teams as $id => $name)
                        $T['t'.$id] = $name;
    
    Peter Rotich's avatar
    Peter Rotich committed
                $this->_choices = $choices;
            }
    
            return $this->_choices;
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getValue() {
    
            if (($value = parent::getValue()) && ($id=$this->getClean()))
               return $value[$id];
        }
    
    
    
        function parse($id) {
            return $this->to_php(null, $id);
        }
    
        function to_php($value, $id=false) {
    
    Peter Rotich's avatar
    Peter Rotich committed
            $type = '';
    
            if (is_array($id)) {
                reset($id);
                $id = key($id);
    
    Peter Rotich's avatar
    Peter Rotich committed
                $type = $id[0];
                $id = substr($id, 1);
    
    Peter Rotich's avatar
    Peter Rotich committed
            switch ($type) {
            case 's':
                return Staff::lookup($id);
            case 't':
                return Team::lookup($id);
            case 'd':
                return Dept::lookup($id);
            default:
                return $id;
            }
    
        }
    
    
        function to_database($value) {
            return (is_object($value))
                ? array($value->getName(), $value->getId())
                : $value;
        }
    
        function toString($value) {
            return (string) $value;
        }
    
        function searchable($value) {
            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(
    
    Marco Borla's avatar
    Marco Borla committed
            'assignee' => array(__('Assignee'), 'AssigneeField'),
    
    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($verbose=false) {
    
            static $_choices;
    
    
            $states = static::$_states;
            if ($this->options['private_too'])
                $states += static::$_privatestates;
    
    
            if (!isset($_choices)) {
                // Translate and cache the choices
    
                foreach ($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(
    
    Marco Borla's avatar
    Marco Borla committed
            '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($verbose=false) {
    
            $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(
    
    Marco Borla's avatar
    Marco Borla committed
            'flags' => array('Ticket Flags', 'TicketFlagField', false),
    
    class FileUploadField extends FormField {
        static $widget = 'FileUploadWidget';
    
        protected $attachments;
    
        protected $files;
    
        static function getFileTypes() {
            static $filetypes;
    
    
            if (!isset($filetypes)) {
    
                if (function_exists('apcu_fetch')) {
    
                    $key = md5(SECRET_SALT . GIT_VERSION . 'filetypes');
    
                    $filetypes = apcu_fetch($key);
    
                }
                if (!$filetypes)
                    $filetypes = YamlDataParser::load(INCLUDE_DIR . '/config/filetype.yaml');
                if ($key)
    
                    apcu_store($key, $filetypes, 7200);
    
        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')),
    
        /**
         * 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 (!$this->isValidFile($file))
                Http::response(413, 'Invalid File');
    
    
            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 (!($F = AttachmentFile::upload($file)))
    
                Http::response(500, 'Unable to store file: '. $file['error']);
    
            // This file is allowed for attachment in this session
    
    aydreeihn's avatar
    aydreeihn committed
            $_SESSION[':uploadedFiles'][$id] = $F->getName();
    
        /**
         * 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'));
    
    
            if (!$this->isValidFile($file))
                 throw new FileUploadError(__('Invalid File'));
    
    
            $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 (!$F = AttachmentFile::create($file))
    
                throw new FileUploadError(__('Unable to save file'));
    
    
        function isValidFile($file) {
    
            // Check invalid image hacks
            if ($file['tmp_name']
                    && stripos($file['type'], 'image/') === 0
                    && function_exists('exif_imagetype')
                    && !exif_imagetype($file['tmp_name']))
                return false;
    
            return true;
        }
    
    
        function isValidFileType($name, $type=false) {
            $config = $this->getConfiguration();
    
    
            // Check MIME type - file ext. shouldn't be solely trusted.
            if ($type && $config['__mimetypes']
    
    JediKev's avatar
    JediKev committed
                    && in_array($type, $config['__mimetypes'], true))
    
            // 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 getAttachments() {
    
            if (!isset($this->attachments) && ($a = $this->getAnswer())
                && ($e = $a->getEntry()) && ($e->get('id'))
            ) {
    
                $this->attachments = GenericAttachments::forIdAndType(
    
                    // Combine the field and entry ids to make the key
    
                    sprintf('%u', abs(crc32('E'.$this->get('id').$e->get('id')))),
    
            return $this->attachments ?: array();
    
        function setAttachments(GenericAttachments $att) {
            $this->attachments = $att;
    
        function getFiles() {
            if (!isset($this->files)) {
                $files = array();
                foreach ($this->getAttachments() as $a) {
                    if ($a && ($f=$a->getFile()))
                        $files[] = $f;
                }
    
                foreach ($this->getClean(false) ?: array() as $key => $value)
    
    aydreeihn's avatar
    aydreeihn committed
                    $files[] = array('id' => $key, 'name' => $value);
    
    
                $this->files = $files;
            }
            return $this->files;
        }
    
    
        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;
    
    
                        // Ensure that the extension is lower-cased for comparison latr
                        $ext = strtolower($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->getAttachments();
            if (isset($this->attachments) && $this->attachments) {
    
                $this->attachments->keepOnlyFileIds($value);
    
            }
            return JsonDataEncoder::encode($value);
        }
    
        function parse($value) {
    
    aydreeihn's avatar
    aydreeihn committed
            return $value;
    
        }
    
        function to_php($value) {
    
            return is_array($value) ? $value : JsonDataParser::decode($value);
    
        function display($value) {
            $links = array();
    
    JediKev's avatar
    JediKev committed
            foreach ($this->getAttachments() as $a) {
    
    JediKev's avatar
    JediKev committed
                $links[] = sprintf('<a class="no-pjax" href="%s"><i class="icon-paperclip icon-flip-horizontal"></i> %s</a>',
    
    JediKev's avatar
    JediKev committed
                    Format::htmlchars($a->file->getDownloadUrl()),
                    Format::htmlchars($a->getFilename()));
    
            }
            return implode('<br/>', $links);
        }
    
    
        function toString($value) {
            $files = array();
            foreach ($this->getFiles() as $f) {
    
                $files[] = $f->name;
    
            }
            return implode(', ', $files);
        }
    
        function db_cleanup($field=false) {
    
            if ($this->getAttachments()) {
    
    
        function asVar($value, $id=false) {
    
            if (($attachments = $this->getAttachments()))
                $attachments = $attachments->all();
    
            return new FileFieldAttachments($attachments ?: array());
    
        }
        function asVarType() {
            return 'FileFieldAttachments';
        }
    
    
        function whatChanged($before, $after) {
            $B = (array) $before;
            $A = (array) $after;
            $added = array_diff($A, $B);
            $deleted = array_diff($B, $A);
            $added = Format::htmlchars(array_keys($added));
            $deleted = Format::htmlchars(array_keys($deleted));
    
            if ($added && $deleted) {
                $desc = sprintf(
                    __('added <strong>%1$s</strong> and removed <strong>%2$s</strong>'),
                    implode(', ', $added), implode(', ', $deleted));
            }
            elseif ($added) {
                $desc = sprintf(
                    __('added <strong>%1$s</strong>'),
                    implode(', ', $added));
            }
            elseif ($deleted) {
                $desc = sprintf(
                    __('removed <strong>%1$s</strong>'),
                    implode(', ', $deleted));
            }
            else {
                $desc = sprintf(
                    __('changed from <strong>%1$s</strong> to <strong>%2$s</strong>'),
                    $this->display($before), $this->display($after));
            }
            return $desc;
        }
    
        var $attachments;
    
        function __construct($attachments) {
            $this->attachments = $attachments;
    
        }
    
        function __toString() {
            $files = array();
    
            foreach ($this->getAttachments() as $a) {
                $files[] = $a->getFilename();
    
        function getAttachments() {
            return $this->attachments ?: array();
        }
    
    
        function getVar($tag) {
            switch ($tag) {
    
            case 'names':
                return $this->__toString();
    
                throw new OOBContent(OOBContent::FILES, $this->getAttachments());
    
            }
        }
    
        static function getVarScope() {
            return array(
    
                'names' => __('List of file names'),
    
                'files' => __('Attached files'),
            );
        }
    
    class ColorChoiceField extends FormField {
        static $widget = 'ColorPickerWidget';
    }
    
    
    class InlineFormData extends ArrayObject {
        var $_form;
    
        function __construct($form, array $data=array()) {
            parent::__construct($data);
            $this->_form = $form;
        }
    
        function getVar($tag) {
            foreach ($this->_form->getFields() as $f) {
                if ($f->get('name') == $tag)
                    return $this[$f->get('id')];
            }
        }
    }
    
    
    class InlineFormField extends FormField {
        static $widget = 'InlineFormWidget';
    
        var $_iform = null;
    
        function validateEntry($value) {
            if (!$this->getInlineForm()->isValid()) {
    
                $this->_errors[] = __('Correct any errors below and try again.');
    
            }
        }
    
        function parse($value) {
            // The InlineFieldWidget returns an array of cleaned data
            return $value;
        }
    
        function to_database($value) {
            return JsonDataEncoder::encode($value);
        }
    
        function to_php($value) {
            $data = JsonDataParser::decode($value);
            // The InlineFormData helps with the variable replacer API
            return new InlineFormData($this->getInlineForm(), $data);
        }
    
        function display($data) {
            $form = $this->getInlineForm();
            ob_start(); ?>
            <div><?php
            foreach ($form->getFields() as $field) { ?>
                <span style="display:inline-block;padding:0 5px;vertical-align:top">
                    <strong><?php echo Format::htmlchars($field->get('label')); ?></strong>
                    <div><?php
                        $value = $data[$field->get('id')];
                        echo $field->display($value); ?></div>
                </span><?php
            } ?>
            </div><?php
            return ob_get_clean();
        }
    
    
        function getInlineForm($data=false) {
    
            $form = $this->get('form');
            if (is_array($form)) {
    
                $form = new SimpleForm($form, $data ?: $this->value ?: $this->getSource());
    
                // Ensure unique, but predictable form and field IDs
                $form->setId(sprintf('%u', crc32($this->get('name')) >> 1));
    
            }
            return $form;
    
    class InlineDynamicFormField extends FormField {
        function getInlineForm($data=false) {
            if (!isset($this->_iform) || $data) {
                $config = $this->getConfiguration();
                $this->_iform = DynamicForm::lookup($config['form']);
                if ($data)
                    $this->_iform = $this->_iform->getForm($data);
    
            return $this->_iform;
    
        function getConfigurationOptions() {
            $forms = DynamicForm::objects()->filter(array('type'=>'G'))
                ->values_flat('id', 'title');
            $choices = array();
            foreach ($forms as $row) {
                list($id, $title) = $row;
                $choices[$id] = $title;
    
            return array(
                'form' => new ChoiceField(array(
                    'id'=>2, 'label'=>'Inline Form', 'required'=>true,
                    'default'=>'', 'choices'=>$choices
                )),
            );
    
    class InlineFormWidget extends Widget {
        function render($mode=false) {
            $form = $this->field->getInlineForm();
            if (!$form)
                return;
            // Handle first-step edits -- load data from $this->value
            if ($form instanceof DynamicForm && !$form->getSource())
                $form = $form->getForm($this->value);
            $inc = ($mode == 'client') ? CLIENTINC_DIR : STAFFINC_DIR;
            include $inc . 'templates/inline-form.tmpl.php';
        }
    
        function getValue() {
            $data = $this->field->getSource();
            if (!$data)
                return null;
            $form = $this->field->getInlineForm($data);
            if (!$form)
                return null;
            return $form->getClean();
    
    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')];
    
            elseif (isset($data[$this->field->get('id')]))
                return $data[$this->field->get('id')];
    
            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($options=array(), $extraConfig=false) {
    
    Jared Hancock's avatar
    Jared Hancock committed
            $config = $this->field->getConfiguration();
    
            if (is_array($extraConfig)) {
                foreach ($extraConfig as $k=>$v)
                    if (!isset($config[$k]) || !$config[$k])
                        $config[$k] = $v;
            }
    
    Jared Hancock's avatar
    Jared Hancock committed
            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['autofocus']))
                $autofocus = 'autofocus';
    
            if (isset($config['disabled']))
                $disabled = 'disabled="disabled"';
    
            if (isset($config['translatable']) && $config['translatable'])
                $translatable = 'data-translate-tag="'.$config['translatable'].'"';
    
            $type = static::$input_type;
            $types = array(
                'email' => 'email',
                'phone' => 'tel',
            );
            if ($type == 'text' && isset($types[$config['validator']]))
                $type = $types[$config['validator']];
    
            $placeholder = sprintf('placeholder="%s"', $this->field->getLocal('placeholder',
                $config['placeholder']));
    
            <input type="<?php echo $type; ?>"
    
                id="<?php echo $this->id; ?>"
    
                <?php echo implode(' ', array_filter(array(
    
                    $size, $maxlength, $classes, $autocomplete, $disabled,
    
                    $translatable, $placeholder, $autofocus))); ?>
    
    Jared Hancock's avatar
    Jared Hancock committed
                name="<?php echo $this->name; ?>"
                value="<?php echo Format::htmlchars($this->value); ?>"/>
            <?php
        }
    }
    
    
    
    class TextboxSelectionWidget extends TextboxWidget {
        //TODO: Support multi-input e.g comma separated inputs
    
        function render($options=array(), $extraConfig=array()) {
    
    
            if ($this->value && is_array($this->value))
                $this->value = current($this->value);
    
            parent::render($options);
        }
    
        function getValue() {
    
            $value = parent::getValue();
    
            if ($value && ($item=$this->field->lookupChoice((string) $value)))
                $value = $item;
    
    Jared Hancock's avatar
    Jared Hancock committed
    class PasswordWidget extends TextboxWidget {
        static $input_type = 'password';
    
    
        function render($mode=false, $extra=false) {
            $extra = array();
            if ($this->field->value) {
                $extra['placeholder'] = '••••••••••••';
            }
            return parent::render($mode, $extra);
        }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
        function parseValue() {
    
    Jared Hancock's avatar
    Jared Hancock committed
            // Show empty box unless failed POST
    
            if ($_SERVER['REQUEST_METHOD'] != 'POST'
    
                    || !$this->field->getForm()->isValid())
    
    Jared Hancock's avatar
    Jared Hancock committed
                $this->value = '';
        }
    }
    
    
    Jared Hancock's avatar
    Jared Hancock committed
    class TextareaWidget extends Widget {
    
        function render($options=array()) {
    
    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 = array('richtext', 'no-bar');
                $class[] = @$config['size'] ?: 'small';