diff --git a/bootstrap.php b/bootstrap.php index df723ae7af7c9e2da93e1ae81ca7fcb8b652dbf2..6d7acf23f8152980feabb54d00ffdf3cb0d53b6a 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -133,7 +133,8 @@ class Bootstrap { define('SEQUENCE_TABLE', $prefix.'sequence'); define('TRANSLATION_TABLE', $prefix.'translation'); define('QUEUE_TABLE', $prefix.'queue'); - define('QUEUE_COLUMN_TABLE', $prefix.'queue_column'); + define('COLUMN_TABLE', $prefix.'queue_column'); + define('QUEUE_COLUMN_TABLE', $prefix.'queue_columns'); define('API_KEY_TABLE',$prefix.'api_key'); define('TIMEZONE_TABLE',$prefix.'timezone'); diff --git a/include/ajax.admin.php b/include/ajax.admin.php index ddeb7c5db090d30078925e202db7de47e36bd475..b1f910c2a08d7464a4c3b47c391a884664206789 100644 --- a/include/ajax.admin.php +++ b/include/ajax.admin.php @@ -190,4 +190,29 @@ class AdminAjaxAPI extends AjaxController { include STAFFINC_DIR . 'templates/quick-add.tmpl.php'; } + + function addQueueColumn($root='Ticket') { + global $ost, $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + + $column = QueueColumn::create(); + if ($_POST) { + $data_form = $column->getDataConfigForm($_POST); + if ($data_form->isValid()) { + $column->update($_POST, $root); + if ($column->save()) + Http::response(201, $this->encode(array( + 'id' => $column->getId(), + 'name' => (string) $column->getName(), + ), 'application/json')); + } + } + + include STAFFINC_DIR . 'templates/queue-column-add.tmpl.php'; + + } } diff --git a/include/ajax.search.php b/include/ajax.search.php index 5ef597a5538b51acb77d724ba09da84e46d435e2..d11a347576474364ffbcf973e0e65a030660e959 100644 --- a/include/ajax.search.php +++ b/include/ajax.search.php @@ -212,19 +212,27 @@ class SearchAjaxAPI extends AjaxController { ))); } - - function editColumn($queue_id, $column) { + function editColumn($column_id) { global $thisstaff; if (!$thisstaff) { Http::response(403, 'Agent login is required'); } - elseif (!($queue = CustomQueue::lookup($queue_id))) { + elseif (!($column = QueueColumn::lookup($column_id))) { Http::response(404, 'No such queue'); } - $data_form = new QueueDataConfigForm($_POST); - include STAFFINC_DIR . 'templates/queue-column.tmpl.php'; + if ($_POST) { + $data_form = $column->getDataConfigForm($_POST); + if ($data_form->isValid()) { + $column->update($_POST, 'Ticket'); + if ($column->save()) + Http::response(201, 'Successfully updated'); + } + } + + $root = 'Ticket'; + include STAFFINC_DIR . 'templates/queue-column-edit.tmpl.php'; } function previewQueue($id=false) { @@ -288,43 +296,4 @@ class SearchAjaxAPI extends AjaxController { $id = $_GET['condition']; include STAFFINC_DIR . 'templates/queue-column-condition-prop.tmpl.php'; } - - function addColumn() { - global $thisstaff; - - if (!$thisstaff) { - Http::response(403, 'Agent login is required'); - } - elseif (!isset($_GET['field'])) { - Http::response(400, '`field` parameter is required'); - } - - $field = $_GET['field']; - // XXX: This method should receive a queue ID or queue root so that - // $field can be properly checked - $fields = SavedSearch::getSearchableFields('Ticket'); - if (!isset($fields[$field])) { - Http::response(400, 'Not a supported field for this queue'); - } - - // Get the tabbed column configuration - list($label, $F) = $fields[$field]; - $column = QueueColumn::create(array( - "id" => (int) $_GET['id'], - "heading" => _S($F->getLabel()), - "primary" => $field, - "width" => 100, - )); - ob_start(); - include STAFFINC_DIR . 'templates/queue-column.tmpl.php'; - $config = ob_get_clean(); - - // Send back the goodies - Http::response(200, $this->encode(array( - 'config' => $config, - 'id' => $column->id, - 'heading' => _S($F->getLabel()), - 'width' => $column->getWidth(), - )), 'application/json'); - } } diff --git a/include/class.i18n.php b/include/class.i18n.php index d7aaa71ed98e7a296b0efd09cb7097602b3e3962..9d8575c1379a14fe823dfe5780d2ff443d1b7f04 100644 --- a/include/class.i18n.php +++ b/include/class.i18n.php @@ -66,6 +66,7 @@ class Internationalization { 'role.yaml' => 'Role::__create', 'file.yaml' => 'AttachmentFile::__create', 'sequence.yaml' => 'Sequence::__create', + 'queue_column.yaml' => 'QueueColumn::__create', 'queue.yaml' => 'CustomQueue::__create', ); diff --git a/include/class.queue.php b/include/class.queue.php index 77077e13824d41957a369681e109fc5e3e1ea4f2..2f1aa3f20dfbe46539a11d9f41f017f4dfdee88f 100644 --- a/include/class.queue.php +++ b/include/class.queue.php @@ -21,7 +21,8 @@ class CustomQueue extends SavedSearch { 'select_related' => array('parent'), 'joins' => array( 'columns' => array( - 'reverse' => 'QueueColumn.queue', + 'reverse' => 'QueueColumnGlue.queue', + 'broker' => 'QueueColumnListBroker', ), 'children' => array( 'reverse' => 'CustomQueue.parent', @@ -113,7 +114,7 @@ class CustomQueue extends SavedSearch { // Apply column, annotations and conditions additions foreach ($this->getColumns() as $C) { - $query = $C->mangleQuery($query); + $query = $C->mangleQuery($query, $this->getRoot()); } return $query; } @@ -145,21 +146,30 @@ class CustomQueue extends SavedSearch { // Update queue columns (but without save) if (isset($vars['columns'])) { $new = $vars['columns']; + $order = array_keys($new); foreach ($this->columns as $col) { - if (false === ($sort = array_search($col->id, $vars['columns']))) { + $key = $col->column_id; + if (!isset($vars['columns'][$key])) { $this->columns->remove($col); continue; } - $col->set('sort', $sort+1); - $col->update($vars, $errors); - unset($new[$sort]); + $info = $vars['columns'][$key]; + $col->set('sort', array_search($key, $order)); + $col->set('heading', $info['heading']); + $col->set('width', $info['width']); + unset($new[$key]); } // Add new columns - foreach ($new as $sort=>$colid) { - $col = QueueColumn::create(array("id" => $colid, "queue" => $this)); - $col->set('sort', $sort+1); - $col->update($vars, $errors); - $this->addColumn($col); + foreach ($new as $info) { + $glue = QueueColumnGlue::create(array( + 'column_id' => $info['column_id'], + 'sort' => array_search($info['column_id'], $order), + 'heading' => $info['heading'], + 'width' => $info['width'] ?: 100 + )); + $glue->queue = $this; + $this->columns->add( + QueueColumn::lookup($info['column_id']), $glue); } // Re-sort the in-memory columns array $this->columns->sort(function($c) { return $c->sort; }); @@ -193,6 +203,11 @@ class CustomQueue extends SavedSearch { static function __create($vars) { $q = static::create($vars); $q->save(); + foreach ($vars['columns'] as $info) { + $glue = QueueColumnGlue::create($info); + $glue->queue_id = $q->getId(); + $glue->save(); + } return $q; } } @@ -628,14 +643,8 @@ extends ChoiceField { class QueueColumn extends VerySimpleModel { static $meta = array( - 'table' => QUEUE_COLUMN_TABLE, + 'table' => COLUMN_TABLE, 'pk' => array('id'), - 'ordering' => array('sort'), - 'joins' => array( - 'queue' => array( - 'constraint' => array('queue_id' => 'SavedSearch.id'), - ), - ), ); var $_annotations; @@ -645,22 +654,40 @@ extends VerySimpleModel { return $this->id; } + function getFilter() { + if ($this->filter) + return QueueColumnFilter::getInstance($this->filter); + } + + function getName() { + return $this->name; + } + + // These getters fetch data from the annotated overlay from the + // queue_column table function getQueue() { return $this->queue; } + function getWidth() { + return $this->width ?: 100; + } + function getHeading() { return $this->heading; } - function getWidth() { - return $this->width ?: 100; + 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'); } - - function getFilter() { - if ($this->filter) - return QueueColumnFilter::getInstance($this->filter); - } function render($row) { // Basic data @@ -732,9 +759,9 @@ extends VerySimpleModel { return $field->addToQuery($query, $path); } - function mangleQuery($query) { + function mangleQuery($query, $root=null) { // Basic data - $fields = SavedSearch::getSearchableFields($this->getQueue()->getRoot()); + $fields = SavedSearch::getSearchableFields($root ?: $this->getQueue()->getRoot()); if ($primary = $fields[$this->primary]) { list(,$field) = $primary; $query = $this->addToQuery($query, $field, @@ -763,7 +790,7 @@ extends VerySimpleModel { } function getDataConfigForm($source=false) { - return new QueueColDataConfigForm($source ?: $this->ht, + return new QueueColDataConfigForm($source ?: $this->__getDbFields(), array('id' => $this->id)); } @@ -805,7 +832,13 @@ extends VerySimpleModel { return $inst; } - function update($vars) { + static function __create($vars) { + $c = static::create($vars); + $c->save(); + return $c; + } + + function update($vars, $root='Ticket') { $form = $this->getDataConfigForm($vars); foreach ($form->getClean() as $k=>$v) $this->set($k, $v); @@ -833,7 +866,7 @@ extends VerySimpleModel { continue; // Determine the criteria $name = $vars['condition_field'][$i]; - $fields = SavedSearch::getSearchableFields($this->getQueue()->getRoot()); + $fields = SavedSearch::getSearchableFields($root); if (!isset($fields[$name])) // No such field exists for this queue root type continue; @@ -884,6 +917,48 @@ extends VerySimpleModel { } } +class QueueColumnGlue +extends VerySimpleModel { + static $meta = array( + 'table' => QUEUE_COLUMN_TABLE, + 'pk' => array('queue_id', 'column_id'), + 'joins' => array( + 'column' => array( + 'constraint' => array('column_id' => 'QueueColumn.id'), + ), + 'queue' => array( + 'constraint' => array('queue_id' => 'CustomQueue.id'), + ), + ), + 'select_related' => array('column', 'queue'), + 'ordering' => array('sort'), + ); +} + +class QueueColumnListBroker +extends InstrumentedList { + function __construct($fkey, $queryset=false) { + parent::__construct($fkey, $queryset); + $this->queryset->select_related('column'); + } + + 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 + // inthe association table as annotations + $m = AnnotatedModel::wrap($m->column, $m, 'QueueColumn'); + } + return $m; + } + + function add($column, $glue=null) { + $glue = $glue ?: QueueColumnGlue::create(); + $glue->column = $column; + parent::add(AnnotatedModel::wrap($column, $glue)); + } +} + abstract class QueueColumnFilter { static $registry; @@ -987,41 +1062,33 @@ QueueColumnFilter::register('TicketLinkWithPreviewFilter'); 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), - )), - 'heading' => new TextboxField(array( - 'label' => __('Heading'), + function buildFields() { + return array( + 'primary' => new DataSourceField(array( + 'label' => __('Primary Data Source'), 'required' => true, - 'layout' => new GridFluidCell(3), + '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(3), - )), - 'width' => new TextboxField(array( - 'label' => __('Width'), - 'default' => 75, - 'configuration' => array( - 'validator' => 'number', - ), - 'layout' => new GridFluidCell(3), + 'layout' => new GridFluidCell(4), )), 'truncate' => new ChoiceField(array( 'label' => __('Text Overflow'), @@ -1031,7 +1098,7 @@ function buildFields() { 'clip' => __("Clip Text"), ), 'default' => 'wrap', - 'layout' => new GridFluidCell(3), + 'layout' => new GridFluidCell(4), )), ); } diff --git a/include/i18n/en_US/queue.yaml b/include/i18n/en_US/queue.yaml index ce7b6a344d2eb6933bd3fe00e3b4391d564e1511..19c096d2031a9973f85d08b6f847d6ac1944f289 100644 --- a/include/i18n/en_US/queue.yaml +++ b/include/i18n/en_US/queue.yaml @@ -26,37 +26,6 @@ # 'T': Tickets # 'A': Tasks # -# Columns are not necessary and a default list is used if no columns are -# specified. -# -# columns: Array of column instances with these fields -# flags: (unused) -# sort: Manual sort order of the queue -# heading: Display name of the column header -# primary: Data source for the field -# secondary: Backup data source / default text -# width: Width weight of the column -# filter: What the field should link to -# 'link:ticket': Ticket -# 'link:user': User -# 'link:org': Organization -# 'link:ticketP': Ticket with hover preview -# truncate: -# 'wrap': Fold words on multiple lines -# annotations: -# c: Annotation class name -# p: Placement -# 'a': After column text -# 'b': Before column text -# '<': Float to start (left) -# '>': Float to end (right) -# conditions: -# crit: Criteria for the condiditon, in the form of [field, method, value] -# prop: Array of CSS properties to apply to the field -# 'font-weight': -# 'font-style': -# ... -# extra: (future use and for plugins) --- - id: 1 title: Open @@ -64,6 +33,31 @@ sort: 1 root: T config: '[["status__state","includes",{"open":"Open"}]]' + columns: + - column_id: 1 + sort: 1 + width: 75 + heading: Ticket + - column_id: 10 + sort: 2 + width: 150 + heading: Last Updated + - column_id: 3 + sort: 3 + width: 300 + heading: Subject + - column_id: 4 + sort: 4 + width: 185 + heading: From + - column_id: 5 + sort: 5 + width: 85 + heading: Priority + - column_id: 8 + sort: 6 + width: 160 + heading: Assigned To - id: 2 title: Closed @@ -71,6 +65,31 @@ sort: 2 root: T config: '[["status__state","includes",{"closed":"Closed"}]]' + columns: + - column_id: 1 + sort: 1 + width: 100 + heading: Ticket + - column_id: 7 + sort: 2 + width: 150 + heading: Date Closed + - column_id: 3 + sort: 3 + width: 300 + heading: Subject + - column_id: 4 + sort: 4 + width: 185 + heading: From + - column_id: 5 + sort: 5 + width: 85 + heading: Priority + - column_id: 8 + sort: 6 + width: 160 + heading: Closed By - title: Unanswered parent_id: 1 @@ -78,13 +97,63 @@ root: T sort: 1 config: '[["status__state","includes",{"open":"Open"}],["answered","nset",null]]' + columns: + - column_id: 1 + sort: 1 + width: 100 + heading: Ticket + - column_id: 10 + sort: 2 + width: 150 + heading: Last Update + - column_id: 3 + sort: 3 + width: 300 + heading: Subject + - column_id: 4 + sort: 4 + width: 185 + heading: From + - column_id: 5 + sort: 5 + width: 85 + heading: Priority + - column_id: 8 + sort: 6 + width: 160 + heading: Assigned To - title: Unassigned parent_id: 1 flags: 0x0b root: T sort: 2 - config: '[["assignee","unassigned",null]] + config: '[["assignee","unassigned",null]]' + columns: + - column_id: 1 + sort: 1 + width: 100 + heading: Ticket + - column_id: 10 + sort: 2 + width: 150 + heading: Last Update + - column_id: 3 + sort: 3 + width: 300 + heading: Subject + - column_id: 4 + sort: 4 + width: 185 + heading: From + - column_id: 5 + sort: 5 + width: 85 + heading: Priority + - column_id: 11 + sort: 6 + width: 160 + heading: Department - title: My Tickets parent_id: 1 @@ -92,3 +161,28 @@ root: T sort: 4 config: '[["assignee","includes",{"M":"Me", "T":"One of my teams"}]]' + columns: + - column_id: 1 + sort: 1 + width: 100 + heading: Ticket + - column_id: 10 + sort: 2 + width: 150 + heading: Last Update + - column_id: 3 + sort: 3 + width: 300 + heading: Subject + - column_id: 4 + sort: 4 + width: 185 + heading: From + - column_id: 5 + sort: 5 + width: 85 + heading: Priority + - column_id: 11 + sort: 6 + width: 160 + heading: Department diff --git a/include/staff/queue.inc.php b/include/staff/queue.inc.php index d873ccba35cc5b46f60ea8ac2713b9d1a130ea38..36eff7912446675921e48369a8f968e945fffc50 100644 --- a/include/staff/queue.inc.php +++ b/include/staff/queue.inc.php @@ -115,123 +115,68 @@ else { </div> <div class="hidden tab_content" id="columns"> - <h2><?php echo __("Manage columns in this queue"); ?></h2> - <p><?php echo __("Add, remove, and customize the content of the columns in this queue using the options below. Click a column header to manage or resize it"); ?></p> - - <div> - <i class="icon-plus-sign"></i> - <select id="add-column" data-next-id="0" onchange="javascript: - var $this = $(this), - selected = $this.find(':selected'), - nextId = $this.data('nextId'), - columns = $('#resizable-columns'); - $.ajax({ - url: 'ajax.php/queue/addColumn', - data: { field: selected.val(), id: nextId }, - dataType: 'json', - success: function(json) { - var div = $('<div></div>') - .addClass('column-header ui-resizable') - .text(json.heading) - .attr({'data-id': nextId}) - .data({colId: 'colconfig-'+nextId, width: json.width}) - .append($('<i>') - .addClass('icon-ellipsis-vertical ui-resizable-handle ui-resizable-handle-e') - ) - .append($('<input />') - .attr({type:'hidden', name:'columns[]'}) - .val(nextId) - ); - config = $('<div></div>') - .addClass('hidden column-configuration') - .attr('id', 'colconfig-' + nextId); - config.append($(json.config)).insertAfter(columns.append(div)); - $this.data('nextId', nextId+1); - } - }); - "> - <option value="">— <?php echo __('Add a column'); ?> —</option> -<?php foreach (SavedSearch::getSearchableFields('Ticket') as $path=>$f) { - list($label,) = $f; ?> - <option value="<?php echo $path; ?>"><?php echo $label; ?></option> + <table class="table two-column"> + <tbody> + <tr class="header"> + <th colspan="2"> + <?php echo __("Manage columns in this queue"); ?> + <div><small><?php echo __( + "Add, remove, and customize the content of the columns in this queue using the options below. Click a column header to manage or resize it"); ?> + </small></div> + </th> + </tr> + <tr class="header"> + <td><small><b><?php echo __('Column Name'); ?></b></small></td> + <td><small><b><?php echo __('Heading and Width'); ?></b></small></td> + </tr> + </tbody> + <tbody class="sortable-rows"> + <tr id="column-template" class="hidden"> + <td> + <i class="faded-more icon-sort"></i> + <input type="hidden" data-name="queue_id" + value="<?php echo $queue->getId(); ?>"/> + <input type="hidden" data-name="column_id" /> + <span></span> + </td> + <td> + <input type="text" size="25" data-name="heading" + data-translate-tag="" /> + <input type="text" size="5" data-name="width" /> + <a class="action-button" + href="#" onclick="javascript: + var colid = $(this).closest('tr').find('[data-name=column_id]').val(); + $.dialog('ajax.php/tickets/search/column/edit/' + colid, 201); + return false; + "><i class="icon-edit"></i> <?php echo __('Edit'); ?></a> + <a href="#" class="pull-right drop-column" title="<?php echo __('Delete'); + ?>"><i class="icon-trash"></i></a> + </td> + </tr> + </tbody> + <tbody> + <tr class="header"> + <td colspan="2"></td> + </tr> + <tr> + <td colspan="2" id="append-column"> + <i class="icon-plus-sign"></i> + <select id="add-column" data-quick-add="queue-column"> + <option value="">— <?php echo __('Add a column'); ?> —</option> +<?php foreach (QueueColumn::objects() as $C) { ?> + <option value="<?php echo $C->id; ?>"><?php echo + Format::htmlchars($C->name); ?></option> <?php } ?> - </select> - - <div id="resizable-columns"> -<?php foreach ($queue->getColumns() as $column) { - $colid = $column->getId(); - $maxcolid = max(@$maxcolid ?: 0, $colid); - echo sprintf('<div data-id="%1$s" data-col-id="colconfig-%1$s" class="column-header" ' - .'data-width="%2$s">%3$s' - .'<i class="icon-ellipsis-vertical ui-resizable-handle ui-resizable-handle-e"></i>' - .'<input type="hidden" name="columns[]" value="%1$s"/>' - .'</div>', - $colid, $column->getWidth(), $column->getHeading(), - $column->sort ?: 1); -} ?> - </div> - <script> - $(function() { - $('#add-column').data('nextId', <?php echo $maxcolid+1; ?>); - var qq = setInterval(function() { - var total = 0, - container = $('#resizable-columns'), - width = container.width(), - w2px = 1.25, - columns = $('.column-header', container); - // Await computation of the <div>'s width - if (width) - clearInterval(qq); - columns.each(function() { - total += $(this).data('width') || 100; - }); - container.data('w2px', w2px); - columns.each(function() { - // FIXME: jQuery will compensate for padding (40px) - $(this).width(w2px * ($(this).data('width') || 100) - 42); - }); - }, 20); - }); - </script> - -<?php foreach ($queue->getColumns() as $column) { - $colid = $column->getId(); - echo sprintf('<div class="hidden column-configuration" id="colconfig-%s">', - $colid); - include STAFFINC_DIR . 'templates/queue-column.tmpl.php'; - echo '</div>'; -} ?> - </div> + <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> + </select> + <button type="button" class="green button"> + <?php echo __('Add'); ?> + </button> + </td> + </tr> + </tbody> + </table> - <script> - var aa = setInterval(function() { - var cols = $('#resizable-columns'); - if (cols.length && cols.sortable) - clearInterval(aa); - cols.sortable({ - containment: 'parent' - }); - $('.column-header', cols).resizable({ - handles: {'e' : '.ui-resizable-handle'}, - grid: [ 20, 0 ], - maxHeight: 16, - minHeight: 16, - stop: function(event, ui) { - var w2px = ui.element.parent().data('w2px'), - width = ui.element.width() - 42; - ui.element.data('width', width / w2px); - // TODO: Update WIDTH text box in the data form - } - }); - cols.click('.column-header', function(e) { - var $this = $(event.target); - $this.parent().children().removeClass('active'); - $this.addClass('active'); - $('.column-configuration', $this.closest('.tab_content')).hide(); - $('#'+$this.data('colId')).fadeIn('fast'); - }); - }, 20); - </script> </div> <div class="hidden tab_content" id="preview-tab"> @@ -263,3 +208,58 @@ else { </p> </form> + +<script> +var addColumn = function(colid, info) { + if (!colid) return; + var copy = $('#column-template').clone(), + name_prefix = 'columns[' + colid + ']'; + info['column_id'] = colid; + copy.find('input[data-name]').each(function() { + var $this = $(this), + name = $this.data('name'); + + if (info[name] !== undefined) + $this.val(info[name]); + $this.attr('name', name_prefix + '[' + name + ']'); + }); + copy.find('span').text(info['name']); + copy.attr('id', '').show().insertBefore($('#column-template')); + copy.removeClass('hidden'); + if (info['trans'] !== undefined) { + var input = copy.find('input[data-translate-tag]') + .attr('data-translate-tag', info['trans']); + if ($.fn.translatable) + input.translatable(); + // Else it will be made translatable when the JS library is loaded + } + copy.find('a.drop-column').click(function() { + $('<option>') + .attr('value', copy.find('input[data-name=column_id]').val()) + .text(info.name) + .insertBefore($('#add-column') + .find('[data-quick-add]') + ); + copy.fadeOut(function() { $(this).remove(); }); + return false; + }); + var selected = $('#add-column').find('option[value=' + colid + ']'); + selected.remove(); +}; + +$('#append-column').find('button').on('click', function() { + var selected = $('#add-column').find(':selected'), + id = parseInt(selected.val()); + if (!id) + return; + addColumn(id, {name: selected.text(), heading: selected.text(), width: 100}); + return false; +}); + +<?php foreach ($queue->columns as $C) { + echo sprintf('addColumn(%d, {name: %s, heading: %s, width: %d, trans: %s});', + $C->column_id, JsonDataEncoder::encode($C->name), + JsonDataEncoder::encode($C->heading), $C->width, + JsonDataEncoder::encode($C->getTranslateTag('heading'))); +} ?> +</script> diff --git a/include/staff/templates/queue-column-add.tmpl.php b/include/staff/templates/queue-column-add.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..f10e303d13bdbf64d1515c04ecbae71bd3f51b16 --- /dev/null +++ b/include/staff/templates/queue-column-add.tmpl.php @@ -0,0 +1,30 @@ +<?php +/** + * Calling conventions + * + * $column - <QueueColumn> instance for this column + */ +$colid = 0; +?> +<h3 class="drag-handle"><?php echo __('Add Queue Column'); ?></h3> +<a class="close" href=""><i class="icon-remove-circle"></i></a> +<hr/> + +<form method="post" action="#admin/quick-add/queue-column"> + +<?php +include 'queue-column.tmpl.php'; +?> + +<hr> +<p class="full-width"> + <span class="buttons pull-left"> + <input type="reset" value="<?php echo __('Reset'); ?>"> + <input type="button" value="<?php echo __('Cancel'); ?>" class="close"> + </span> + <span class="buttons pull-right"> + <input type="submit" value="<?php echo __('Save'); ?>"> + </span> + </p> + + </form> diff --git a/include/staff/templates/queue-column-edit.tmpl.php b/include/staff/templates/queue-column-edit.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..c186335852e51ada91bbcb8357f08924c9f458f4 --- /dev/null +++ b/include/staff/templates/queue-column-edit.tmpl.php @@ -0,0 +1,32 @@ +<?php +/** + * Calling conventions + * + * $column - <QueueColumn> instance for this column + */ +$colid = $column->getId(); +?> +<h3 class="drag-handle"><?php echo __('Manage Queue Column'); ?> — + <?php echo $column->get('name') ?></h3> +<a class="close" href=""><i class="icon-remove-circle"></i></a> +<hr/> + +<form method="post" action="#tickets/search/column/edit/<?php + echo $colid; ?>"> + +<?php +include 'queue-column.tmpl.php'; +?> + +<hr> +<p class="full-width"> + <span class="buttons pull-left"> + <input type="reset" value="<?php echo __('Reset'); ?>"> + <input type="button" value="<?php echo __('Cancel'); ?>" class="close"> + </span> + <span class="buttons pull-right"> + <input type="submit" value="<?php echo __('Save'); ?>"> + </span> + </p> + + </form> diff --git a/include/staff/templates/queue-column.tmpl.php b/include/staff/templates/queue-column.tmpl.php index 9890c73c6b25d00390aefac6d968970df2450d91..65867c95ff82a4e0eb2d1668bf30aa30bf4ae62c 100644 --- a/include/staff/templates/queue-column.tmpl.php +++ b/include/staff/templates/queue-column.tmpl.php @@ -3,29 +3,26 @@ * Calling conventions * * $column - <QueueColumn> instance for this column + * $root - <Class> name of queue root ('Ticket') + * $data_form - <QueueColDataConfigForm> instance, optional */ $colid = $column->getId(); -$data_form = $column->getDataConfigForm($_POST); +$data_form = $data_form ?: $column->getDataConfigForm($_POST); ?> -<ul class="alt tabs"> - <li class="active"><a href="#<?php echo $colid; ?>-data"><?php echo __('Data'); ?></a></li> - <li><a href="#<?php echo $colid; ?>-annotations"><?php echo __('Annotations'); ?></a></li> - <li><a href="#<?php echo $colid; ?>-conditions"><?php echo __('Conditions'); ?></a></li> - <a onclick="javascript: - $(this).closest('.column-configuration').hide(); - $('#resizable-columns').find('div[data-id=<?php echo $colid; ?>]').hide() - .find('input[name^=columns]').remove(); - " class="button red pull-right"><?php echo __("Delete Column"); ?></a> +<ul class="tabs"> + <li class="active"><a href="#data"><?php echo __('Data'); ?></a></li> + <li><a href="#annotations"><?php echo __('Annotations'); ?></a></li> + <li><a href="#conditions"><?php echo __('Conditions'); ?></a></li> </ul> -<div class="tab_content" id="<?php echo $colid; ?>-data"> +<div class="tab_content" id="data"> <?php print $data_form->asTable(); ?> </div> <div class="hidden tab_content" data-col-id="<?php echo $colid; ?>" - id="<?php echo $colid; ?>-annotations" style="max-width: 400px"> + id="annotations" style="max-width: 400px"> <div class="empty placeholder" style="margin-left: 20px"> <em><?php echo __('No annotations for this field'); ?></em> </div> @@ -69,7 +66,7 @@ $data_form = $column->getDataConfigForm($_POST); <script> $(function() { var addAnnotation = function(type, desc, icon, pos) { - var template = $('.annotation.template', '#<?php echo $colid; ?>-annotations'), + var template = $('.annotation.template', '#annotations'), clone = template.clone().show().removeClass('template').insertBefore(template), input = clone.find('[data-field=input]'), colid = clone.closest('.tab_content').data('colId'), @@ -86,14 +83,14 @@ $data_form = $column->getDataConfigForm($_POST); position.val(pos); template.closest('.tab_content').find('.empty').hide(); }; - $('select.add-annotation', '#<?php echo $colid; ?>-annotations').change(function() { + $('select.add-annotation', '#annotations').change(function() { var selected = $(this).find(':selected'); addAnnotation(selected.val(), selected.text(), selected.data('icon')); selected.prop('disabled', true); }); - $('#<?php echo $colid; ?>-annotations').click('a[data-field=delete]', + $('#annotations').click('a[data-field=delete]', function() { - var tab = $('#<?php echo $colid; ?>-annotations'); + var tab = $('#annotations'); if ($('.annotation', tab).length === 0) tab.find('.empty').show(); }); @@ -110,13 +107,13 @@ $data_form = $column->getDataConfigForm($_POST); </div> </div> -<div class="hidden tab_content" id="<?php echo $colid; ?>-conditions"> +<div class="hidden tab_content" id="conditions"> <div style="margin: 0 20px"><?php echo __("Conditions are used to change the view of the data in a row based on some conditions of the data. For instance, a column might be shown bold if some condition is met."); ?></div> <div class="conditions" style="margin: 20px; max-width: 400px"> <?php if ($column->getConditions()) { - $fields = SavedSearch::getSearchableFields($column->getQueue()->getRoot()); + $fields = SavedSearch::getSearchableFields($root); foreach ($column->getConditions() as $i=>$condition) { $id = QueueColumnCondition::getUid(); list($label, $field) = $condition->getField(); diff --git a/include/staff/templates/queue-tickets.tmpl.php b/include/staff/templates/queue-tickets.tmpl.php index 0d999886a6b307e9923047107ceb59252aafb417..49a8125755f97eb5c8c9b461c30249c9b1926fa2 100644 --- a/include/staff/templates/queue-tickets.tmpl.php +++ b/include/staff/templates/queue-tickets.tmpl.php @@ -122,7 +122,7 @@ if ($canManageTickets) { ?> } foreach ($columns as $C) { echo sprintf('<th width="%s">%s</th>', $C->getWidth(), - Format::htmlchars($C->getHeading())); + Format::htmlchars($C->getLocalHeading())); } ?> </tr> </thead> @@ -145,8 +145,8 @@ foreach ($tickets as $T) { </tbody> <tfoot> <tr> - <td colspan="7"> - <?php if ($total && $canManageTickets) { + <td colspan="<?php echo count($columns)+1; ?>"> + <?php if ($count && $canManageTickets) { echo __('Select');?>: <a id="selectAll" href="#ckb"><?php echo __('All');?></a> <a id="selectNone" href="#ckb"><?php echo __('None');?></a> diff --git a/include/upgrader/streams/core/98ad7d55-00000000.patch.sql b/include/upgrader/streams/core/98ad7d55-00000000.patch.sql index b253c9efd9cafd9b498e8b217a02e7f1e19f2244..4993af2fdca44fe614f4efb5eccdaf7e135852f3 100644 --- a/include/upgrader/streams/core/98ad7d55-00000000.patch.sql +++ b/include/upgrader/streams/core/98ad7d55-00000000.patch.sql @@ -16,13 +16,10 @@ ALTER TABLE `%TABLE_PREFIX%queue` DROP TABLE IF EXISTS `%TABLE_PREFIX%queue_column`; CREATE TABLE `%TABLE_PREFIX%queue_column` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `queue_id` int(10) unsigned NOT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', - `sort` int(10) unsigned NOT NULL DEFAULT '0', - `heading` varchar(64) NOT NULL DEFAULT '', + `name` varchar(64) NOT NULL DEFAULT '', `primary` varchar(64) NOT NULL DEFAULT '', `secondary` varchar(64) DEFAULT NULL, - `width` int(10) unsigned DEFAULT NULL, `filter` varchar(32) DEFAULT NULL, `truncate` varchar(16) DEFAULT NULL, `annotations` text, @@ -31,3 +28,12 @@ CREATE TABLE `%TABLE_PREFIX%queue_column` ( PRIMARY KEY (`id`) ) DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `%TABLE_PREFIX%queue_columns`; +CREATE TABLE `%TABLE_PREFIX%queue_columns` ( + `queue_id` int(11) unsigned NOT NULL, + `column_id` int(11) unsigned NOT NULL, + `sort` int(10) unsigned NOT NULL DEFAULT '1', + `heading` varchar(64) DEFAULT NULL, + `width` int(10) unsigned NOT NULL DEFAULT '100', + PRIMARY KEY (`queue_id`, `column_id`) +) DEFAULT CHARSET=utf8; diff --git a/scp/ajax.php b/scp/ajax.php index 1069c3b078044a2bd3acffa8b57e626cc993cde5..610e9f5ca302892a99bd9dcfcff20e834f70f6ba 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -174,7 +174,11 @@ $dispatcher = patterns('', url_post('^/(?P<id>\d+)$', 'saveSearch'), url_delete('^/(?P<id>\d+)$', 'deleteSearch'), url_post('^/create$', 'createSearch'), - url_get('^/field/(?P<id>[\w_!:]+)$', 'addField') + url_get('^/field/(?P<id>[\w_!:]+)$', 'addField'), + url('^/column/edit/(?P<id>\d+)$', 'editColumn'), + url_post('^(?P<id>\d+)/delete$', 'deleteQueues'), + url_post('^(?P<id>\d+)/disable$', 'disableQueues'), + url_post('^(?P<id>\d+)/enable$', 'undisableQueues') )) )), url('^/tasks/', patterns('ajax.tasks.php:TasksAjaxAPI', @@ -242,7 +246,8 @@ $dispatcher = patterns('', url('^/department$', 'addDepartment'), url('^/team$', 'addTeam'), url('^/role$', 'addRole'), - url('^/staff$', 'addStaff') + url('^/staff$', 'addStaff'), + url('^/queue-column$', 'addQueueColumn') )), url_get('^/role/(?P<id>\d+)/perms', 'getRolePerms') )), diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql index 643fe925ece4fbe6e4d87139f00b0fd058f138d4..1a39a4a34d778bb8627648fe9e5c5697667aeabc 100644 --- a/setup/inc/streams/core/install-mysql.sql +++ b/setup/inc/streams/core/install-mysql.sql @@ -827,7 +827,7 @@ DROP TABLE IF EXISTS `%TABLE_PREFIX%queue`; CREATE TABLE `%TABLE_PREFIX%queue` ( `id` int(11) unsigned not null auto_increment, `parent_id` int(11) unsigned not null default 0, - `columns_id` int(11) unsigned, + `columns_id` int(11) unsigned default null, `flags` int(11) unsigned not null default 0, `staff_id` int(11) unsigned not null default 0, `sort` int(11) unsigned not null default 0, @@ -844,13 +844,10 @@ CREATE TABLE `%TABLE_PREFIX%queue` ( DROP TABLE IF EXISTS `%TABLE_PREFIX%queue_column`; CREATE TABLE `%TABLE_PREFIX%queue_column` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, - `queue_id` int(10) unsigned NOT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', - `sort` int(10) unsigned NOT NULL DEFAULT '0', - `heading` varchar(64) NOT NULL DEFAULT '', + `name` varchar(64) NOT NULL DEFAULT '', `primary` varchar(64) NOT NULL DEFAULT '', `secondary` varchar(64) DEFAULT NULL, - `width` int(10) unsigned DEFAULT NULL, `filter` varchar(32) DEFAULT NULL, `truncate` varchar(16) DEFAULT NULL, `annotations` text, @@ -859,6 +856,16 @@ CREATE TABLE `%TABLE_PREFIX%queue_column` ( PRIMARY KEY (`id`) ) DEFAULT CHARSET=utf8; +DROP TABLE IF EXISTS `%TABLE_PREFIX%queue_columns`; +CREATE TABLE `%TABLE_PREFIX%queue_columns` ( + `queue_id` int(11) unsigned NOT NULL, + `column_id` int(11) unsigned NOT NULL, + `sort` int(10) unsigned NOT NULL DEFAULT '1', + `heading` varchar(64) DEFAULT NULL, + `width` int(10) unsigned NOT NULL DEFAULT '100', + PRIMARY KEY (`queue_id`, `column_id`) +) DEFAULT CHARSET=utf8; + DROP TABLE IF EXISTS `%TABLE_PREFIX%translation`; CREATE TABLE `%TABLE_PREFIX%translation` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT,