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