From 7e6e203ab051d8a74a8fc09b18f4d2d7641a6150 Mon Sep 17 00:00:00 2001 From: Jared Hancock <jared@osticket.com> Date: Wed, 16 Dec 2015 20:57:15 -0600 Subject: [PATCH] queue: Add date format filter, fix several UI issues * Add relative and full formats to the filter list * All dates in queue are database-relative * Fix very odd rendering of conditions in queue table * Fix "clip" truncate mode * Re-implement background color for Priority column * Allocate no space for hidden annotations * Add checkboxes to queue preview for closer resemblance to ticket queue * Add default formats to initial date columns --- include/class.forms.php | 9 +- include/class.queue.php | 152 ++++++++++++++---- include/class.ticket.php | 5 + include/i18n/en_US/queue_column.yaml | 6 + .../staff/templates/queue-preview.tmpl.php | 8 +- .../staff/templates/queue-tickets.tmpl.php | 8 +- 6 files changed, 148 insertions(+), 40 deletions(-) diff --git a/include/class.forms.php b/include/class.forms.php index 054302393..774485ffc 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -2308,11 +2308,14 @@ class PriorityField extends ChoiceField { : $prio; } - function display($prio) { + function display($prio, &$styles=null) { if (!$prio instanceof Priority) return parent::display($prio); - return sprintf('<span class="fill" style="padding: 2px; background-color: %s">%s</span>', - $prio->getColor(), Format::htmlchars($prio->getDesc())); + if (is_array($styles)) + $styles += array( + 'background-color' => $prio->getColor() + ); + return Format::htmlchars($prio->getDesc()); } function toString($value) { diff --git a/include/class.queue.php b/include/class.queue.php index 8053f6937..6068f7049 100644 --- a/include/class.queue.php +++ b/include/class.queue.php @@ -294,8 +294,12 @@ abstract class QueueColumnAnnotation { /** * Estimate the width of the rendered annotation in pixels */ - function getWidth() { - return 15; + function getWidth($row) { + return $this->isVisible($row) ? 25 : 0; + } + + function isVisible($row) { + return true; } } @@ -323,6 +327,10 @@ extends QueueColumnAnnotation { ); } } + + function isVisible($row) { + return $row[static::$qname] > 1; + } } class ThreadAttachmentCount @@ -349,6 +357,10 @@ extends QueueColumnAnnotation { $count); } } + + function isVisible($row) { + return $row[static::$qname] > 0; + } } class ThreadCollaboratorCount @@ -373,6 +385,10 @@ extends QueueColumnAnnotation { $count); } } + + function isVisible($row) { + return $row[static::$qname] > 0; + } } class OverdueFlagDecoration @@ -388,6 +404,10 @@ extends QueueColumnAnnotation { if ($row['isoverdue']) return '<span class="Icon overdueTicket"></span>'; } + + function isVisible($row) { + return $row['isoverdue']; + } } class TicketSourceDecoration @@ -426,6 +446,10 @@ extends QueueColumnAnnotation { if ($row['_locked']) return sprintf('<span class="Icon lockedTicket"></span>'); } + + function isVisible($row) { + return $row['_locked']; + } } class DataSourceField @@ -540,20 +564,17 @@ class QueueColumnCondition { } } - function render($row, $text) { + function render($row, $text, &$styles=array()) { $annotation = $this->getAnnotationName(); if ($V = $row[$annotation]) { - $style = array(); foreach ($this->getProperties() as $css=>$value) { $field = QueueColumnConditionProperty::getField($css); $field->value = $value; $V = $field->getClean(); if (is_array($V)) $V = current($V); - $style[] = "{$css}:{$V}"; + $styles[$css] = $V; } - $text = sprintf('<span class="fill" style="%s">%s</span>', - implode(';', $style), $text); } return $text; } @@ -653,6 +674,36 @@ extends ChoiceField { } } +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) { + $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 @@ -743,22 +794,28 @@ extends VerySimpleModel { // Basic data $text = $this->renderBasicValue($row); - // Truncate - $text = $this->applyTruncate($text); - // Filter if ($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); + $text = $C->render($row, $text, $styles); } - return $text; + $style = Format::array_implode(':', ';', $styles); + return array($text, $style); } function renderBasicValue($row) { @@ -767,26 +824,28 @@ extends VerySimpleModel { $primary = SavedSearch::getOrmPath($this->primary); $secondary = SavedSearch::getOrmPath($this->secondary); - // TODO: Consider data filter if configured + // 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]) && (list(,$field) = $F) && ($T = $field->from_query($row, $primary)) ) { - return $field->display($field->to_php($T)); + return new LazyDisplayWrapper($field, $T); } if (($F = $fields[$secondary]) && (list(,$field) = $F) && ($T = $field->from_query($row, $secondary)) ) { - return $field->display($field->to_php($T)); + return new LazyDisplayWrapper($field, $T); } } - function applyTruncate($text) { + function applyTruncate($text, $row) { $offset = 0; foreach ($this->getAnnotations() as $a) - $offset += $a->getWidth(); + $offset += $a->getWidth($row); $width = $this->width - $offset; switch ($this->truncate) { @@ -795,7 +854,7 @@ extends VerySimpleModel { 'truncate', $width, $text); case 'clip': return sprintf('<span class="%s" style="max-width:%dpx">%s</span>', - 'truncate clip', $width, $text); + 'truncate bleed', $width, $text); default: case 'wrap': return $text; @@ -1021,7 +1080,7 @@ abstract class QueueColumnFilter { static $id = null; static $desc = null; - static function register($filter) { + static function register($filter, $group) { if (!isset($filter::$id)) throw new Exception('QueueColumnFilter must define $id'); if (isset(static::$registry[$filter::$id])) @@ -1030,20 +1089,24 @@ abstract class QueueColumnFilter { if (!is_subclass_of($filter, get_called_class())) throw new Exception('Filter must extend QueueColumnFilter'); - static::$registry[$filter::$id] = $filter; + static::$registry[$filter::$id] = array($group, $filter); } static function getFilters() { - $base = static::$registry; - foreach ($base as $id=>$class) { - $base[$id] = __($class::$desc); + $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])) - return new static::$registry[$id](); + if (isset(static::$registry[$id])) { + list(, $class) = static::$registry[$id]; + return new $class(); + } } function mangleQuery($query, $column) { return $query; } @@ -1058,7 +1121,7 @@ extends QueueColumnFilter { function filter($text, $row) { if ($link = $this->getLink($row)) - return sprintf('<a href="%s">%s</a>', $link, $text); + return sprintf('<a style="display:inline" href="%s">%s</a>', $link, $text); } function mangleQuery($query, $column) { @@ -1099,9 +1162,9 @@ extends TicketLinkFilter { return Organization::getLink($row['user__org_id']); } } -QueueColumnFilter::register('TicketLinkFilter'); -QueueColumnFilter::register('UserLinkFilter'); -QueueColumnFilter::register('OrgLinkFilter'); +QueueColumnFilter::register('TicketLinkFilter', __('Link')); +QueueColumnFilter::register('UserLinkFilter', __('Link')); +QueueColumnFilter::register('OrgLinkFilter', __('Link')); class TicketLinkWithPreviewFilter extends TicketLinkFilter { @@ -1110,11 +1173,38 @@ extends TicketLinkFilter { function filter($text, $row) { $link = $this->getLink($row); - return sprintf('<a class="preview" data-preview="#tickets/%d/preview" href="%s">%s</a>', + 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'); +QueueColumnFilter::register('TicketLinkWithPreviewFilter', __('Link')); + +class DateTimeFilter +extends QueueColumnFilter { + static $id = 'date:full'; + static $desc = /* @trans */ "Date and Time"; + + function filter($text, $row) { + return $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 { diff --git a/include/class.ticket.php b/include/class.ticket.php index 4a424f672..417e7be77 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -1863,18 +1863,23 @@ implements RestrictedAccess, Threadable, Searchable { )), 'created' => new DatetimeField(array( 'label' => __('Create Date'), + 'configuration' => array('fromdb' => true), )), 'est_duedate' => new DatetimeField(array( 'label' => __('Due Date'), + 'configuration' => array('fromdb' => true), )), 'reopened' => new DatetimeField(array( 'label' => __('Reopen Date'), + 'configuration' => array('fromdb' => true), )), 'closed' => new DatetimeField(array( 'label' => __('Close Date'), + 'configuration' => array('fromdb' => true), )), 'lastupdate' => new DatetimeField(array( 'label' => __('Last Update'), + 'configuration' => array('fromdb' => true), )), 'assignee' => new AssigneeChoiceField(array( 'label' => __('Assignee'), diff --git a/include/i18n/en_US/queue_column.yaml b/include/i18n/en_US/queue_column.yaml index 1777af857..bdc10fd76 100644 --- a/include/i18n/en_US/queue_column.yaml +++ b/include/i18n/en_US/queue_column.yaml @@ -41,6 +41,7 @@ name: "Date Created" primary: "created" secondary: null + filter: "date:full" truncate: "wrap" annotations: "[]" conditions: "[]" @@ -77,6 +78,7 @@ - id: 7 name: "Close Date" primary: "closed" + filter: "date:full" truncate: "wrap" annotations: "[]" conditions: "[]" @@ -91,6 +93,7 @@ - id: 9 name: "Due Date" primary: "est_duedate" + filter: "date:human" truncate: "wrap" annotations: "[]" conditions: "[]" @@ -98,6 +101,7 @@ - id: 10 name: "Last Updated" primary: "lastupdate" + filter: "date:full" truncate: "wrap" annotations: "[]" conditions: "[]" @@ -112,6 +116,7 @@ - id: 12 name: "Last Message" primary: "thread__lastmessage" + filter: "date:human" truncate: "wrap" annotations: "[]" conditions: "[]" @@ -119,6 +124,7 @@ - id: 12 name: "Last Response" primary: "thread__lastresponse" + filter: "date:human" truncate: "wrap" annotations: "[]" conditions: "[]" diff --git a/include/staff/templates/queue-preview.tmpl.php b/include/staff/templates/queue-preview.tmpl.php index 18e231160..81ae549f9 100644 --- a/include/staff/templates/queue-preview.tmpl.php +++ b/include/staff/templates/queue-preview.tmpl.php @@ -17,6 +17,7 @@ $columns = $queue->getColumns(); <table class="list queue" border="0" cellspacing="1" cellpadding="2" width="940"> <thead> <tr> + <th width="12px"></th> <?php foreach ($columns as $C) { echo sprintf('<th width="%s">%s</th>', $C->getWidth(), @@ -28,9 +29,12 @@ foreach ($columns as $C) { <?php foreach ($tickets as $T) { echo '<tr>'; + echo '<td><input type="checkbox" disabled="disabled" /></td>'; foreach ($columns as $C) { - echo '<td class="offset">'; - echo $C->render($T); + list($content, $styles) = $C->render($T); + $style = $styles ? 'style="'.$styles.'"' : ''; + echo "<td $style>"; + echo "<div $style>$content</div>"; echo "</td>"; } echo '</tr>'; diff --git a/include/staff/templates/queue-tickets.tmpl.php b/include/staff/templates/queue-tickets.tmpl.php index 99238c950..3c05576dc 100644 --- a/include/staff/templates/queue-tickets.tmpl.php +++ b/include/staff/templates/queue-tickets.tmpl.php @@ -157,13 +157,13 @@ foreach ($columns as $C) { foreach ($tickets as $T) { echo '<tr>'; if ($canManageTickets) { ?> - <td><input type="checkbox" name="ckb[]" /></td> + <td><input type="checkbox" class="ckb" name="ckb[]" /></td> <?php } foreach ($columns as $C) { - echo '<td class="offset">'; - echo $C->render($T); - echo "</td>"; + list($contents, $styles) = $C->render($T); + $style = $styles ? 'style="'.$styles.'"' : ''; + echo "<td $style><div $style>$contents</div></td>"; } echo '</tr>'; } -- GitLab