Skip to content
Snippets Groups Projects
class.queue.php 95.1 KiB
Newer Older
  • Learn to ignore specific revisions
  •         $this->field = null;
            $this->value = $what;
            $this->safe = $safe;
        }
    
        function __toString() {
            return $this->display();
        }
    
        function display(&$styles=array()) {
            if (isset($this->field))
                return $this->field->display(
                    $this->field->to_php($this->value), $styles);
            if ($this->safe)
                return $this->value;
            return Format::htmlchars($this->value);
        }
    }
    
     * A column of a custom queue. Columns have many customizable features
     * including:
    
     *   * Data Source (primary and secondary)
     *   * Heading
     *   * Link (to an object like the ticket)
     *   * Size and truncate settings
     *   * annotations (like counts and flags)
     *   * Conditions (which change the formatting like bold text)
     *
     * Columns are stored in a separate table from the queue itself, but other
     * breakout items for the annotations and conditions, for instance, are stored
     * as JSON text in the QueueColumn model.
    
     */
    class QueueColumn
    extends VerySimpleModel {
        static $meta = array(
    
            'table' => COLUMN_TABLE,
    
            'pk' => array('id'),
    
            'ordering' => array('name'),
    
        const FLAG_SORTABLE = 0x0001;
    
    
        var $_annotations;
        var $_conditions;
    
        var $_queue;            // Apparent queue if being inherited
    
        var $_fields;
    
    
        function getId() {
            return $this->id;
        }
    
    
        function getFilter() {
    
             if ($this->filter
                    && ($F = QueueColumnFilter::getInstance($this->filter)))
                return $F;
    
         }
    
        function getName() {
            return $this->name;
        }
    
        // These getters fetch data from the annotated overlay from the
        // queue_column table
    
        function getQueue() {
    
    JediKev's avatar
    JediKev committed
            if (!isset($this->_queue)) {
                $queue = $this->queue;
    
                if (!$queue && ($queue_id = $this->queue_id))
                    $queue = CustomQueue::lookup($queue_id);
    
                $this->_queue = $queue;
            }
    
            return $this->_queue;
    
        }
        /**
         * If a column is inherited into a child queue and there are conditions
         * added to that queue, then the column will need to be linked at
         * run-time to the child queue rather than the parent.
         */
        function setQueue(CustomQueue $queue) {
            $this->_queue = $queue;
    
        function getFields() {
            if (!isset($this->_fields)) {
                $root = ($q = $this->getQueue()) ? $q->getRoot() : 'Ticket';
                $fields = CustomQueue::getSearchableFields($root);
                $primary = CustomQueue::getOrmPath($this->primary);
                $secondary = CustomQueue::getOrmPath($this->secondary);
                if (($F = $fields[$primary]) && (list(,$field) = $F))
                    $this->_fields[$primary] = $field;
                if (($F = $fields[$secondary]) && (list(,$field) = $F))
                    $this->_fields[$secondary] = $field;
            }
            return $this->_fields;
        }
    
        function getField($path=null) {
            $fields = $this->getFields();
            return @$fields[$path ?: $this->primary];
        }
    
    
        function getWidth() {
            return $this->width ?: 100;
        }
    
    
        function getHeading() {
            return $this->heading;
        }
    
    
        function getTranslateTag($subtag) {
            return _H(sprintf('column.%s.%s.%s', $subtag, $this->queue_id, $this->id));
        }
        function getLocal($subtag) {
            $tag = $this->getTranslateTag($subtag);
            $T = CustomDataTranslation::translate($tag);
            return $T != $tag ? $T : $this->get($subtag);
        }
        function getLocalHeading() {
            return $this->getLocal('heading');
    
        protected function setFlag($flag, $value=true, $field='flags') {
            return $value
                ? $this->{$field} |= $flag
                : $this->clearFlag($flag, $field);
        }
    
        protected function clearFlag($flag, $field='flags') {
            return $this->{$field} &= ~$flag;
        }
    
        function isSortable() {
            return $this->bits & self::FLAG_SORTABLE;
        }
    
        function setSortable($sortable) {
            $this->setFlag(self::FLAG_SORTABLE, $sortable, 'bits');
        }
    
    
        function render($row) {
            // Basic data
            $text = $this->renderBasicValue($row);
    
    
            if ($text && ($filter = $this->getFilter())) {
    
                $text = $filter->filter($text, $row) ?: $text;
    
            $styles = array();
            if ($text instanceof LazyDisplayWrapper) {
                $text = $text->display($styles);
            }
    
            // Truncate
            $text = $this->applyTruncate($text, $row);
    
    
            // annotations and conditions
    
            foreach ($this->getAnnotations() as $D) {
    
                $text = $D->render($row, $text);
            }
    
            foreach ($this->getConditions() as $C) {
    
                $text = $C->render($row, $text, $styles);
    
            $style = Format::array_implode(':', ';', $styles);
            return array($text, $style);
    
        }
    
        function renderBasicValue($row) {
    
            $fields = $this->getFields();
    
            $primary = CustomQueue::getOrmPath($this->primary);
            $secondary = CustomQueue::getOrmPath($this->secondary);
    
            // Return a lazily ::display()ed value so that the value to be
            // rendered by the field could be changed or display()ed when
            // converted to a string.
    
            if (($F = $fields[$primary])
    
                && ($T = $F->from_query($row, $primary))
    
                return new LazyDisplayWrapper($F, $T);
    
            }
            if (($F = $fields[$secondary])
    
                && ($T = $F->from_query($row, $secondary))
    
                return new LazyDisplayWrapper($F, $T);
    
             return new LazyDisplayWrapper($F, '');
        }
    
        function from_query($row) {
            if (!($f = $this->getField($this->primary)))
                return '';
    
            $val = $f->to_php($f->from_query($row, $this->primary));
            if (is_numeric($val))
                $val = $f->display($val);
    
            return $val;
    
        function applyTruncate($text, $row) {
    
            $offset = 0;
            foreach ($this->getAnnotations() as $a)
    
                $offset += $a->getWidth($row);
    
    
            $width = $this->width - $offset;
    
            switch ($this->truncate) {
    
            case 'lclip':
                $linfo = Internationalization::getCurrentLanguageInfo();
    
                // Use `rtl` class to cut the beginning of LTR text. But, wrap
                // the text with an appropriate direction so the ending
                // punctuation is not rearranged.
                $dir = $linfo['direction'] ?: 'ltr';
                $text = sprintf('<span class="%s">%s</span>', $dir, $text);
                $class[] = $dir == 'rtl' ? 'ltr' : 'rtl';
    
                $class[] = 'bleed';
            case 'ellipsis':
                $class[] = 'truncate';
    
                return sprintf('<span class="%s" style="max-width:%dpx">%s</span>',
    
                    implode(' ', $class), $width, $text);
    
        function addToQuery($query, $field, $path) {
            if (preg_match('/__answers!\d+__/', $path)) {
                // Ensure that only one record is returned from the join through
                // the entry and answers joins
                return $query->annotate(array(
                    $path => SqlAggregate::MAX($path)
                ));
            }
            return $field->addToQuery($query, $path);
        }
    
    
        function mangleQuery($query, $root=null) {
    
            $fields = $this->getFields();
            if ($field = $fields[$this->primary]) {
    
                $query = $this->addToQuery($query, $field,
    
                    CustomQueue::getOrmPath($this->primary, $query));
    
            if ($field = $fields[$this->secondary]) {
    
                $query = $this->addToQuery($query, $field,
    
                    CustomQueue::getOrmPath($this->secondary, $query));
    
            if ($filter = $this->getFilter())
                $query = $filter->mangleQuery($query, $this);
    
            // annotations
    
            foreach ($this->getAnnotations() as $D) {
    
                $query = $D->annotate($query);
            }
    
            // Conditions
    
            foreach ($this->getConditions() as $C) {
    
                $query = $C->annotate($query);
            }
    
            return $query;
        }
    
    
        function applySort($query, $reverse=false) {
    
    	    $root = ($q = $this->getQueue()) ? $q->getRoot() : 'Ticket';
    
            $fields = CustomQueue::getSearchableFields($root);
    
    
            $keys = array();
    
            if ($primary = $fields[$this->primary]) {
                list(,$field) = $primary;
    
                $keys[] = array(CustomQueue::getOrmPath($this->primary, $query),
                        $field);
    
            if ($secondary = $fields[$this->secondary]) {
                list(,$field) = $secondary;
    
                $keys[] = array(CustomQueue::getOrmPath($this->secondary,
                            $query), $field);
            }
    
            if (count($keys) > 1) {
                $fields = array();
                foreach ($keys as $key) {
                    list($path, $field) = $key;
    
                    foreach ($field->getSortKeys($path) as $field)
                        $fields[]  = new SqlField($field);
    
                // Force nulls to the buttom.
                $fields[] = 'zzz';
    
    
                $alias = sprintf('C%d', $this->getId());
                $expr = call_user_func_array(array('SqlFunction', 'COALESCE'),
                        $fields);
                $query->annotate(array($alias => $expr));
    
                $reverse = $reverse ? '-' : '';
                $query = $query->order_by("{$reverse}{$alias}");
            } else {
                list($path, $field) = $keys[0];
                $query = $field->applyOrderBy($query, $reverse, $path);
    
        function getDataConfigForm($source=false) {
    
            return new QueueColDataConfigForm($source ?: $this->getDbFields(),
    
                array('id' => $this->id));
        }
    
    
        function getAnnotations() {
    
            if (!isset($this->_annotations)) {
                $this->_annotations = array();
                if ($this->annotations
                    && ($anns = JsonDataParser::decode($this->annotations))
                ) {
                    foreach ($anns as $D)
                        if ($T = QueueColumnAnnotation::fromJson($D))
                            $this->_annotations[] = $T;
                }
            }
    
            return $this->_annotations;
    
        function getConditions($include_queue=true) {
    
            if (!isset($this->_conditions)) {
                $this->_conditions = array();
                if ($this->conditions
                    && ($conds = JsonDataParser::decode($this->conditions))
                ) {
                    foreach ($conds as $C)
                        if ($T = QueueColumnCondition::fromJson($C))
                            $this->_conditions[] = $T;
                }
    
                // Support row-spanning conditions
                if ($include_queue && ($q = $this->getQueue())
                    && ($q_conds = $q->getConditions())
                ) {
    
                    $this->_conditions = array_merge($q_conds, $this->_conditions);
    
            return $this->_conditions;
        }
    
    
        static function __create($vars) {
    
            $c = new static($vars);
    
            $c->save();
            return $c;
        }
    
    
        static function placeholder($vars) {
            return static::__hydrate($vars);
        }
    
    
        function update($vars, $root='Ticket') {
    
            $form = $this->getDataConfigForm($vars);
            foreach ($form->getClean() as $k=>$v)
                $this->set($k, $v);
    
            // Do the annotations
            $this->_annotations = $annotations = array();
            if (isset($vars['annotations'])) {
                foreach (@$vars['annotations'] as $i=>$class) {
                    if ($vars['deco_column'][$i] != $this->id)
                        continue;
                    if (!class_exists($class) || !is_subclass_of($class, 'QueueColumnAnnotation'))
                        continue;
                    $json = array('c' => $class, 'p' => $vars['deco_pos'][$i]);
                    $annotations[] = $json;
                    $this->_annotations[] = QueueColumnAnnotation::fromJson($json);
                }
    
    
            // Do the conditions
    
            $this->_conditions = $conditions = array();
            if (isset($vars['conditions'])) {
    
                list($this->_conditions, $conditions)
                    = self::getConditionsFromPost($vars, $this->id, $root);
    
            // Store as JSON array
    
            $this->annotations = JsonDataEncoder::encode($annotations);
            $this->conditions = JsonDataEncoder::encode($conditions);
        }
    
    
        static function getConditionsFromPost(array $vars, $myid, $root='Ticket') {
            $condition_objects = $conditions = array();
    
            if (!isset($vars['conditions']))
                return array($condition_objects, $conditions);
    
            foreach (@$vars['conditions'] as $i=>$id) {
                if ($vars['condition_column'][$i] != $myid)
                    // Not a condition for this column
                    continue;
                // Determine the criteria
                $name = $vars['condition_field'][$i];
                $fields = CustomQueue::getSearchableFields($root);
                if (!isset($fields[$name]))
                    // No such field exists for this queue root type
                    continue;
                $parts = CustomQueue::getSearchField($fields[$name], $name);
                $search_form = new SimpleForm($parts, $vars, array('id' => $id));
                $search_form->getField("{$name}+search")->value = true;
                $crit = $search_form->getClean();
                // Check the box to enable searching on the field
                $crit["{$name}+search"] = true;
    
                // Isolate only the critical parts of the criteria
                $crit = QueueColumnCondition::isolateCriteria($crit);
    
                // Determine the properties
                $props = array();
                foreach ($vars['properties'] as $i=>$cid) {
                    if ($cid != $id)
                        // Not a property for this condition
                        continue;
    
                    // Determine the property configuration
                    $prop = $vars['property_name'][$i];
                    if (!($F = QueueColumnConditionProperty::getField($prop))) {
                        // Not a valid property
                        continue;
                    }
                    $prop_form = new SimpleForm(array($F), $vars, array('id' => $cid));
                    $props[$prop] = $prop_form->getField($prop)->getClean();
                }
                $json = array('crit' => $crit, 'prop' => $props);
                $condition_objects[] = QueueColumnCondition::fromJson($json);
                $conditions[] = $json;
            }
            return array($condition_objects, $conditions);
        }
    
    
    class QueueConfig
    extends VerySimpleModel {
        static $meta = array(
            'table' => QUEUE_CONFIG_TABLE,
            'pk' => array('queue_id', 'staff_id'),
            'joins' => array(
                'queue' => array(
                    'constraint' => array(
                        'queue_id' => 'CustomQueue.id'),
                ),
                'staff' => array(
                    'constraint' => array(
                        'staff_id' => 'Staff.staff_id',
                    )
                ),
                'columns' => array(
                    'reverse' => 'QueueColumnGlue.config',
                    'constrain' => array('staff_id' =>'QueueColumnGlue.staff_id'),
                    'broker' => 'QueueColumnListBroker',
                ),
            ),
        );
    
        function getSettings() {
            return JsonDataParser::decode($this->setting);
        }
    
    
        function update($vars, &$errors) {
    
            // settings of interest
            $setting = array(
                    'sort_id' => (int) $vars['sort_id'],
                    'filter' => $vars['filter'],
                    'inherit-columns' => isset($vars['inherit-columns']),
                    'criteria' => $vars['criteria'] ?: array(),
                    );
    
            if (!$setting['inherit-columns'] && $vars['columns']) {
                if (!$this->columns->update($vars['columns'], $errors, array(
                                    'queue_id' => $this->queue_id,
                                    'staff_id' => $this->staff_id)))
                    $setting['inherit-columns'] = true;
                $this->columns->reset();
            }
    
            $this->setting =  JsonDataEncoder::encode($setting);
    
            return $this->save();
        }
    
        function save($refetch=false) {
            if ($this->dirty)
                $this->updated = SqlFunction::NOW();
            return parent::save($refetch || $this->dirty);
        }
    
        static function create($vars=false) {
            $inst = new static($vars);
            return $inst;
        }
    }
    
    
    
    Peter Rotich's avatar
    Peter Rotich committed
    class QueueExport
    extends VerySimpleModel {
        static $meta = array(
            'table' => QUEUE_EXPORT_TABLE,
            'pk' => array('id'),
            'joins' => array(
                'queue' => array(
                    'constraint' => array('queue_id' => 'CustomQueue.id'),
                ),
            ),
            'select_related' => array('queue'),
            'ordering' => array('sort'),
        );
    
    
        function getPath() {
            return $this->path;
        }
    
        function getField() {
            return $this->getPath();
        }
    
        function getHeading() {
            return $this->heading;
        }
    
        static function create($vars=false) {
            $inst = new static($vars);
            return $inst;
        }
    }
    
    
    class QueueColumnGlue
    extends VerySimpleModel {
        static $meta = array(
            'table' => QUEUE_COLUMN_TABLE,
    
            'pk' => array('queue_id', 'staff_id', 'column_id'),
    
            'joins' => array(
                'column' => array(
                    'constraint' => array('column_id' => 'QueueColumn.id'),
                ),
                'queue' => array(
    
                    'constraint' => array(
                        'queue_id' => 'CustomQueue.id',
                        'staff_id' => 'CustomQueue.staff_id'),
                ),
                'config' => array(
                    'constraint' => array(
                        'queue_id' => 'QueueConfig.queue_id',
                        'staff_id' => 'QueueConfig.staff_id'),
    
            'select_related' => array('column'),
    
            'ordering' => array('sort'),
        );
    }
    
    
    class QueueColumnGlueMIM
    extends ModelInstanceManager {
    
        function getOrBuild($modelClass, $fields, $cache=true) {
            $m = parent::getOrBuild($modelClass, $fields, $cache);
            if ($m && $modelClass === 'QueueColumnGlue') {
                // Instead, yield the QueueColumn instance with the local fields
    
                // in the association table as annotations
    
                $m = AnnotatedModel::wrap($m->column, $m, 'QueueColumn');
            }
            return $m;
        }
    
    }
    
    class QueueColumnListBroker
    extends InstrumentedList {
        function __construct($fkey, $queryset=false) {
            parent::__construct($fkey, $queryset, 'QueueColumnGlueMIM');
            $this->queryset->select_related('column');
        }
    
        function add($column, $glue=null, $php7_is_annoying=true) {
    
            $glue = $glue ?: new QueueColumnGlue();
    
            $glue->column = $column;
    
            $anno = AnnotatedModel::wrap($column, $glue);
    
            parent::add($anno, false);
    
    
        function update($columns, &$errors, $options=array()) {
    
    
            $new = $columns;
            $order = array_keys($new);
            foreach ($this as $col) {
                $key = $col->column_id;
                if (!isset($columns[$key])) {
                    $this->remove($col);
                    continue;
                }
                $info = $columns[$key];
                $col->set('sort', array_search($key, $order));
                $col->set('heading', $info['heading']);
                $col->set('width', $info['width']);
                $col->setSortable($info['sortable']);
                unset($new[$key]);
            }
            // Add new columns
            foreach ($new as $info) {
                $glue = new QueueColumnGlue(array(
                    'staff_id' => $options['staff_id'] ?: 0 ,
                    'queue_id' => $options['queue_id'] ?: 0,
                    'column_id' => $info['column_id'],
                    'sort' => array_search($info['column_id'], $order),
                    'heading' => $info['heading'],
                    'width' => $info['width'] ?: 100,
                    'bits' => $info['sortable'] ?  QueueColumn::FLAG_SORTABLE : 0,
                ));
    
                $this->add(QueueColumn::lookup($info['column_id']), $glue);
            }
            // Re-sort the in-memory columns array
            $this->sort(function($c) { return $c->sort; });
    
            return $this->saveAll();
        }
    
    class QueueSort
    extends VerySimpleModel {
        static $meta = array(
            'table' => QUEUE_SORT_TABLE,
            'pk' => array('id'),
            'ordering' => array('name'),
            'joins' => array(
                'queue' => array(
                    'constraint' => array('queue_id' => 'CustomQueue.id'),
                ),
            ),
        );
    
        var $_columns;
    
        function getRoot($hint=false) {
            switch ($hint ?: $this->root) {
            case 'T':
            default:
                return 'Ticket';
            }
        }
    
        function getName() {
            return $this->name;
        }
    
        function getId() {
            return $this->id;
        }
    
        function applySort(QuerySet $query, $reverse=false, $root=false) {
            $fields = CustomQueue::getSearchableFields($this->getRoot($root));
            foreach ($this->getColumnPaths() as $path=>$descending) {
                $descending = $reverse ? !$descending : $descending;
                if (isset($fields[$path])) {
                    list(,$field) = $fields[$path];
                    $query = $field->applyOrderBy($query, $descending,
                        CustomQueue::getOrmPath($path, $query));
                }
            }
            return $query;
        }
    
        function getColumnPaths() {
            if (!isset($this->_columns)) {
                $columns = array();
                foreach (JsonDataParser::decode($this->columns) as $path) {
                    if ($descending = $path[0] == '-')
                        $path = substr($path, 1);
                    $columns[$path] = $descending;
                }
                $this->_columns = $columns;
            }
            return $this->_columns;
        }
    
        function getColumns() {
            $columns = array();
            $paths = $this->getColumnPaths();
            $everything = CustomQueue::getSearchableFields($this->getRoot());
            foreach ($paths as $p=>$descending) {
                if (isset($everything[$p])) {
                    $columns[$p] = array($everything[$p], $descending);
                }
            }
            return $columns;
        }
    
        function getDataConfigForm($source=false) {
            return new QueueSortDataConfigForm($source ?: $this->getDbFields(),
                array('id' => $this->id));
        }
    
        static function forQueue(CustomQueue $queue) {
            return static::objects()->filter([
    
                'root' => $queue->root ?: 'T',
    
            ]);
        }
    
        function save($refetch=false) {
            if ($this->dirty)
                $this->updated = SqlFunction::NOW();
            return parent::save($refetch || $this->dirty);
        }
    
        function update($vars, &$errors=array()) {
            if (!isset($vars['name']))
                $errors['name'] = __('A title is required');
    
            $this->name = $vars['name'];
            if (isset($vars['root']))
                $this->root = $vars['root'];
            elseif (!isset($this->root))
                $this->root = 'T';
    
            $fields = CustomQueue::getSearchableFields($this->getRoot($vars['root']));
            $columns = array();
            if (@is_array($vars['columns'])) {
                foreach ($vars['columns']as $path=>$info) {
                    $descending = (int) @$info['descending'];
                    // TODO: Check if column is valid, stash in $columns
                    if (!isset($fields[$path]))
                        continue;
                    $columns[] = ($descending ? '-' : '') . $path;
                }
                $this->columns = JsonDataEncoder::encode($columns);
            }
    
            if (count($errors))
                return false;
    
            return $this->save();
        }
    
        static function __create($vars) {
            $c = new static($vars);
            $c->save();
            return $c;
        }
    }
    
    class QueueSortGlue
    extends VerySimpleModel {
        static $meta = array(
            'table' => QUEUE_SORTING_TABLE,
            'pk' => array('sort_id', 'queue_id'),
            'joins' => array(
                'ordering' => array(
                    'constraint' => array('sort_id' => 'QueueSort.id'),
                ),
                'queue' => array(
                    'constraint' => array('queue_id' => 'CustomQueue.id'),
                ),
            ),
            'select_related' => array('ordering', 'queue'),
            'ordering' => array('sort'),
        );
    }
    
    class QueueSortGlueMIM
    extends ModelInstanceManager {
        function getOrBuild($modelClass, $fields, $cache=true) {
            $m = parent::getOrBuild($modelClass, $fields, $cache);
            if ($m && $modelClass === 'QueueSortGlue') {
                // Instead, yield the QueueColumn instance with the local fields
                // in the association table as annotations
                $m = AnnotatedModel::wrap($m->ordering, $m, 'QueueSort');
            }
            return $m;
        }
    }
    
    class QueueSortListBroker
    extends InstrumentedList {
        function __construct($fkey, $queryset=false) {
            parent::__construct($fkey, $queryset, 'QueueSortGlueMIM');
            $this->queryset->select_related('ordering');
        }
    
        function add($ordering, $glue=null, $php7_is_annoying=true) {
            $glue = $glue ?: new QueueSortGlue();
            $glue->ordering = $ordering;
            $anno = AnnotatedModel::wrap($ordering, $glue);
            parent::add($anno, false);
            return $anno;
        }
    }
    
    
    abstract class QueueColumnFilter {
        static $registry;
    
        static $id = null;
        static $desc = null;
    
    
        static function register($filter, $group) {
    
            if (!isset($filter::$id))
                throw new Exception('QueueColumnFilter must define $id');
            if (isset(static::$registry[$filter::$id]))
                throw new Exception($filter::$id
                    . ': QueueColumnFilter already registered under that id');
            if (!is_subclass_of($filter, get_called_class()))
                throw new Exception('Filter must extend QueueColumnFilter');
    
    
            static::$registry[$filter::$id] = array($group, $filter);
    
        }
    
        static function getFilters() {
    
            $list = static::$registry;
            $base = array();
            foreach ($list as $id=>$stuff) {
                list($group, $class) = $stuff;
                $base[$group][$id] = __($class::$desc);
    
            }
            return $base;
        }
    
        static function getInstance($id) {
    
            if (isset(static::$registry[$id])) {
    
                list(, $class) = @static::$registry[$id];
                if ($class && class_exists($class))
                    return new $class();
    
        }
    
        function mangleQuery($query, $column) { return $query; }
    
        abstract function filter($value, $row);
    }
    
    class TicketLinkFilter
    extends QueueColumnFilter {
        static $id = 'link:ticket';
        static $desc = /* @trans */ "Ticket Link";
    
        function filter($text, $row) {
    
            if ($link = $this->getLink($row))
    
                return sprintf('<a style="display:inline" href="%s">%s</a>', $link, $text);
    
        }
    
        function mangleQuery($query, $column) {
            static $fields = array(
                'link:ticket'   => 'ticket_id',
                'link:ticketP'  => 'ticket_id',
                'link:user'     => 'user_id',
                'link:org'      => 'user__org_id',
            );
    
            if (isset($fields[static::$id])) {
                $query = $query->values($fields[static::$id]);
            }
            return $query;
        }
    
        function getLink($row) {
            return Ticket::getLink($row['ticket_id']);
        }
    }
    
    class UserLinkFilter
    extends TicketLinkFilter {
        static $id = 'link:user';
        static $desc = /* @trans */ "User Link";
    
        function getLink($row) {
            return User::getLink($row['user_id']);
        }
    }
    
    class OrgLinkFilter
    extends TicketLinkFilter {
        static $id = 'link:org';
        static $desc = /* @trans */ "Organization Link";
    
        function getLink($row) {
    
            return Organization::getLink($row['user__org_id']);
    
    QueueColumnFilter::register('TicketLinkFilter', __('Link'));
    QueueColumnFilter::register('UserLinkFilter', __('Link'));
    QueueColumnFilter::register('OrgLinkFilter', __('Link'));
    
    
    class TicketLinkWithPreviewFilter
    extends TicketLinkFilter {
        static $id = 'link:ticketP';
        static $desc = /* @trans */ "Ticket Link with Preview";
    
        function filter($text, $row) {
            $link = $this->getLink($row);
    
            return sprintf('<a style="display: inline" class="preview" data-preview="#tickets/%d/preview" href="%s">%s</a>',
    
                $row['ticket_id'], $link, $text);
        }
    }
    
    QueueColumnFilter::register('TicketLinkWithPreviewFilter', __('Link'));
    
    class DateTimeFilter
    extends QueueColumnFilter {
        static $id = 'date:full';
        static $desc = /* @trans */ "Date and Time";
    
        function filter($text, $row) {
    
            return $text ?
                $text->changeTo(Format::datetime($text->value)) : '';
    
        }
    }
    
    class HumanizedDateFilter
    extends QueueColumnFilter {
        static $id = 'date:human';
        static $desc = /* @trans */ "Relative Date and Time";
    
        function filter($text, $row) {
            return sprintf(
                '<time class="relative" datetime="%s" title="%s">%s</time>',
                date(DateTime::W3C, Misc::db2gmtime($text->value)),
                Format::daydatetime($text->value),
                Format::relativeTime(Misc::db2gmtime($text->value))
            );
        }
    }
    QueueColumnFilter::register('DateTimeFilter', __('Date Format'));
    QueueColumnFilter::register('HumanizedDateFilter', __('Date Format'));
    
    class QueueColDataConfigForm
    extends AbstractForm {
    
        function buildFields() {
            return array(
                'primary' => new DataSourceField(array(
                    'label' => __('Primary Data Source'),
    
                    'required' => true,
    
                    'configuration' => array(
                        'root' => 'Ticket',
                    ),
                    'layout' => new GridFluidCell(6),
                )),
                'secondary' => new DataSourceField(array(
                    'label' => __('Secondary Data Source'),
                    'configuration' => array(
                        'root' => 'Ticket',
                    ),
                    'layout' => new GridFluidCell(6),
                )),
                'name' => new TextboxField(array(
                    'label' => __('Name'),
                    'required' => true,
                    'layout' => new GridFluidCell(4),
    
                'filter' => new ChoiceField(array(
                    'label' => __('Filter'),
    
                    'required' => false,
    
                    'choices' => QueueColumnFilter::getFilters(),
    
                    'layout' => new GridFluidCell(4),
    
                )),
                'truncate' => new ChoiceField(array(
                    'label' => __('Text Overflow'),
                    'choices' => array(
                        'wrap' => __("Wrap Lines"),
                        'ellipsis' => __("Add Ellipsis"),
                        'clip' => __("Clip Text"),
    
                        'lclip' => __("Clip Beginning Text"),
    
                    ),
                    'default' => 'wrap',
    
                    'layout' => new GridFluidCell(4),
    
    
    class QueueSortDataConfigForm
    extends AbstractForm {
        function getInstructions() {
    
            return __('Add, and remove the fields in this list using the options below. Sorting can be performed on any field, whether displayed in the queue or not.');
    
        }
    
        function buildFields() {
            return array(
                'name' => new TextboxField(array(
                    'required' => true,
                    'layout' => new GridFluidCell(12),
                    'translatable' => isset($this->options['id'])
                        ? _H('queuesort.name.'.$this->options['id']) : false,
                    'configuration' => array(
                        'placeholder' => __('Sort Criteria Title'),
                    ),
                )),
            );
        }