Newer
Older
return $this->flags & self::FLAG_INHERIT_CRITERIA;
}
function inheritColumns() {
return $this->hasFlag(self::FLAG_INHERIT_COLUMNS);
}
function useStandardColumns() {
return !count($this->columns);
}
function inheritExport() {
return ($this->hasFlag(self::FLAG_INHERIT_EXPORT) ||
!count($this->exports));
}
function inheritSorting() {
return $this->hasFlag(self::FLAG_INHERIT_SORTING);
}
function isDefaultSortInherited() {
return $this->hasFlag(self::FLAG_INHERIT_DEF_SORT);
}
function buildPath() {
if (!$this->id)
return;
$path = $this->parent ? $this->parent->buildPath() : '';
return rtrim($path, "/") . "/{$this->id}/";
}
function getFullName() {
$base = $this->getName();
if ($this->parent)
$base = sprintf("%s / %s", $this->parent->getFullName(), $base);
return $base;
}
function isASubQueue() {
return $this->parent ? $this->parent->isASubQueue() :
$this->isAQueue();
}
function isAQueue() {
return $this->hasFlag(self::FLAG_QUEUE);
}
function isASearch() {
return !$this->isAQueue() || !$this->isSaved();
}
function isPrivate() {
return !$this->isAQueue() && !$this->isPublic() &&
$this->staff_id;
}
function isPublic() {
return $this->hasFlag(self::FLAG_PUBLIC);
}
protected function hasFlag($flag) {
return ($this->flags & $flag) !== 0;
}
protected function clearFlag($flag) {
return $this->flags &= ~$flag;
}
protected function setFlag($flag, $value=true) {
return $value
? $this->flags |= $flag
: $this->clearFlag($flag);
}
function disable() {
$this->setFlag(self::FLAG_DISABLED);
}
function enable() {
$this->clearFlag(self::FLAG_DISABLED);
}
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
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
function updateExports($fields, $save=true) {
if (!$fields)
return false;
$order = array_keys($fields);
// Filter exportable fields
if (!($fields = array_intersect_key($this->getExportableFields(), $fields)))
return false;
$new = $fields;
foreach ($this->exports as $f) {
$key = $f->getPath();
if (!isset($fields[$key])) {
$this->exports->remove($f);
continue;
}
$info = $fields[$key];
if (is_array($info))
$heading = $info['heading'];
else
$heading = $info;
$f->set('heading', $heading);
$f->set('sort', array_search($key, $order)+1);
unset($new[$key]);
}
foreach ($new as $k => $field) {
if (is_array($field))
$heading = $field['heading'];
else
$heading = $field;
$f = QueueExport::create(array(
'path' => $k,
'heading' => $heading,
'sort' => array_search($k, $order)+1));
$this->exports->add($f);
}
$this->exports->sort(function($f) { return $f->sort; });
if (!count($this->exports) && $this->parent)
$this->hasFlag(self::FLAG_INHERIT_EXPORT);
if ($save)
$this->exports->saveAll();
return true;
}
function update($vars, &$errors=array()) {
// Set basic search information
if (!$vars['queue-name'])
$errors['queue-name'] = __('A title is required');
'title' => $vars['queue-name'],
'staff_id' => $this->staff_id)))
&& $q->getId() != $this->id
)
$errors['queue-name'] = __('Saved queue with same name exists');
$this->title = $vars['queue-name'];
$this->parent_id = @$vars['parent_id'] ?: 0;
if ($this->parent_id && !$this->parent)
$errors['parent_id'] = __('Select a valid queue');
// Try to avoid infinite recursion determining ancestry
if ($this->parent_id && isset($this->id)) {
$P = $this;
while ($P = $P->parent)
if ($P->parent_id == $this->id)
$errors['parent_id'] = __('Cannot be a descendent of itself');
}
if ($vars['sort_id']) {
if ($vars['filter'] === '::') {
if (!$this->parent)
$errors['filter'] = __('No parent selected');
}
elseif ($vars['filter'] && !array_key_exists($vars['filter'],
static::getSearchableFields($this->getRoot()))
) {
$errors['filter'] = __('Select an item from the list');
}
}
// Set basic queue information
$this->path = $this->buildPath();
$this->setFlag(self::FLAG_INHERIT_CRITERIA,
$this->parent_id > 0 && isset($vars['inherit']));
$this->setFlag(self::FLAG_INHERIT_COLUMNS,
isset($vars['inherit-columns']));
$this->parent_id > 0 && isset($vars['inherit-exports']));
$this->setFlag(self::FLAG_INHERIT_SORTING,
$this->parent_id > 0 && isset($vars['inherit-sorting']));
// Update queue columns (but without save)
if (!isset($vars['columns']) && $this->parent) {
// No columns -- imply column inheritance
$this->setFlag(self::FLAG_INHERIT_COLUMNS);
}
if ($this->getId()
&& isset($vars['columns'])
&& !$this->hasFlag(self::FLAG_INHERIT_COLUMNS)) {
if ($this->columns->update($vars['columns'], $errors, array(
'queue_id' => $this->getId(),
'staff_id' => $this->staff_id)))
$this->columns->reset();
// Update export fields for the queue
if (isset($vars['exports']) &&
!$this->hasFlag(self::FLAG_INHERIT_EXPORT)) {
$this->updateExports($vars['exports'], false);
if (!count($this->exports) && $this->parent)
$this->hasFlag(self::FLAG_INHERIT_EXPORT);
// Update advanced sorting options for the queue
if (isset($vars['sorts']) && !$this->hasFlag(self::FLAG_INHERIT_SORTING)) {
foreach ($this->sorts as $sort) {
$key = $sort->sort_id;
$idx = array_search($key, $vars['sorts']);
if (false === $idx) {
else {
$sort->set('sort', $idx);
unset($new[$idx]);
}
}
// Add new columns
foreach ($new as $id) {
if (!$sort = QueueSort::lookup($id))
continue;
$glue = new QueueSortGlue(array(
'sort_id' => $id,
'sort' => array_search($id, $order),
));
}
// Re-sort the in-memory columns array
$this->sorts->sort(function($c) { return $c->sort; });
}
if (!count($this->sorts) && $this->parent) {
// No sorting -- imply sorting inheritance
$this->setFlag(self::FLAG_INHERIT_SORTING);
}
// Configure default sorting
$this->setFlag(self::FLAG_INHERIT_DEF_SORT,
$this->parent && $vars['sort_id'] === '::');
if ($vars['sort_id']) {
if ($vars['sort_id'] === '::') {
if (!$this->parent)
$errors['sort_id'] = __('No parent selected');
}
elseif ($qs = QueueSort::lookup($vars['sort_id'])) {
$this->sort_id = $vars['sort_id'];
}
else {
$errors['sort_id'] = __('Select an item from the list');
}
}
list($this->_conditions, $conditions)
= QueueColumn::getConditionsFromPost($vars, $this->id, $this->getRoot());
// TODO: Move this to SavedSearch::update() and adjust
// AjaxSearch::_saveSearch()
$form = $form ?: $this->getForm($vars);
if (!$vars) {
$errors['criteria'] = __('No criteria specified');
}
elseif (!$form->isValid()) {
$errors['criteria'] = __('Validation errors exist on criteria');
}
else {
$this->criteria = static::isolateCriteria($form->getClean(),
$this->getRoot());
$this->config = JsonDataEncoder::encode([
'conditions' => $conditions,
]);
// Clear currently set criteria.and conditions.
$this->criteria = $this->_conditions = null;
return 0 === count($errors);
}
function psave() {
return parent::save();
}
$nopath = !isset($this->path);
$path_changed = isset($this->dirty['parent_id']);
if ($this->dirty)
$this->updated = SqlFunction::NOW();
if (!($rv = parent::save($refetch || $this->dirty)))
$this->path = $this->buildPath();
$this->save();
}
if ($path_changed) {
$this->children->reset();
$move_children = function($q) use (&$move_children) {
foreach ($q->children as $qq) {
$qq->path = $qq->buildPath();
$qq->save();
$move_children($qq);
}
};
$move_children($this);
}
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
/**
* Fetch a tree-organized listing of the queues. Each queue is listed in
* the tree exactly once, and every visible queue is represented. The
* returned structure is an array where the items are two-item arrays
* where the first item is a CustomQueue object an the second is a list
* of the children using the same pattern (two-item arrays of a CustomQueue
* and its children). Visually:
*
* [ [ $queue, [ [ $child, [] ], [ $child, [] ] ], [ $queue, ... ] ]
*
* Parameters:
* $staff - <Staff> staff object which should be used to determine
* visible queues.
* $pid - <int> parent_id of root queue. Default is zero (top-level)
*/
static function getHierarchicalQueues(Staff $staff, $pid=0) {
$all = static::objects()
->filter(Q::any(array(
'flags__hasbit' => self::FLAG_PUBLIC,
'flags__hasbit' => static::FLAG_QUEUE,
'staff_id' => $staff->getId(),
)))
->exclude(['flags__hasbit' => self::FLAG_DISABLED])
->asArray();
// Find all the queues with a given parent
$for_parent = function($pid) use ($all, &$for_parent) {
$results = [];
foreach (new \ArrayIterator($all) as $q) {
if ($q->parent_id == $pid)
$results[] = [ $q, $for_parent($q->getId()) ];
}
return $results;
};
return $for_parent($pid);
}
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
static function getOrmPath($name, $query=null) {
// Special case for custom data `__answers!id__value`. Only add the
// join and constraint on the query the first pass, when the query
// being mangled is received.
$path = array();
if ($query && preg_match('/^(.+?)__(answers!(\d+))/', $name, $path)) {
// Add a join to the model of the queryset where the custom data
// is forked from — duplicate the 'answers' join and add the
// constraint to the query based on the field_id
// $path[1] - part before the answers (user__org__entries)
// $path[2] - answers!xx join part
// $path[3] - the `xx` part of the answers!xx join component
$root = $query->model;
$meta = $root::getMeta()->getByPath($path[1]);
$joins = $meta['joins'];
if (!isset($joins[$path[2]])) {
$meta->addJoin($path[2], $joins['answers']);
}
// Ensure that the query join through answers!xx is only for the
// records which match field_id=xx
$query->constrain(array("{$path[1]}__{$path[2]}" =>
array("{$path[1]}__{$path[2]}__field_id" => (int) $path[3])
));
// Leave $name unchanged
}
return $name;
}
static function create($vars=false) {
$queue = new static($vars);
$queue->created = SqlFunction::NOW();
if (!isset($vars['flags']))
$queue->setFlag(self::FLAG_QUEUE);
static function __create($vars) {
$q = static::create($vars);
$glue = new QueueColumnGlue($info);
$glue->queue_id = $q->getId();
$glue->save();
}
if (isset($vars['sorts'])) {
foreach ($vars['sorts'] as $info) {
$glue = new QueueSortGlue($info);
$glue->queue_id = $q->getId();
$glue->save();
}
}
abstract class QueueColumnAnnotation {
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
static $icon = false;
static $desc = '';
var $config;
function __construct($config) {
$this->config = $config;
}
static function fromJson($config) {
$class = $config['c'];
if (class_exists($class))
return new $class($config);
}
static function getDescription() {
return __(static::$desc);
}
static function getIcon() {
return static::$icon;
}
static function getPositions() {
return array(
"<" => __('Start'),
"b" => __('Before'),
"a" => __('After'),
">" => __('End'),
);
}
function decorate($text, $dec) {
static $positions = array(
'<' => '<span class="pull-left">%2$s</span>%1$s',
'>' => '<span class="pull-right">%2$s</span>%1$s',
'a' => '%1$s%2$s',
'b' => '%2$s%1$s',
if (!isset($positions[$pos]))
return $text;
return sprintf($positions[$pos], $text, $dec);
}
// Render the annotation with the database record $row. $text is the
// text of the cell before annotations were applied.
function render($row, $cell) {
if ($decoration = $this->getDecoration($row, $cell))
return $this->decorate($cell, $decoration);
return $cell;
}
// Add the annotation to a QuerySet
// Fetch some HTML to render the decoration on the page. This function
// can return boolean FALSE to indicate no decoration should be applied
abstract function getDecoration($row, $text);
function getPosition() {
return strtolower($this->config['p']) ?: 'a';
}
function getClassName() {
return @$this->config['c'] ?: get_class();
}
static function getAnnotations($root) {
// Ticket annotations
static $annotations;
if (!isset($annotations[$root])) {
foreach (get_declared_classes() as $class)
if (is_subclass_of($class, get_called_class()))
$annotations[$root][] = $class;
}
return $annotations[$root];
}
/**
* Estimate the width of the rendered annotation in pixels
*/
function getWidth($row) {
return $this->isVisible($row) ? 25 : 0;
}
function isVisible($row) {
return true;
static function addToQuery($query, $name=false) {
$name = $name ?: static::$qname;
$annotation = new Static(array());
return $annotation->annotate($query, $name);
}
static function from_query($row, $name=false) {
$name = $name ?: static::$qname;
return $row[$name];
}
}
class TicketThreadCount
static $icon = 'comments-alt';
static $qname = '_thread_count';
static $desc = /* @trans */ 'Thread Count';
function annotate($query, $name=false) {
$name = $name ?: static::$qname;
return $query->annotate(array(
->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1)))
->exclude(array('entries__flags__hasbit' => ThreadEntry::FLAG_HIDDEN))
->aggregate(array('count' => SqlAggregate::COUNT('entries__id')))
));
}
function getDecoration($row, $text) {
$threadcount = $row[static::$qname];
if ($threadcount > 1) {
return sprintf(
'<small class="faded-more"><i class="icon-comments-alt"></i> %s</small>',
$threadcount
);
}
}
function isVisible($row) {
return $row[static::$qname] > 1;
}
class TicketReopenCount
extends QueueColumnAnnotation {
static $icon = 'folder-open-alt';
static $qname = '_reopen_count';
static $desc = /* @trans */ 'Reopen Count';
function annotate($query, $name=false) {
$name = $name ?: static::$qname;
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1)))
->filter(array('events__annulled' => 0, 'events__state' => 'reopened'))
->aggregate(array('count' => SqlAggregate::COUNT('events__id')))
));
}
function getDecoration($row, $text) {
$reopencount = $row[static::$qname];
if ($reopencount) {
return sprintf(
' <small class="faded-more"><i class="icon-%s"></i> %s</small>',
static::$icon,
$reopencount > 1 ? $reopencount : ''
);
}
}
function isVisible($row) {
return $row[static::$qname];
}
}
class ThreadAttachmentCount
static $icon = 'paperclip';
static $qname = '_att_count';
static $desc = /* @trans */ 'Attachment Count';
// TODO: Convert to Thread attachments
return $query->annotate(array(
->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1)))
->filter(array('entries__attachments__inline' => 0))
->aggregate(array('count' => SqlAggregate::COUNT('entries__attachments__id')))
));
}
function getDecoration($row, $text) {
$count = $row[static::$qname];
if ($count) {
return sprintf(
'<i class="small icon-paperclip icon-flip-horizontal" data-toggle="tooltip" title="%s"></i>',
$count);
}
}
function isVisible($row) {
return $row[static::$qname] > 0;
}
class ThreadCollaboratorCount
extends QueueColumnAnnotation {
static $icon = 'group';
static $qname = '_collabs';
static $desc = /* @trans */ 'Collaborator Count';
function annotate($query, $name=false) {
$name = $name ?: static::$qname;
return $query->annotate(array(
->filter(array('ticket__ticket_id' => new SqlField('ticket_id', 1)))
->aggregate(array('count' => SqlAggregate::COUNT('collaborators__id')))
));
}
function getDecoration($row, $text) {
$count = $row[static::$qname];
if ($count) {
return sprintf(
'<span class="pull-right faded-more" data-toggle="tooltip" title="%d"><i class="icon-group"></i></span>',
$count);
}
}
function isVisible($row) {
return $row[static::$qname] > 0;
}
class OverdueFlagDecoration
static $icon = 'exclamation';
static $desc = /* @trans */ 'Overdue Icon';
return $query->values('isoverdue');
}
function getDecoration($row, $text) {
if ($row['isoverdue'])
return '<span class="Icon overdueTicket"></span>';
function isVisible($row) {
return $row['isoverdue'];
}
}
class TicketSourceDecoration
static $icon = 'phone';
static $desc = /* @trans */ 'Ticket Source';
return $query->values('source');
}
function getDecoration($row, $text) {
return sprintf('<span class="Icon %sTicket"></span>',
strtolower($row['source']));
class LockDecoration
extends QueueColumnAnnotation {
static $icon = "lock";
static $desc = /* @trans */ 'Locked';
global $thisstaff;
return $query
->annotate(array(
'_locked' => new SqlExpr(new Q(array(
'lock__expire__gt' => SqlFunction::NOW(),
Q::not(array('lock__staff_id' => $thisstaff->getId())),
));
}
function getDecoration($row, $text) {
if ($row['_locked'])
return sprintf('<span class="Icon lockedTicket"></span>');
}
function isVisible($row) {
return $row['_locked'];
}
class AssigneeAvatarDecoration
extends QueueColumnAnnotation {
static $icon = "user";
static $desc = /* @trans */ 'Assignee Avatar';
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
return $query->values('staff_id', 'team_id');
}
function getDecoration($row, $text) {
if ($row['staff_id'] && ($staff = Staff::lookup($row['staff_id'])))
return sprintf('<span class="avatar">%s</span>',
$staff->getAvatar(16));
elseif ($row['team_id'] && ($team = Team::lookup($row['team_id']))) {
$avatars = [];
foreach ($team->getMembers() as $T)
$avatars[] = $T->getAvatar(16);
return sprintf('<span class="avatar group %s">%s</span>',
count($avatars), implode('', $avatars));
}
}
function isVisible($row) {
return $row['staff_id'] + $row['team_id'] > 0;
}
function getWidth($row) {
if (!$this->isVisible($row))
return 0;
// If assigned to a team with no members, return 0 width
$width = 10;
if ($row['team_id'] && ($team = Team::lookup($row['team_id'])))
$width += (count($team->getMembers()) - 1) * 10;
return $width ? $width + 10 : $width;
}
}
class UserAvatarDecoration
extends QueueColumnAnnotation {
static $icon = "user";
static $desc = /* @trans */ 'User Avatar';
return $query->values('user_id');
}
function getDecoration($row, $text) {
if ($row['user_id'] && ($user = User::lookup($row['user_id'])))
return sprintf('<span class="avatar">%s</span>',
$user->getAvatar(16));
}
function isVisible($row) {
return $row['user_id'] > 0;
}
}
class DataSourceField
extends ChoiceField {
function getChoices($verbose=false) {
$config = $this->getConfiguration();
$root = $config['root'];
$fields = array();
foreach (CustomQueue::getSearchableFields($root) as $path=>$f) {
list($label,) = $f;
$fields[$path] = $label;
}
return $fields;
}
}
class QueueColumnCondition {
var $config;
var $properties = array();
function __construct($config, $queue=null) {
if (is_array($config['prop']))
$this->properties = $config['prop'];
}
function getProperties() {
return $this->properties;
}
// Add the annotation to a QuerySet
function annotate($query) {
if (!($Q = $this->getSearchQ($query)))
return $query;
// Add an annotation to the query
return $query->annotate(array(
$this->getAnnotationName() => new SqlExpr(array($Q))
));
}
function getField($name=null) {
// FIXME
#$root = $this->getColumn()->getRoot();
$searchable = CustomQueue::getSearchableFields($root);
if (!isset($name))
list($name) = $this->config['crit'];
// Lookup the field to search this condition
if (isset($searchable[$name])) {
return $searchable[$name];
}
function getFieldName() {
list($name) = $this->config['crit'];
return $name;
}
function getCriteria() {
return $this->config['crit'];
}
list($name, $method, $value) = $this->config['crit'];
// XXX: Move getOrmPath to be more of a utility
// Ensure the special join is created to support custom data joins
$name = @CustomQueue::getOrmPath($name, $query);
$name2 = null;
if (preg_match('/__answers!\d+__/', $name)) {
// Ensure that only one record is returned from the join through
// the entry and answers joins
$name2 = $this->getAnnotationName().'2';
$query->annotate(array($name2 => SqlAggregate::MAX($name)));
}
if (list(,$field) = $this->getField($name))
return $field->getSearchQ($method, $value, $name2 ?: $name);
/**
* Take the criteria from the SavedSearch fields setup and isolate the
* field name being search, the method used for searhing, and the method-
* specific data entered in the UI.
*/
static function isolateCriteria($criteria, $base='Ticket') {
$searchable = CustomQueue::getSearchableFields($base);
foreach ($criteria as $k=>$v) {
if (substr($k, -7) === '+method') {
list($name,) = explode('+', $k, 2);
if (!isset($searchable[$name]))
continue;
// Lookup the field to search this condition
list($label, $field) = $searchable[$name];
// Get the search method and value
$method = $v;
// Not all search methods require a value
$value = $criteria["{$name}+{$method}"];
return array($name, $method, $value);
function render($row, $text, &$styles=array()) {
if ($V = $row[$this->getAnnotationName()]) {
foreach ($this->getProperties() as $css=>$value) {
$field = QueueColumnConditionProperty::getField($css);
$field->value = $value;
$V = $field->getClean();
if (is_array($V))
$V = current($V);
$styles[$css] = $V;
}
}
return $text;
}
function getAnnotationName() {
// This should be predictable based on the criteria so that the
// query can deduplicate the same annotations used in different
// conditions
if (!isset($this->annotation_name)) {
$this->annotation_name = $this->getShortHash();
}
return $this->annotation_name;
function __toString() {
list($name, $method, $value) = $this->config['crit'];
if (is_array($value))
$value = implode('+', $value);
return "{$name} {$method} {$value}";
}
function getHash($binary=false) {
return sha1($this->__toString(), $binary);
}
function getShortHash() {
return substr(base64_encode($this->getHash(true)), 0, 7);
}
static function getUid() {
return static::$uid++;
}
static function fromJson($config, $queue=null) {
$config = JsonDataParser::decode($config);
if (!is_array($config))
throw new BadMethodCallException('$config must be string or array');
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
1960
1961
1962
1963
}
}
class QueueColumnConditionProperty
extends ChoiceField {
static $properties = array(
'background-color' => 'ColorChoiceField',
'color' => 'ColorChoiceField',
'font-family' => array(
'monospace', 'serif', 'sans-serif', 'cursive', 'fantasy',
),
'font-size' => array(
'small', 'medium', 'large', 'smaller', 'larger',
),
'font-style' => array(
'normal', 'italic', 'oblique',
),
'font-weight' => array(
'lighter', 'normal', 'bold', 'bolder',
),
'text-decoration' => array(
'none', 'underline',
),
'text-transform' => array(
'uppercase', 'lowercase', 'captalize',
),
);
function __construct($property) {
$this->property = $property;
}
static function getProperties() {
return array_keys(static::$properties);
}
static function getField($prop) {
$choices = static::$properties[$prop];
if (!isset($choices))
return null;
if (is_array($choices))
return new ChoiceField(array(
'choices' => array_combine($choices, $choices),
));
elseif (class_exists($choices))
return new $choices(array('name' => $prop));
function getChoices($verbose=false) {
if (isset($this->property))
return static::$properties[$this->property];
$keys = array_keys(static::$properties);
return array_combine($keys, $keys);
}
}
class LazyDisplayWrapper {
function __construct($field, $value) {
$this->field = $field;
$this->value = $value;
$this->safe = false;
}
/**
* Allow a filter to change the value of this to a "safe" value which
* will not be automatically encoded with htmlchars()
*/
function changeTo($what, $safe=false) {