Newer
Older
)),
'format' => new ChoiceField(array(
'label'=>__('Display format'), 'default'=>'us',
'choices'=>array(''=>'-- '.__('Unformatted').' --',
'us'=>__('United States')),
function hasSpecialSearch() {
return false;
}
function validateEntry($value) {
parent::validateEntry($value);
$config = $this->getConfiguration();
# Run validator against $this->value for email type
list($phone, $ext) = explode("X", $value, 2);
if ($phone && (
!is_numeric($phone) ||
strlen($phone) < $config['digits']))
$this->_errors[] = __("Enter a valid phone number");
$this->_errors[] = __("Enter a valid phone extension");
$this->_errors[] = __("Enter a phone number for the extension");
function parse($value) {
// NOTE: Value may have a legitimate 'X' to separate the number and
// extension parts. Don't remove the 'X'
$val = preg_replace('/[^\dX]/', '', $value);
// Pass completely-incorrect string for validation error
return $val ?: $value;
$config = $this->getConfiguration();
list($phone, $ext) = explode("X", $value, 2);
switch ($config['format']) {
case 'us':
$phone = Format::phone($phone);
break;
}
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);
}
return ($value) ? __('Yes') : __('No');
function getSearchMethods() {
return array(
'set' => __('checked'),
'set.not' => __('unchecked'),
);
}
function getSearchMethodWidgets() {
return array(
'set' => null,
'set.not' => null,
);
}
}
class ChoiceField extends FormField {
static $widget = 'ChoicesWidget';
function getConfigurationOptions() {
return array(
'choices' => new TextareaField(array(
'id'=>1, 'label'=>__('Choices'), 'required'=>false, 'default'=>'',
'hint'=>__('List choices, one per line. To protect against spelling changes, specify key:value names to preserve entries if the list item names change'),
'configuration'=>array('html'=>false)
)),
'default' => new TextboxField(array(
'id'=>3, 'label'=>__('Default'), 'required'=>false, 'default'=>'',
'hint'=>__('(Enter a key). Value selected from the list initially'),
'configuration'=>array('size'=>20, 'length'=>40),
)),
'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,
'translatable'=>$this->getTranslateTag('prompt'),
),
'multiselect' => new BooleanField(array(
'id'=>1, 'label'=>'Multiselect', 'required'=>false, 'default'=>false,
'configuration'=>array(
'desc'=>'Allow multiple selections')
)),
return $this->to_php($value ?: null);
}
function to_database($value) {
if (!is_array($value)) {
$choices = $this->getChoices();
if (isset($choices[$value]))
$value = array($value => $choices[$value]);
}
if (is_array($value))
return $value;
}
function to_php($value) {
if (is_string($value))
$array = JsonDataParser::parse($value) ?: $value;
else
$array = $value;
$config = $this->getConfiguration();
if (!$config['multiselect']) {
if (is_array($array) && count($array) < 2) {
reset($array);
return key($array);
}
if (is_string($array) && strpos($array, ',') !== false) {
list($array,) = explode(',', $array, 2);
}
$selection = $this->getChoice($value);
return is_array($selection)
? (implode(', ', array_filter($selection)) ?: $value)
: (string) $selection;
$selection = array();
if ($value && is_array($value)) {
} elseif (isset($choices[$value]))
$selection[] = $choices[$value];
elseif ($this->get('default'))
$selection[] = $choices[$this->get('default')];
return $selection;
function getChoices($verbose=false) {
if ($this->_choices === null || $verbose) {
// 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);
}
// Add old selections if nolonger available
// This is necessary so choices made previously can be
// retained
$values = ($a=$this->getAnswer()) ? $a->getValue() : array();
if ($values && is_array($values)) {
foreach ($values as $k => $v) {
if (!isset($this->_choices[$k])) {
if ($verbose) $v .= ' (retired)';
$this->_choices[$k] = $v;
}
}
}
}
}
return $this->_choices;
}
function getSearchMethods() {
return array(
'set' => __('has a value'),
'notset' => __('does not have a value'),
'includes' => __('includes'),
'!includes' => __('does not include'),
);
}
function getSearchMethodWidgets() {
return array(
'set' => null,
'includes' => array('ChoiceField', array(
'choices' => $this->getChoices(),
'configuration' => array('multiselect' => true),
)),
'!includes' => array('ChoiceField', array(
'choices' => $this->getChoices(),
'configuration' => array('multiselect' => true),
)),
function getSearchQ($method, $value, $name=false) {
$name = $name ?: $this->get('name');
switch ($method) {
case '!includes':
return Q::not(array("{$name}__in" => array_keys($value)));
case 'includes':
return new Q(array("{$name}__in" => array_keys($value)));
default:
return parent::getSearchQ($method, $value, $name);
}
}
}
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 toString($value) {
global $cfg;
$config = $this->getConfiguration();
// If GMT is set, convert to local time zone. Otherwise, leave
// unchanged (default TZ is UTC)
if ($config['time'])
return Format::datetime($value, false, !$config['gmt'] ? 'UTC' : false);
return Format::date($value, false, false, !$config['gmt'] ? 'UTC' : false);
function export($value) {
$config = $this->getConfiguration();
if (!$value)
return '';
else
return Format::date($value, false, 'y-MM-dd HH:mm:ss', !$config['gmt'] ? 'UTC' : false);
function getConfigurationOptions() {
return array(
'time' => new BooleanField(array(
'id'=>1, 'label'=>__('Time'), 'required'=>false, 'default'=>false,
'desc'=>__('Show time selection with date picker')))),
'id'=>2, 'label'=>__('Timezone Aware'), 'required'=>false,
'desc'=>__("Show date/time relative to user's timezone")))),
'id'=>3, 'label'=>__('Earliest'), 'required'=>false,
'hint'=>__('Earliest date selectable'))),
'id'=>4, 'label'=>__('Latest'), 'required'=>false,
'default'=>null, 'hint'=>__('Latest date selectable'))),
'id'=>5, 'label'=>__('Allow Future Dates'), 'required'=>false,
'desc'=>__('Allow entries into the future' /* Used in the date field */)),
)),
);
}
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');
function getSearchMethods() {
return array(
'set' => __('has a value'),
'notset' => __('does not have a value'),
'equal' => __('on'),
'notequal' => __('not on'),
'before' => __('before'),
'after' => __('after'),
'between' => __('between'),
'ndaysago' => __('in the last n days'),
'ndays' => __('in the next n days'),
);
}
function getSearchMethodWidgets() {
$config_notime = $config = $this->getConfiguration();
$config_notime['time'] = false;
return array(
'set' => null,
'notset' => null,
'equal' => array('DatetimeField', array(
'configuration' => $config_notime,
)),
'notequal' => array('DatetimeField', array(
'configuration' => $config_notime,
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
)),
'before' => array('DatetimeField', array(
'configuration' => $config,
)),
'after' => array('DatetimeField', array(
'configuration' => $config,
)),
'between' => array('InlineformField', array(
'form' => array(
'left' => new DatetimeField(),
'text' => new FreeTextField(array(
'configuration' => array('content' => 'and'))
),
'right' => new DatetimeField(),
),
)),
'ndaysago' => array('InlineformField', array(
'form' => array(
'until' => new TextboxField(array(
'configuration' => array('validator'=>'number', 'size'=>4))
),
'text' => new FreeTextField(array(
'configuration' => array('content' => 'days'))
),
),
)),
'ndays' => array('InlineformField', array(
'form' => array(
'until' => new TextboxField(array(
'configuration' => array('validator'=>'number', 'size'=>4))
),
'text' => new FreeTextField(array(
'configuration' => array('content' => 'days'))
),
),
)),
);
}
function getSearchQ($method, $value, $name=false) {
$name = $name ?: $this->get('name');
switch ($method) {
case 'after':
return new Q(array("{$name}__gte" => $value));
case 'before':
return new Q(array("{$name}__lt" => $value));
case 'between':
return new Q(array(
"{$name}__gte" => $value['left'],
"{$name}__lte" => $value['right'],
));
case 'ndaysago':
return new Q(array(
"{$name}__lt" => SqlFunction::NOW(),
"{$name}__gte" => SqlExpression::minus(SqlFunction::NOW(), SqlInterval::DAY($value['until'])),
));
case 'ndays':
return new Q(array(
"{$name}__gt" => SqlFunction::NOW(),
"{$name}__lte" => SqlExpression::plus(SqlFunction::NOW(), SqlInterval::DAY($value['until'])),
));
default:
return parent::getSearchQ($method, $value, $name);
}
}
/**
* This is kind-of a special field that doesn't have any data. It's used as
* a field to provide a horizontal section break in the display of a form
*/
class SectionBreakField extends FormField {
static $widget = 'SectionBreakWidget';
function hasData() {
return false;
}
function isBlockLevel() {
return true;
}
}
class ThreadEntryField extends FormField {
static $widget = 'ThreadEntryWidget';
function isChangeable() {
return false;
}
function isBlockLevel() {
return true;
}
function isPresentationOnly() {
return true;
}
function hasSpecialSearch() {
return false;
}
function getConfigurationOptions() {
global $cfg;
$attachments = new FileUploadField();
$fileupload_config = $attachments->getConfigurationOptions();
if ($cfg->getAllowedFileTypes())
$fileupload_config['extensions']->set('default', $cfg->getAllowedFileTypes());
foreach ($fileupload_config as $C) {
$C->set('visibility', new VisibilityConstraint(new Q(array(
'attachments__eq'=>true,
)), VisibilityConstraint::HIDDEN));
}
return array(
'attachments' => new BooleanField(array(
'label'=>__('Enable Attachments'),
'default'=>$cfg->allowAttachments(),
'configuration'=>array(
'desc'=>__('Enables attachments on tickets, regardless of channel'),
'validators' => function($self, $value) {
if (!ini_get('file_uploads'))
$self->addError(__('The "file_uploads" directive is disabled in php.ini'));
}
+ $fileupload_config;
function isAttachmentsEnabled() {
$config = $this->getConfiguration();
return $config['attachments'];
}
}
class PriorityField extends ChoiceField {
function getWidget() {
$widget = parent::getWidget();
if ($widget->value instanceof Priority)
$widget->value = $widget->value->getId();
return $widget;
}
function hasIdValue() {
return true;
}
global $cfg;
$this->ht['default'] = $cfg->getDefaultPriorityId();
$sql = 'SELECT priority_id, priority_desc FROM '.PRIORITY_TABLE
.' ORDER BY priority_urgency DESC';
if (!($res = db_query($sql)))
return $choices;
while ($row = db_fetch_row($res))
$choices[$row[0]] = $row[1];
return $choices;
}
function parse($id) {
return $this->to_php(null, $id);
}
function to_php($value, $id=false) {
if (is_array($id)) {
reset($id);
$id = key($id);
}
return Priority::lookup($id);
}
function to_database($prio) {
return ($prio instanceof Priority)
? array($prio->getDesc(), $prio->getId())
: $prio;
}
function toString($value) {
return ($value instanceof Priority) ? $value->getDesc() : $value;
}
function searchable($value) {
// Priority isn't searchable this way
return null;
}
function getConfigurationOptions() {
return array(
'prompt' => new TextboxField(array(
'id'=>2, 'label'=>__('Prompt'), 'required'=>false, 'default'=>'',
'hint'=>__('Leading text shown before a value is selected'),
'configuration'=>array('size'=>40, 'length'=>40),
)),
);
FormField::addFieldTypes(/*@trans*/ 'Dynamic Fields', function() {
'priority' => array(__('Priority Level'), PriorityField),
class TicketStateField extends ChoiceField {
'name' => /* @trans, @context "ticket state name" */ 'Open',
'verb' => /* @trans, @context "ticket state action" */ 'Open'
'name' => /* @trans, @context "ticket state name" */ 'Closed',
'verb' => /* @trans, @context "ticket state action" */ 'Close'
// Private states
static $_privatestates = array(
'name' => /* @trans, @context "ticket state name" */ 'Archived',
'verb' => /* @trans, @context "ticket state action" */ 'Archive'
'name' => /* @trans, @context "ticket state name" */ 'Deleted',
'verb' => /* @trans, @context "ticket state action" */ 'Delete'
);
function hasIdValue() {
return true;
}
function isChangeable() {
return false;
}
function getChoices() {
static $_choices;
if (!isset($_choices)) {
// Translate and cache the choices
foreach (static::$_states as $k => $v)
$_choices[$k] = _P('ticket state name', $v['name']);
$this->ht['default'] = '';
}
return $_choices;
}
function getChoice($state) {
if ($state && is_array($state))
$state = key($state);
if (isset(static::$_states[$state]))
return _P('ticket state name', static::$_states[$state]['name']);
if (isset(static::$_privatestates[$state]))
return _P('ticket state name', static::$_privatestates[$state]['name']);
}
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']);
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
}
FormField::addFieldTypes('Dynamic Fields', function() {
return array(
'state' => array('Ticket State', TicketStateField, false),
);
});
class TicketFlagField extends ChoiceField {
// Supported flags (TODO: move to configurable custom list)
static $_flags = array(
'onhold' => array(
'flag' => 1,
'name' => 'Onhold',
'states' => array('open'),
),
'overdue' => array(
'flag' => 2,
'name' => 'Overdue',
'states' => array('open'),
),
'answered' => array(
'flag' => 4,
'name' => 'Answered',
'states' => array('open'),
)
);
var $_choices;
function hasIdValue() {
return true;
}
function isChangeable() {
return true;
}
function getChoices() {
$this->ht['default'] = '';
if (!$this->_choices) {
foreach (static::$_flags as $k => $v)
$this->_choices[$k] = $v['name'];
}
return $this->_choices;
}
function getConfigurationOptions() {
return array(
'prompt' => new TextboxField(array(
'id'=>2, 'label'=>'Prompt', 'required'=>false, 'default'=>'',
'hint'=>'Leading text shown before a value is selected',
'configuration'=>array('size'=>40, 'length'=>40),
)),
);
}
}
FormField::addFieldTypes('Dynamic Fields', function() {
return array(
'flags' => array('Ticket Flags', TicketFlagField, false),
);
});
class FileUploadField extends FormField {
static $widget = 'FileUploadWidget';
protected $attachments;
static function getFileTypes() {
static $filetypes;
if (!isset($filetypes))
$filetypes = YamlDataParser::load(INCLUDE_DIR . '/config/filetype.yaml');
return $filetypes;
}
function getConfigurationOptions() {
// Compute size selections
$sizes = array('262144' => '— '.__('Small').' —');
$next = 512 << 10;
$max = strtoupper(ini_get('upload_max_filesize'));
$limit = (int) $max;
if (!$limit) $limit = 2 << 20; # 2M default value
elseif (strpos($max, 'K')) $limit <<= 10;
elseif (strpos($max, 'M')) $limit <<= 20;
elseif (strpos($max, 'G')) $limit <<= 30;
while ($next <= $limit) {
// Select the closest, larger value (in case the
// current value is between two)
$sizes[$next] = Format::file_size($next);
$next *= 2;
}
// Add extra option if top-limit in php.ini doesn't fall
// at a power of two
if ($next < $limit * 2)
$sizes[$limit] = Format::file_size($limit);
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(),
'mimetypes' => new ChoiceField(array(
'label'=>__('Restrict by File Type'),
'hint'=>__('Optionally, choose acceptable file types.'),
'required'=>false,
'choices'=>$types,
'configuration'=>array('multiselect'=>true,'prompt'=>__('No restrictions'))
'extensions' => new TextareaField(array(
'label'=>__('Additional File Type Filters'),
'hint'=>__('Optionally, enter comma-separated list of additional file types, by extension. (e.g .doc, .pdf).'),
'configuration'=>array('html'=>false, 'rows'=>2),
)),
'max' => new TextboxField(array(
'label'=>__('Maximum Files'),
'hint'=>__('Users cannot upload more than this many files.'),
'default'=>false,
'required'=>false,
'validator'=>'number',
'configuration'=>array('size'=>8, 'length'=>4, 'placeholder'=>__('No limit')),
function hasSpecialSearch() {
return false;
}
/**
* Called from the ajax handler for async uploads via web clients.
*/
function ajaxUpload($bypass=false) {
$config = $this->getConfiguration();
$files = AttachmentFile::format($_FILES['upload'],
// For numeric fields assume configuration exists
!is_numeric($this->get('id')));
if (count($files) != 1)
Http::response(400, 'Send one file at a time');
$file = array_shift($files);
$file['name'] = urldecode($file['name']);
if (!$bypass && !$this->isValidFileType($file['name'], $file['type']))
Http::response(415, 'File type is not allowed');
$config = $this->getConfiguration();
if (!$bypass && $file['size'] > $config['size'])
Http::response(413, 'File is too large');
if (!($id = AttachmentFile::upload($file)))
Http::response(500, 'Unable to store file: '. $file['error']);
/**
* Called from FileUploadWidget::getValue() when manual upload is used
* for browsers which do not support the HTML5 way of uploading async.
*/
function uploadFile($file) {
if (!$this->isValidFileType($file['name'], $file['type']))
throw new FileUploadError(__('File type is not allowed'));
$config = $this->getConfiguration();
if ($file['size'] > $config['size'])
throw new FileUploadError(__('File size is too large'));
return AttachmentFile::upload($file);
}
/**
* Called from API and email routines and such to handle attachments
* sent other than via web upload
*/
function uploadAttachment(&$file) {
if (!$this->isValidFileType($file['name'], $file['type']))
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
throw new FileUploadError(__('File type is not allowed'));
if (is_callable($file['data']))
$file['data'] = $file['data']();
if (!isset($file['size'])) {
// bootstrap.php include a compat version of mb_strlen
if (extension_loaded('mbstring'))
$file['size'] = mb_strlen($file['data'], '8bit');
else
$file['size'] = strlen($file['data']);
}
$config = $this->getConfiguration();
if ($file['size'] > $config['size'])
throw new FileUploadError(__('File size is too large'));
if (!$id = AttachmentFile::save($file))
throw new FileUploadError(__('Unable to save file'));
return $id;
}
function isValidFileType($name, $type=false) {
$config = $this->getConfiguration();
// Check MIME type - file ext. shouldn't be solely trusted.
if ($type && $config['__mimetypes']
&& in_array($type, $config['__mimetypes']))
// Return true if all file types are allowed (.*)
if (!$config['__extensions'] || in_array('.*', $config['__extensions']))
$allowed = $config['__extensions'];
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
return ($ext && is_array($allowed) && in_array(".$ext", $allowed));
}
function getFiles() {
if (!isset($this->attachments) && ($a = $this->getAnswer())
&& ($e = $a->getEntry()) && ($e->get('id'))
) {
$this->attachments = new GenericAttachments(
// Combine the field and entry ids to make the key
sprintf('%u', crc32('E'.$this->get('id').$e->get('id'))),
'E');
}
return $this->attachments ? $this->attachments->getAll() : array();
}
function getConfiguration() {
$config = parent::getConfiguration();
$_types = self::getFileTypes();
$mimetypes = array();
$extensions = array();
if (isset($config['mimetypes']) && is_array($config['mimetypes'])) {
foreach ($config['mimetypes'] as $type=>$desc) {
foreach ($_types[$type]['types'] as $mime=>$exts) {
$mimetypes[$mime] = true;
if (is_array($exts))
foreach ($exts as $ext)
$extensions['.'.$ext] = true;
}
}
}
if (strpos($config['extensions'], '.*') !== false)
$config['extensions'] = '';
if (is_string($config['extensions'])) {
foreach (preg_split('/\s+/', str_replace(',',' ', $config['extensions'])) as $ext) {
if (!$ext) {
continue;
}
elseif (strpos($ext, '/')) {
}
else {
if ($ext[0] != '.')
$ext = '.' . $ext;
// Add this to the MIME types list so it can be exported to
// the @accept attribute
if (!isset($extensions[$ext]))
$mimetypes[$ext] = true;
$extensions[$ext] = true;
}
$config['__extensions'] = array_keys($extensions);
}
elseif (is_array($config['extensions'])) {
$config['__extensions'] = $config['extensions'];
}
// 'mimetypes' is the array represented from the user interface,
// '__mimetypes' is a complete list of supported MIME types.
$config['__mimetypes'] = array_keys($mimetypes);
return $config;
}
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
// When the field is saved to database, encode the ID listing as a json
// array. Then, inspect the difference between the files actually
// attached to this field
function to_database($value) {
$this->getFiles();
if (isset($this->attachments)) {
$ids = array();
// Handle deletes
foreach ($this->attachments->getAll() as $f) {
if (!in_array($f['id'], $value))
$this->attachments->delete($f['id']);
else
$ids[] = $f['id'];
}
// Handle new files
foreach ($value as $id) {
if (!in_array($id, $ids))
$this->attachments->upload($id);
}
}
return JsonDataEncoder::encode($value);
}
function parse($value) {
// Values in the database should be integer file-ids
return array_map(function($e) { return (int) $e; },
$value ?: array());
}
function to_php($value) {
return JsonDataParser::decode($value);
}
function display($value) {
$links = array();
foreach ($this->getFiles() as $f) {
$hash = strtolower($f['key']
. md5($f['id'].session_id().strtolower($f['key'])));
$links[] = sprintf('<a class="no-pjax" href="file.php?h=%s">%s</a>',
$hash, Format::htmlchars($f['name']));
}
return implode('<br/>', $links);
}
function toString($value) {
$files = array();
foreach ($this->getFiles() as $f) {
$files[] = $f['name'];
}
return implode(', ', $files);
}
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
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 errors in the inline form');
}
}
function parse($value) {
// The InlineFieldWidget returns an array of cleaned data
return $value;