Newer
Older
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;
}
return $values;
}
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) {
if (!isset($this->value))
$this->value = $this->field->get('default');
$config = $this->field->getConfiguration();
$type = $config['multiple'] ? 'checkbox' : 'radio';
if (isset($config['classes']))
$classes = 'class="'.$config['classes'].'"';
foreach ($choices as $k => $v) {
if (is_array($v)) {
$this->renderSectionBreak($k);
$this->emitChoices($v);
continue;
}
?>
<label <?php echo $classes; ?>
for="<?php echo $id; ?>" style="display:block;">
<input id="<?php echo $id; ?>" style="vertical-align:top;"
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) { ?>
<em style="display:inline-block;vertical-align:baseline;width:90%;width:calc(100% - 25px);"><?php
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
echo Format::viewableImages($v); ?></em>
<?php } ?>
</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 }
}
}
class CheckboxWidget extends Widget {
function __construct($field) {
parent::__construct($field);
$this->name = '_field-checkboxes';
}
if (!isset($this->value))
$this->value = $this->field->get('default');
if (isset($config['classes']))
$classes = 'class="'.$config['classes'].'"';
<div <?php echo implode(' ', array_filter(array($classes))); ?>>
<label class="checkbox">
<input id="<?php echo $this->id; ?>"
type="checkbox" name="<?php echo $this->name; ?>[]" <?php
if ($this->value) echo 'checked="checked"'; ?> value="<?php
echo $this->field->get('id'); ?>"/>
<?php
if ($config['desc']) {
echo Format::viewableImages($config['desc']);
$data = $this->field->getSource();
if (count($data)) {
if (!isset($data[$this->name]))
return @in_array($this->field->get('id'), $data[$this->name]);
function getJsValueGetter() {
return '%s.is(":checked")';
}
class DatetimePickerWidget extends Widget {
$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);
}
list($hr, $min) = explode(':', date('H:i', $this->value));
$this->value = Format::date($this->value, false, false, 'UTC');
}
?>
<input type="text" name="<?php echo $this->name; ?>"
id="<?php echo $this->id; ?>"
value="<?php echo Format::htmlchars($this->value); ?>" size="12"
autocomplete="off" class="dp" />
<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); ?>')
});
});
</script>
<?php
if ($config['time'])
// TODO: Add time picker -- requires time picker or selection with
// Misc::timeDropdown
echo ' ' . 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);
}
class SectionBreakWidget extends Widget {
?><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 {
$object_id = false;
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);
<span class="required"><?php
echo Format::htmlchars($this->field->getLocal('label'));
?>: <span class="error">*</span></span><br/>
<textarea style="width:100%;" name="<?php echo $this->field->get('name'); ?>"
placeholder="<?php echo Format::htmlchars($this->field->get('hint')); ?>"
class="<?php if ($cfg->isRichTextEnabled()) echo 'richtext';
?> draft draft-delete" <?php echo $attrs; ?>
cols="21" rows="8" style="width:80%;"><?php echo
$draft ?: Format::htmlchars($this->value); ?></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(
'id'=>'attach',
'name'=>'attach:' . $this->field->get('id'),
'configuration'=>$config)
);
$field->setForm($this->field->getForm());
return $field;
class FileUploadWidget extends Widget {
static $media = array(
'css' => array(
'/css/filedrop.css',
),
);
$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;
}
// 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(),
);
?><div id="<?php echo $id;
?>" class="filedrop"><div class="files"></div>
<div class="dropzone"><i class="icon-upload"></i>
<?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']); ?>"/>
$(function(){$('#<?php echo $id; ?> .dropzone').filedropbox({
url: 'ajax.php/form/upload/<?php echo $this->field->get('id') ?>',
link: $('#<?php echo $id; ?>').find('a.manual'),
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();
// If no value was sent, assume an empty list
$base = parent::getValue();
if (!$base)
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';
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',
'name'=>'files',
'configuration' => array('extensions'=>'')
)),
);
}
function hasData() {
return false;
}
function isBlockLevel() {
return true;
}
/* utils */
function to_config($config) {
$keepers = array();
if ($config && isset($config['attachments']))
foreach ($config['attachments'] as $fid)
$keepers[] = $fid;
$this->getAttachments()->keepOnlyFileIds($keepers);
return $config;
}
function db_cleanup($field=false) {
$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 {
$class = $config['classes'] ?: 'thread-body'
?><div class="<?php echo $class; ?>" style="padding:0"><?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) { ?>
<a href="<?php echo $attach->file->getDownloadUrl(); ?>"
target="_blank" download="<?php echo $attach->file->getDownloadUrl();
?>" class="truncate no-pjax">
<?php echo Format::htmlchars($attach->getFilename()); ?>
</a>
</div>
<?php } ?>
</section>
<?php }
class VisibilityConstraint {
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");',
}
$expression = $this->compileQ($this->constraint, $form);
?>
if (<?php echo $expression; ?>)
$(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);
}
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) = explode('__', $c, 2);
$field = $form->getField($f);
$wval = $field->getClean();
switch ($op) {
case 'eq':
case null:
$expr[] = ($wval == $value && $field->isVisible());
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
}
}
}
$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, $op) = explode('__', $c, 2);
$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) = explode('__', $c, 2);
$widget = $form->getField($f)->getWidget();
$id = $widget->id;
$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;
}
}
class AssignmentForm extends Form {
static $id = 'assign';
var $_assignee = null;
function __construct($source=null, $options=array()) {
parent::__construct($source, $options);
// Department of the object -- if necessary to limit assinees
if (isset($options['dept']))
$this->_dept = $options['dept'];
}
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'),
'configuration' => array(
'criteria' => array(
'available' => true,
),
'dept' => $this->_dept ?: null,
),
)
),
'comments' => new TextareaField(array(
'id' => 2,
'label'=> '',
'required'=>false,
'default'=>'',
'configuration' => array(
'html' => true,
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
'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']));
}
$form = $this;
include $inc;
}
function getAssignee() {
if (!isset($this->_assignee)) {
$value = $this->getField('assignee')->getClean();
if ($value[0] == 's')
$this->_assignee = Staff::lookup(substr($value, 1));
elseif ($value[0] == 't')
$this->_assignee = Team::lookup(substr($value, 1));
}
return $this->_assignee;
}
function assigneeCriteria() {
$dept = $this->id;
return function () use($dept) {
return array('dept_id' =>$dept);
};
}
}
class TransferForm extends Form {
static $id = 'transfer';
var $_dept = null;
function __construct($source=null, $options=array()) {
parent::__construct($source, $options);
}
function getFields() {
if ($this->fields)
return $this->fields;
$fields = array(
'dept' => new DepartmentField(array(
'id'=>1,
'label' => __('Department'),
'flags' => hexdec(0X450F3),
'required' => true,
'validator-error' => __('Department selection required'),
)
),
'comments' => new TextareaField(array(
'id' => 2,
'label'=> '',
'required'=>false,
'default'=>'',
'configuration' => array(
'html' => true,
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
'placeholder' => __('Optional reason for the transfer'),
),
)
),
);
$this->setFields($fields);
return $this->fields;
}
function isValid() {
if (!parent::isValid())
return false;
// Do additional validations
if (!($dept = $this->getDept()))
$this->getField('dept')->addError(
__('Unknown department'));
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'),
get_class(), $options['template']));
}
$form = $this;
include $inc;
}
function getDept() {
if (!isset($this->_dept)) {
if (($id = $this->getField('dept')->getClean()))
$this->_dept = Dept::lookup($id);
}
return $this->_dept;
}
}
/**
* FieldUnchanged
*
* Thrown in the to_database() method to indicate the value should not be
* saved in the database (it wasn't changed in the request)
*/
class FieldUnchanged extends Exception {}