Skip to content
Snippets Groups Projects
class.forms.php 131 KiB
Newer Older
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()) {

        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';
            $class = sprintf('class="%s"', implode(' ', $class));
            $this->value = Format::viewableImages($this->value);
        }
        if (isset($config['context']))
            $attrs['data-root-context'] = '"'.$config['context'].'"';
        <span style="display:inline-block;width:100%">
        <textarea <?php echo $rows." ".$cols." ".$maxlength." ".$class
                .' '.Format::array_implode('=', ' ', $attrs)
                .' 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($options=array()) {
        $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="tel" 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 {
    function render($options=array()) {

        $mode = isset($options['mode']) ? $options['mode'] : null;
            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'])
            ? $this->field->getLocal('prompt', $config['prompt'])
            : __('Select'
            /* Used as a default prompt for a custom drop-down list */);
        $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'];
            if (is_array($def_key))
                $def_key = key($def_key);
            $have_def = isset($choices[$def_key]);
            $def_val = $have_def ? $choices[$def_key] : $prompt;
        $values = $this->value;
        if (!is_array($values) && isset($values)) {
            $values = array($values => $this->field->getChoice($values));
        }
        if (!is_array($values))
Peter Rotich's avatar
Peter Rotich committed
            $values = $have_def ? array($def_key => $choices[$def_key]) : array();

        if (isset($config['classes']))
            $classes = 'class="'.$config['classes'].'"';
        ?>
        <select name="<?php echo $this->name; ?>[]"
            <?php echo implode(' ', array_filter(array($classes))); ?>
            id="<?php echo $this->id; ?>"
            <?php if (isset($config['data']))
              foreach ($config['data'] as $D=>$V)
                echo ' data-'.$D.'="'.Format::htmlchars($V).'"';
            ?>
            data-placeholder="<?php echo $prompt; ?>"
            <?php if ($config['multiselect'])
                echo ' multiple="multiple"'; ?>>
            <?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>
        $this->emitChoices($choices, $values, $have_def, $def_key); ?>
Jared Hancock's avatar
Jared Hancock committed
        </select>
        <?php
        if ($config['multiselect']) {
         ?>
        <script type="text/javascript">
        $(function() {
            $("#<?php echo $this->id; ?>")
            .select2({'minimumResultsForSearch':10, 'width': '350px'});
    function emitChoices($choices, $values=array(), $have_def=false, $def_key=null) {
        reset($choices);
        if (is_array(current($choices)) || current($choices) instanceof Traversable)
            return $this->emitComplexChoices($choices, $values, $have_def, $def_key);

        foreach ($choices as $key => $name) {
            if (!$have_def && $key == $def_key)
                continue; ?>
            <option value="<?php echo $key; ?>" <?php
                if (isset($values[$key])) echo 'selected="selected"';
            ?>><?php echo Format::htmlchars($name); ?></option>
    function emitComplexChoices($choices, $values=array(), $have_def=false, $def_key=null) {
        foreach ($choices as $label => $group) { ?>
            <optgroup label="<?php echo $label; ?>"><?php
            foreach ($group as $key => $name) {
                if (!$have_def && $key == $def_key)
                    continue; ?>
            <option value="<?php echo $key; ?>" <?php
                if (isset($values[$key])) echo 'selected="selected"';
            ?>><?php echo Format::htmlchars($name); ?></option>
        if (!($value = parent::getValue()))
            return null;

        if ($value && !is_array($value))
            $value = array($value);

        // Assume multiselect
        $values = array();
        $choices = $this->field->getChoices();

        if ($choices && is_array($value)) {
            // Complex choices
            if (is_array(current($choices))
                    || current($choices) instanceof Traversable) {
                foreach ($choices as $label => $group) {
                     foreach ($group as $k => $v)
                        if (in_array($k, $value))
                            $values[$k] = $v;
                }
            } else {
                foreach($value as $k => $v) {
                    if (isset($choices[$v]))
                        $values[$v] = $choices[$v];
                    elseif (($i=$this->field->lookupChoice($v)))
                        $values += $i;
                }

    function getJsValueGetter() {
        return '%s.find(":selected").val()';
    }
/**
 * A widget for the ChoiceField which will render a list of radio boxes or
 * checkboxes depending on the value of $config['multiple']. Complex choices
 * are also supported and will be rendered as divs.
 */
class BoxChoicesWidget extends Widget {
    function render($options=array()) {
        $this->emitChoices($this->field->getChoices());
    }

    function emitChoices($choices) {
      static $uid = 1;

      if (!isset($this->value))
          $this->value = $this->field->get('default');
      $config = $this->field->getConfiguration();
      $type = $config['multiple'] ? 'checkbox' : 'radio';

      $classes = array('checkbox');
      if (isset($config['classes']))
          $classes = array_merge($classes, (array) $config['classes']);

      foreach ($choices as $k => $v) {
          if (is_array($v)) {
              $this->renderSectionBreak($k);
              $this->emitChoices($v);
              continue;
          }
          $id = sprintf("%s-%s", $this->id, $uid++);
        <label class="<?php echo implode(' ', $classes); ?>"
            for="<?php echo $id; ?>">
        <input id="<?php echo $id; ?>" type="<?php echo $type; ?>"
            name="<?php echo $this->name; ?>[]" <?php
            if ($this->value[$k]) echo 'checked="checked"'; ?> value="<?php
            echo Format::htmlchars($k); ?>"/>
        <?php
        if ($v) {
            echo Format::viewableImages($v);
        } ?>
        </label>
<?php   }
    }

    function renderSectionBreak($label) { ?>
        <div><?php echo Format::htmlchars($label); ?></div>
<?php
    }

    function getValue() {
        $data = $this->field->getSource();
        if (count($data)) {
            if (!isset($data[$this->name]))
                return array();
            return $this->collectValues($data[$this->name], $this->field->getChoices());
        }
        return parent::getValue();
    }

    function collectValues($data, $choices) {
        $value = array();
        foreach ($choices as $k => $v) {
            if (is_array($v))
                $value = array_merge($value, $this->collectValues($data, $v));
            elseif (@in_array($k, $data))
                $value[$k] = $v;
        }
        return $value;
    }
}

/**
 * An extension to the BoxChoicesWidget which will render complex choices in
 * tabs.
 */
class TabbedBoxChoicesWidget extends BoxChoicesWidget {
    function render($options=array()) {
        $tabs = array();
        foreach ($this->field->getChoices() as $label=>$group) {
            if (is_array($group)) {
                $tabs[$label] = $group;
            }
            else {
                $this->emitChoices(array($label=>$group));
            }
        }
        if ($tabs) {
            ?>
            <div>
            <ul class="alt tabs">
<?php       $i = 0;
            foreach ($tabs as $label => $group) {
                $active = $i++ == 0; ?>
                <li <?php if ($active) echo 'class="active"';
                  ?>><a href="#<?php echo sprintf('%s-%s', $this->name, Format::slugify($label));
                  ?>"><?php echo Format::htmlchars($label); ?></a></li>
<?php       } ?>
            </ul>
<?php       $i = 0;
            foreach ($tabs as $label => $group) {
                $first = $i++ == 0; ?>
                <div class="tab_content <?php if (!$first) echo 'hidden'; ?>" id="<?php
                  echo sprintf('%s-%s', $this->name, Format::slugify($label));?>">
<?php           $this->emitChoices($group); ?>
                </div>
<?php       } ?>
            </div>
<?php   }
    }
}

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

    function render($options=array()) {
Jared Hancock's avatar
Jared Hancock committed
        $config = $this->field->getConfiguration();
        if (!isset($this->value))
            $this->value = $this->field->get('default');
        $classes = array('checkbox');
        if (isset($config['classes']))
            $classes = array_merge($classes, (array) $config['classes']);
        <label class="<?php echo implode(' ', $classes); ?>">
        <input id="<?php echo $this->id; ?>"
            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']) {
            echo Format::viewableImages($config['desc']);
        </label>
<?php
Jared Hancock's avatar
Jared Hancock committed
    }

    function getValue() {
        $data = $this->field->getSource();
        if (count($data)) {
            if (!isset($data[$this->name]))
                return false;
            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($options=array()) {
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);
            if ($config['gmt']) {
                // Convert to GMT time
                $tz = new DateTimeZone($cfg->getTimezone());
                $D = DateTime::createFromFormat('U', $this->value);
                $this->value += $tz->getOffset($D);
            }
Jared Hancock's avatar
Jared Hancock committed
            list($hr, $min) = explode(':', date('H:i', $this->value));
            $this->value = Format::date($this->value, false, false, 'UTC');
Jared Hancock's avatar
Jared Hancock committed
        }
        ?>
        <input type="text" name="<?php echo $this->name; ?>"
Peter Rotich's avatar
Peter Rotich committed
            id="<?php echo $this->id; ?>" style="display:inline-block;width:auto"
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(true); ?>')
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']) {
                // Convert to GMT time
                $tz = new DateTimeZone($cfg->getTimezone());
                $D = DateTime::createFromFormat('U', $datetime);
                $datetime -= $tz->getOffset($D);
            }
Jared Hancock's avatar
Jared Hancock committed
        return $datetime;
    }
}

class SectionBreakWidget extends Widget {
    function render($options=array()) {
        ?><div class="form-header section-break"><h3><?php
        echo Format::htmlchars($this->field->getLocal('label'));
        ?></h3><em><?php echo Format::htmlchars($this->field->getLocal('hint'));
        ?></em></div>
        <?php
    }
}

class ThreadEntryWidget extends Widget {
    function render($options=array()) {
        if ($options['client']) {
            $namespace = $options['draft-namespace']
                ?: 'ticket.client';
             $object_id = substr(session_id(), -12);
        } else {
            $namespace = $options['draft-namespace'] ?: 'ticket.staff';
        list($draft, $attrs) = Draft::getDraftAndDataAttrs($namespace, $object_id, $this->value);
        <textarea style="width:100%;" name="<?php echo $this->field->get('name'); ?>"
Peter Rotich's avatar
Peter Rotich committed
            placeholder="<?php echo Format::htmlchars($this->field->get('placeholder')); ?>"
            class="<?php if ($cfg->isRichTextEnabled()) echo 'richtext';
                ?> draft draft-delete" <?php echo $attrs; ?>
            cols="21" rows="8" style="width:80%;"><?php echo
            Format::htmlchars($this->value) ?: $draft; ?></textarea>
        $config = $this->field->getConfiguration();
        if (!$config['attachments'])
            return;
        $attachments = $this->getAttachments($config);
        print $attachments->render($options);
        foreach ($attachments->getMedia() as $type=>$urls) {
            foreach ($urls as $url)
                Form::emitMedia($url, $type);

    function getAttachments($config=false) {
        if (!$config)
            $config = $this->field->getConfiguration();

        $field = new FileUploadField(array(
            'name'=>'attach:' . $this->field->get('id'),
        $field->setForm($this->field->getForm());
        return $field;
class FileUploadWidget extends Widget {
    static $media = array(
        'css' => array(
            '/css/filedrop.css',
        ),
    );

    function render($options) {
        $config = $this->field->getConfiguration();
        $name = $this->field->getFormName();
        $id = substr(md5(spl_object_hash($this)), 10);
        $attachments = $this->field->getFiles();
        $mimetypes = array_filter($config['__mimetypes'],
            function($t) { return strpos($t, '/') !== false; }
        );
        $maxfilesize = ($config['size'] ?: 1048576) / 1048576;
        $files = $F = array();
        $new = array_fill_keys($this->field->getClean(), 1);
        foreach ($attachments as $f) {
            $F[] = $f->file;
            unset($new[$f->id]);
        }
        // Add in newly added files not yet saved (if redisplaying after an
        // error)
        if ($new) {
            $F = array_merge($F, AttachmentFile::objects()->filter(array(
                'id__in' => array_keys($new)))->all());
        }
        foreach ($F as $file) {
            $files[] = array(
                'id' => $file->getId(),
                'name' => $file->getName(),
                'type' => $file->getType(),
                'size' => $file->getSize(),
                'download_url' => $file->getDownloadUrl(),
            );
            ?>" class="filedrop"><div class="files"></div>
            <div class="dropzone"><i class="icon-upload"></i>
Jared Hancock's avatar
Jared Hancock committed
            <?php echo sprintf(
                __('Drop files here or %s choose them %s'),
                '<a href="#" class="manual">', '</a>'); ?>
        <input type="file" multiple="multiple"
            id="file-<?php echo $id; ?>" style="display:none;"
            accept="<?php echo implode(',', $config['__mimetypes']); ?>"/>
        <script type="text/javascript">
        $(function(){$('#<?php echo $id; ?> .dropzone').filedropbox({
          url: 'ajax.php/form/upload/<?php echo $this->field->get('id') ?>',
          link: $('#<?php echo $id; ?>').find('a.manual'),
          paramname: 'upload[]',
          fallback_id: 'file-<?php echo $id; ?>',
          allowedfileextensions: <?php echo JsonDataEncoder::encode(
            $config['__extensions'] ?: array()); ?>,
          allowedfiletypes: <?php echo JsonDataEncoder::encode(
          maxfiles: <?php echo $config['max'] ?: 20; ?>,
          maxfilesize: <?php echo $maxfilesize; ?>,
          name: '<?php echo $name; ?>[]',
          files: <?php echo JsonDataEncoder::encode($files); ?>
        });});
        </script>
<?php
    }

    function getValue() {
        $ids = array();
        // Handle manual uploads (IE<10)
        if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES[$this->name])) {
            foreach (AttachmentFile::format($_FILES[$this->name]) as $file) {
                    $F = $this->field->uploadFile($file);
                    $ids[] = $F->getId();
                }
                catch (FileUploadError $ex) {}
        // If no value was sent, assume an empty list
        $base = parent::getValue();
        if (!$base)
            return array();
        if (is_array($base)) {
            foreach ($base as $info) {
                @list($id, $name) = explode(',', $info, 2);
                // Keep the values as the IDs
                if ($name)
                    $ids[$name] = $id;
                else
                    $ids[] = $id;
            }
        }
class FileUploadError extends Exception {}

class FreeTextField extends FormField {
    static $widget = 'FreeTextWidget';
    protected $attachments;

    function getConfigurationOptions() {
        return array(
            'content' => new TextareaField(array(
                'configuration' => array('html' => true, 'size'=>'large'),
                'label'=>__('Content'), 'required'=>true, 'default'=>'',
                'hint'=>__('Free text shown in the form, such as a disclaimer'),
            )),
            'attachments' => new FileUploadField(array(
                'id'=>'attach',
                'label' => __('Attachments'),
                'name'=>'files',
                'configuration' => array('extensions'=>'')
            )),
        );
    }

    function hasData() {
        return false;
    }

    function isBlockLevel() {
        return true;
    }

    /* utils */

    function to_config($config) {
        if ($config && isset($config['attachments']))
            $keepers = $config['attachments'] = array_values($config['attachments']);
        $this->getAttachments()->keepOnlyFileIds($keepers);

        return $config;
    }

    function db_cleanup($field=false) {
        if ($field && $this->getFiles())
            $this->getAttachments()->deleteAll();
    }

    function getAttachments() {

        if (!isset($this->attachments))
            $this->attachments = GenericAttachments::forIdAndType($this->get('id'), 'I');

        return $this->attachments;
    }

    function getFiles() {

        if (!($attachments = $this->getAttachments()))
            return array();

        return $attachments->all();
    }

}

class FreeTextWidget extends Widget {
    function render($options=array()) {
        $config = $this->field->getConfiguration();
        $class = $config['classes'] ?: 'thread-body bleed';
        ?><div class="<?php echo $class; ?>"><?php
        if ($label = $this->field->getLocal('label')) { ?>
            <h3><?php
            echo Format::htmlchars($label);
        ?></h3><?php
        }
        if ($hint = $this->field->getLocal('hint')) { ?>
        <em><?php
            echo Format::htmlchars($hint);
        ?></em><?php
        } ?>
        <div><?php
            echo Format::viewableImages($config['content']); ?></div>
        </div>
        <?php
        if (($attachments=$this->field->getFiles())) { ?>
            <section class="freetext-files">
            <div class="title"><?php echo __('Related Resources'); ?></div>
            <?php foreach ($attachments as $attach) { ?>
                <div class="file">
                <a href="<?php echo $attach->file->getDownloadUrl(); ?>"
                    target="_blank" download="<?php echo $attach->file->getDownloadUrl();
                    ?>" class="truncate no-pjax">
                    <i class="icon-file"></i>
                    <?php echo Format::htmlchars($attach->getFilename()); ?>
                </a>
                </div>
            <?php } ?>
        </section>
        <?php }
class VisibilityConstraint {
    static $operators = array(
        'eq' => 1,
    );

    const HIDDEN =      0x0001;
    const VISIBLE =     0x0002;

    var $initial;
    var $constraint;

    function __construct($constraint, $initial=self::VISIBLE) {
        $this->constraint = $constraint;
        $this->initial = $initial;
    }

    function emitJavascript($field) {
        $func = 'recheck';
        $form = $field->getForm();
?>
    <script type="text/javascript">
      !(function() {
        var <?php echo $func; ?> = function() {
          var target = $('#field<?php echo $field->getWidget()->id; ?>');

<?php   $fields = $this->getAllFields($this->constraint);
        foreach ($fields as $f) {
            $field = $form->getField($f);
            echo sprintf('var %1$s = $("#%1$s");',
                $field->getWidget()->id);
        }
        $expression = $this->compileQ($this->constraint, $form);
?>
          if (<?php echo $expression; ?>)
Peter Rotich's avatar
Peter Rotich committed
            target.slideDown('fast', function (){
                $(this).trigger('show');
                });
          else
            target.slideUp('fast', function (){
                $(this).trigger('hide');
                });
        };

<?php   foreach ($fields as $f) {
            $w = $form->getField($f)->getWidget();
?>
        $('#<?php echo $w->id; ?>').on('change', <?php echo $func; ?>);
        $('#field<?php echo $w->id; ?>').on('show hide', <?php
                echo $func; ?>);
<?php   } ?>
      })();
    </script><?php
    }

    /**
     * Determines if the field was visible when the form was submitted
     */
    function isVisible($field) {
        return $this->compileQPhp($this->constraint, $field);
    }

    static function splitFieldAndOp($field) {
        $op = substr($field, strrpos($field, '__') + 2);
        if (isset(static::$operators[$op]))
            $field = substr($field, 0, strrpos($field, '__'));
        return array($field, $op);
    }

    function compileQPhp(Q $Q, $field) {
        if (!($form = $field->getForm())) {
            return $this->initial == self::VISIBLE;
        }
        $expr = array();
        foreach ($Q->constraints as $c=>$value) {
            if ($value instanceof Q) {
                $expr[] = $this->compileQPhp($value, $field);
            }
            else {
                @list($f, $op) = self::splitFieldAndOp($c);
                $field = $form->getField($f);
                $wval = $field->getClean();
                switch ($op) {
                case 'eq':
                case null:
                    $expr[] = ($wval == $value && $field->isVisible());
                }
            }
        }
        $glue = $Q->isOred()
            ? function($a, $b) { return $a || $b; }
            : function($a, $b) { return $a && $b; };
        $initial = !$Q->isOred();
        $expression = array_reduce($expr, $glue, $initial);
        if ($Q->isNegated)
            $expression = !$expression;
        return $expression;
    }

    function getAllFields(Q $Q, &$fields=array()) {
        foreach ($Q->constraints as $c=>$value) {
            if ($c instanceof Q) {
                $this->getAllFields($c, $fields);
            }
            else {
                @list($f) = self::splitFieldAndOp($c);
                $fields[$f] = true;
            }
        }
        return array_keys($fields);
    }

    function compileQ($Q, $form) {
        $expr = array();
        foreach ($Q->constraints as $c=>$value) {
            if ($value instanceof Q) {
                $expr[] = $this->compileQ($value, $form);
            }
            else {
                list($f, $op) = self::splitFieldAndOp($c);
                $widget = $form->getField($f)->getWidget();
                $id = $widget->id;
                switch ($op) {
                case 'eq':
                    $expr[] = sprintf('(%s.is(":visible") && %s)',
                            $id,
                            sprintf('%s == %s',
                                sprintf($widget->getJsValueGetter(), $id),
                                JsonDataEncoder::encode($value))
                            );
                }
            }
        }
        $glue = $Q->isOred() ? ' || ' : ' && ';
        $expression = implode($glue, $expr);
        if (count($expr) > 1)
            $expression = '('.$expression.')';
        if ($Q->isNegated)
            $expression = '!'.$expression;
        return $expression;
    }
}

Peter Rotich's avatar
Peter Rotich committed
class AssignmentForm extends Form {

    static $id = 'assign';
    var $_assignee = null;
Peter Rotich's avatar
Peter Rotich committed
    var $_dept = null;
Peter Rotich's avatar
Peter Rotich committed

    function __construct($source=null, $options=array()) {
        parent::__construct($source, $options);
Peter Rotich's avatar
Peter Rotich committed
        // Department of the object -- if necessary to limit assinees
        if (isset($options['dept']))
            $this->_dept = $options['dept'];
Peter Rotich's avatar
Peter Rotich committed
    }

    function getFields() {

        if ($this->fields)
            return $this->fields;

        $fields = array(
            'assignee' => new AssigneeField(array(
                    'id'=>1,
                    'label' => __('Assignee'),
                    'flags' => hexdec(0X450F3),
                    'required' => true,
                    'validator-error' => __('Assignee selection required'),
Peter Rotich's avatar
Peter Rotich committed
                    'configuration' => array(
                        'criteria' => array(
                            'available' => true,
                            ),
                        'dept' => $this->_dept ?: null,
                       ),
Peter Rotich's avatar
Peter Rotich committed
                    )
                ),
            'comments' => new TextareaField(array(
                    'id' => 2,
                    'label'=> '',
                    'required'=>false,
                    'default'=>'',
                    'configuration' => array(
                        'html' => true,
                        'size' => 'small',
Peter Rotich's avatar
Peter Rotich committed
                        'placeholder' => __('Optional reason for the assignment'),
                        ),
                    )
                ),
            );

        $this->setFields($fields);

        return $this->fields;
    }

    function isValid() {

        if (!parent::isValid())
            return false;

        // Do additional assignment validation
        if (!($assignee = $this->getAssignee())) {
            $this->getField('assignee')->addError(
                    __('Unknown assignee'));
        } elseif ($assignee instanceof Staff) {
            // Make sure the agent is available
            if (!$assignee->isAvailable())
                $this->getField('assignee')->addError(
                        __('Agent is unavailable for assignment')
                        );
        }

        return !$this->errors();
    }

    function render($options) {

        switch(strtolower($options['template'])) {
        case 'simple':
            $inc = STAFFINC_DIR . 'templates/dynamic-form-simple.tmpl.php';
            break;
        default:
            throw new Exception(sprintf(__('%s: Unknown template style %s'),
                        'FormUtils', $options['template']));