Newer
Older
// 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);
function __call($what, $args) {
// XXX: Throw exception if $this->parent is not set
throw new Exception(sprintf(__('%s: Call to undefined function'),
$what));
// 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 setAnswer($ans) { $this->answer = $ans; }
function setValue($value) {
$this->reset();
$this->getWidget()->value = $value;
}
if (is_numeric($this->get('id')))
return substr(md5(
session_id() . '-field-id-'.$this->get('id')), -16);
return $this->get('name') ?: $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($options=array()) {
$rv = $this->getWidget()->render($options);
if ($v = $this->get('visibility')) {
$v->emitJavascript($this);
}
return $rv;
function renderExtras($options=array()) {
function getMedia() {
$widget = $this->getWidget();
return $widget::$media;
}
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
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;
}
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
/**
* 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;
/**
* Indicates if the field places data in the `value_id` column. This
* is currently used by the materialized view system
*/
function hasIdValue() {
return false;
}
/**
* Indicates if the field has subfields accessible via getSubFields()
* method. Useful for filter integration. Should connect with
* getFilterData()
*/
function hasSubFields() {
return false;
}
function getSubFields() {
return null;
}
/**
* Indicates if the field provides for searching for something other
* than keywords. For instance, textbox fields can have hits by keyword
* searches alone, but selection fields should provide the option to
* match a specific value or set of values and therefore need to
* participate on any search builder.
*/
function hasSpecialSearch() {
return true;
}
function getConfigurationForm($source=null) {
$type = static::getFieldType($this->get('type'));
$clazz = $type[1];
$T = new $clazz($this->ht);
$config = $this->getConfiguration();
$this->_cform = new SimpleForm($T->getConfigurationOptions(), $source);
foreach ($this->_cform->getFields() as $name=>$f) {
if ($config && isset($config[$name]))
$f->value = $config[$name];
elseif ($f->get('default'))
$f->value = $f->get('default');
}
}
function configure($prop, $value) {
$this->getConfiguration();
$this->_config[$prop] = $value;
}
function getWidget($widgetClass=false) {
if (!static::$widget)
throw new Exception(__('Widget not defined for this field'));
$wc = $widgetClass ?: $this->get('widget') ?: static::$widget;
$this->_widget->parseValue();
}
return $this->_widget;
function getSelectName() {
$name = $this->get('name') ?: 'field_'.$this->get('id');
if ($this->hasIdValue())
$name .= '_id';
return $name;
}
function getTranslateTag($subtag) {
return _H(sprintf('field.%s.%s%s', $subtag, $this->get('id'),
$this->get('form_id') ? '' : '*internal*'));
}
function getLocal($subtag, $default=false) {
$tag = $this->getTranslateTag($subtag);
$T = CustomDataTranslation::translate($tag);
return $T != $tag ? $T : ($default ?: $this->get($subtag));
}
}
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'),
'regex'=>__('Custom (Regular Expression)'), ''=>__('None')))),
'regex' => new TextboxField(array(
'id'=>6, 'label'=>__('Regular Expression'), 'required'=>true,
'configuration'=>array('size'=>40, 'length'=>100),
'visibility' => new VisibilityConstraint(
new Q(array('validator__eq'=>'regex')),
VisibilityConstraint::HIDDEN
),
'cleaners' => function ($self, $value) {
$wrapped = "/".$value."/iu";
if (false === @preg_match($value, ' ')
&& false !== @preg_match($wrapped, ' ')) {
if ($value == '//iu')
return '';
return $value;
},
'validators' => function($self, $v) {
if (false === @preg_match($v, ' '))
$self->addError(__('Cannot compile this regular expression'));
})),
'validator-error' => new TextboxField(array(
'id'=>4, 'label'=>__('Validation Error'), 'default'=>'',
'configuration'=>array('size'=>40, 'length'=>60,
'translatable'=>$this->getTranslateTag('validator-error')
),
'hint'=>__('Message shown to user if the input does not match the validator'))),
'placeholder' => new TextboxField(array(
'id'=>5, 'label'=>__('Placeholder'), 'required'=>false, 'default'=>'',
'hint'=>__('Text shown in before any input from the user'),
'configuration'=>array('size'=>40, 'length'=>40,
'translatable'=>$this->getTranslateTag('placeholder')
),
function hasSpecialSearch() {
return false;
}
function validateEntry($value) {
parent::validateEntry($value);
'email' => array(array('Validator', 'is_valid_email'),
__('Enter a valid email address')),
'phone' => array(array('Validator', 'is_phone'),
__('Enter a valid phone number')),
__('Enter a valid IP address')),
'number' => array('is_numeric', __('Enter a number')),
'regex' => array(
function($v) use ($config) {
$regex = $config['regex'];
return @preg_match($regex, $v);
}, __('Value does not match required pattern')
),
);
// Support configuration forms, as well as GUI-based form fields
$valid = $this->get('validator');
if (!$valid) {
$valid = $config['validator'];
}
if (!$value || !isset($validators[$valid]))
return;
$error = $func[1];
if ($config['validator-error'])
$error = $this->getLocal('validator-error', $config['validator-error']);
if (is_array($func) && is_callable($func[0]))
if (!call_user_func($func[0], $value))
class PasswordField extends TextboxField {
static $widget = 'PasswordWidget';
function parse($value) {
// Don't trim the value
return $value;
}
// If not set in UI, don't save the empty value
if (!$value)
throw new FieldUnchanged();
return Crypto::encrypt($value, SECRET_SALT, 'pwfield');
return Crypto::decrypt($value, SECRET_SALT, 'pwfield');
static $widget = 'TextareaWidget';
function getConfigurationOptions() {
return array(
'cols' => new TextboxField(array(
'id'=>1, 'label'=>__('Width').' '.__('(chars)'), 'required'=>true, 'default'=>40)),
'id'=>2, 'label'=>__('Height').' '.__('(rows)'), 'required'=>false, 'default'=>4)),
'id'=>3, 'label'=>__('Max Length'), 'required'=>false, 'default'=>0)),
'id'=>4, 'label'=>__('HTML'), 'required'=>false, 'default'=>true,
'configuration'=>array('desc'=>__('Allow HTML input in this box')))),
'placeholder' => new TextboxField(array(
'id'=>5, 'label'=>__('Placeholder'), 'required'=>false, 'default'=>'',
'hint'=>__('Text shown in before any input from the user'),
'configuration'=>array('size'=>40, 'length'=>40,
'translatable'=>$this->getTranslateTag('placeholder')),
function hasSpecialSearch() {
return false;
}
function display($value) {
$config = $this->getConfiguration();
if ($config['html'])
return Format::safe_html($value);
else
return nl2br(Format::htmlchars($value));
$value = preg_replace(array('`<br(\s*)?/?>`i', '`</div>`i'), "\n", $value); //<?php
$value = Format::htmldecode(Format::striptags($value));
return Format::searchable($value);
}
function export($value) {
return (!$value) ? $value : Format::html2text($value);
}
function parse($value) {
$config = $this->getConfiguration();
if ($config['html'])
return Format::sanitize($value);
else
return $value;
}
}
class PhoneField extends FormField {
static $widget = 'PhoneNumberWidget';
function getConfigurationOptions() {
return array(
'ext' => new BooleanField(array(
'label'=>__('Extension'), 'default'=>true,
'desc'=>__('Add a separate field for the extension'),
),
)),
'digits' => new TextboxField(array(
'label'=>__('Minimum length'), 'default'=>7,
'hint'=>__('Fewest digits allowed in a valid phone number'),
'configuration'=>array('validator'=>'number', 'size'=>5),
)),
'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'),
'nset' => __('unchecked'),
);
}
function getSearchMethodWidgets() {
return array(
'set' => null,
function getSearchQ($method, $value, $name=false) {
$name = $name ?: $this->get('name');
switch ($method) {
case 'set':
return new Q(array($name => '1'));
return new Q(array($name => '0'));
default:
return parent::getSearchQ($method, $value, $name);
}
}
}
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))
$value = JsonDataParser::parse($value) ?: $value;
// CDATA table may be built with comma-separated key,value,key,value
if (is_string($value)) {
$values = array();
$choices = $this->getChoices();
foreach (explode(',', $value) as $V) {
if (isset($choices[$V]))
$values[$V] = $choices[$V];
if (array_filter($values))
$value = $values;
$config = $this->getConfiguration();
if (!$config['multiselect'] && is_array($value) && count($value) < 2) {
reset($value);
if (!is_array($value))
$value = $this->getChoice($value);
if (is_array($value))
return implode(', ', $value);
return (string) $value;
function whatChanged($before, $after) {
$B = (array) $before;
$A = (array) $after;
$added = array_diff($A, $B);
$deleted = array_diff($B, $A);
$added = array_map(array($this, 'display'), $added);
$deleted = array_map(array($this, 'display'), $deleted);
if ($added && $deleted) {
$desc = sprintf(
__('added <strong>%1$s</strong> and removed <strong>%2$s</strong>'),
implode(', ', $added), implode(', ', $deleted));
}
elseif ($added) {
$desc = sprintf(
__('added <strong>%1$s</strong>'),
implode(', ', $added));
}
elseif ($deleted) {
$desc = sprintf(
__('removed <strong>%1$s</strong>'),
implode(', ', $deleted));
}
else {
$desc = sprintf(
__('changed from <strong>%1$s</strong> to <strong>%2$s</strong>'),
$this->display($before), $this->display($after));
}
return $desc;
}
/*
Return criteria to which the choice should be filtered by
*/
function getCriteria() {
$config = $this->getConfiguration();
$criteria = array();
if (isset($config['criteria']))
$criteria = $config['criteria'];
return $criteria;
}
$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 lookupChoice($value) {
return null;
}
function getSearchMethods() {
return array(
'set' => __('has a value'),
'nset' => __('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);
}
}
function describeSearchMethod($method) {
switch ($method) {
case 'includes':
return __('%s includes %s' /* includes -> if a list includes a selection */);
case 'includes':
return __('%s does not include %s' /* includes -> if a list includes a selection */);
default:
return parent::describeSearchMethod($method);
}
}
}
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 asVar($value, $id=false) {
if (!$value) return null;
return new FormattedDate((int) $value, 'UTC', false, false);
}
function asVarType() {
return 'FormattedDate';
}
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');
// SearchableField interface ------------------------------
function getSearchMethods() {
return array(
'set' => __('has a value'),
'nset' => __('does not have a value'),
'equal' => __('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,
'equal' => array('DatetimeField', array(
'configuration' => $config_notime,
'nequal' => array('DatetimeField', array(
'configuration' => $config_notime,
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
)),
'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');
$value = is_int($value)
? DateTime::createFromFormat('U', Misc::dbtime($value)) ?: $value
: $value;
case 'equal':
$l = clone $value;
$r = $value->add(new DateInterval('P1D'));
return new Q(array(
"{$name}__gte" => $l,
"{$name}__lt" => $r
));
case 'nequal':
$l = clone $value;
$r = $value->add(new DateInterval('P1D'));
return Q::any(array(
"{$name}__lt" => $l,
"{$name}__gte" => $r,
));
case 'after':
return new Q(array("{$name}__gte" => $value));
case 'before':
return new Q(array("{$name}__lt" => $value));
case 'between':
foreach (array('left', 'right') as $side) {
$value[$side] = is_int($value[$side])
? DateTime::createFromFormat('U', Misc::dbtime($value[$side])) ?: $value[$side]
: $value[$side];
}
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);
}
}
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
function describeSearchMethod($method) {
switch ($method) {
case 'before':
return __('%1$s before %2$s' /* occurs before a date and time */);
case 'after':
return __('%1$s after %2$s' /* occurs after a date and time */);
case 'ndays':
return __('%1$s in the next %2$s' /* occurs within a window (like 3 days) */);
case 'ndaysago':
return __('%1$s in the last %2$s' /* occurs within a recent window (like 3 days) */);
case 'between':
return __('%1$s between %2$s and %3$s');
default:
return parent::describeSearchMethod($method);
}
}
function describeSearch($method, $value, $name=false) {
if ($method === 'between') {
$l = $this->toString($value['left']);
$r = $this->toString($value['right']);
$desc = $this->describeSearchMethod($method);
return sprintf($desc, $name, $l, $r);
}
return parent::describeSearch($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 getMedia() {
$config = $this->getConfiguration();
$media = parent::getMedia() ?: array();
if ($config['attachments'])
$media = array_merge_recursive($media, FileUploadWidget::$media);
return $media;
}
function getConfiguration() {
global $cfg;
$config = parent::getConfiguration();