diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php index 0338545a6d4935489c8cb31613b6154bfc712db8..e999dcd686b8d13dfb27f8f71cdef0a577f8f4b6 100644 --- a/include/ajax.tickets.php +++ b/include/ajax.tickets.php @@ -499,6 +499,46 @@ function refer($tid, $target=null) { $thread = $ticket->getThread(); include STAFFINC_DIR . 'templates/refer.tmpl.php'; } + function editField($tid, $fid) { + global $thisstaff; + + if (!($ticket=Ticket::lookup($tid))) + Http::response(404, __('No such ticket')); + + if (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_EDIT)) + Http::response(403, __('Permission denied')); + elseif (!($field=$ticket->getField($fid))) + Http::response(404, __('No such field')); + + $errors = array(); + $info = array( + ':title' => sprintf(__('Ticket #%s: %s %s'), + $ticket->getNumber(), + __('Update'), + $field->getlabel() + ), + ':action' => sprintf('#tickets/%d/field/%s/edit', + $ticket->getId(), $field->getId()) + ); + + $form = $field->getEditForm($_POST); + if ($_POST && $form->isValid()) { + if ($ticket->updateField($form, $errors)) { + $_SESSION['::sysmsgs']['msg'] = sprintf( + __('%s successfully'), + sprintf( + __('%s updated'), + __($field->getLabel()) + ) + ); + Http::response(201, $field->getClean()); + } + $form->addErrors($errors); + $info['error'] = $errors['err'] ?: __('Unable to update field'); + } + + include STAFFINC_DIR . 'templates/field-edit.tmpl.php'; + } function assign($tid, $target=null) { global $thisstaff; diff --git a/include/class.forms.php b/include/class.forms.php index c834309b4c65ea7b8daa15c4b6265af35d3fea2c..d13a0533ce51f6f57fa6e2d4fc8c76d88f0daf6c 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -324,6 +324,10 @@ class SimpleForm extends Form { parent::__construct($source, $options); $this->setFields($fields); } + + function getId() { + return $this->getFormId(); + } } class CustomForm extends SimpleForm { @@ -1283,11 +1287,66 @@ class FormField { return _H(sprintf('field.%s.%s%s', $subtag, $this->get('id'), $this->get('form_id') ? '' : '*internal*')); } + function getLocal($subtag, $default=false) { $tag = $this->getTranslateTag($subtag); $T = CustomDataTranslation::translate($tag); return $T != $tag ? $T : ($default ?: $this->get($subtag)); } + + function getEditForm($source=null) { + + $fields = array( + 'field' => $this, + 'comments' => new TextareaField(array( + 'id' => 2, + 'label'=> '', + 'required' => false, + 'default' => '', + 'configuration' => array( + 'html' => true, + 'size' => 'small', + 'placeholder' => __('Optional reason for the update'), + ) + )) + ); + + return new SimpleForm($fields, $source); + } + + function getChanges() { + $a = $this->to_database($this->getClean()); + $b = $this->to_database($this->answer ? $this->answer->getValue() : $this->get('default')); + return ($a != $b) ? array($b, $a) : false; + } + + + function save() { + + if (!($changes=$this->getChanges())) + return true; + + if (!($a = $this->answer)) + return false; + + $val = $changes[1]; + if (is_array($val)) { + $a->set('value', $val[0]); + $a->set('value_id', $val[1]); + } else { + $a->set('value', $val); + } + + if (!$a->save(true)) + return false; + + return $this->parent->save(); + } + + + static function init($config) { + return new Static($config); + } } class TextboxField extends FormField { @@ -2508,6 +2567,10 @@ class TimezoneField extends ChoiceField { return $choices; } + function whatChanged($before, $after) { + return FormField::whatChanged($before, $after); + } + function searchable($value) { return null; } @@ -2595,6 +2658,66 @@ FormField::addFieldTypes(/*@trans*/ 'Dynamic Fields', function() { }); +class SLAField extends ChoiceField { + function getWidget($widgetClass=false) { + $widget = parent::getWidget($widgetClass); + if ($widget->value instanceof SLA) + $widget->value = $widget->value->getId(); + return $widget; + } + + function hasIdValue() { + return true; + } + + function getChoices($verbose=false) { + global $cfg; + + $choices = array(); + if (($depts = SLA::getSLAs())) + foreach ($depts as $id => $name) + $choices[$id] = $name; + + return $choices; + } + + function parse($id) { + return $this->to_php(null, $id); + } + + function to_php($value, $id=false) { + if (is_array($id)) { + reset($id); + $id = key($id); + } + return $id; + } + + function to_database($sla) { + return ($sla instanceof SLA) + ? array($sla->getName(), $slas->getId()) + : $sla; + } + + function toString($value) { + return (string) $value; + } + + function searchable($value) { + return null; + } + + function getConfigurationOptions() { + return array( + 'prompt' => new TextboxField(array( + 'id'=>2, 'label'=>__('Prompt'), 'required'=>false, 'default'=>'', + 'hint'=>__('Leading text shown before a value is selected'), + 'configuration'=>array('size'=>40, 'length'=>40), + )), + ); + } +} + class AssigneeField extends ChoiceField { var $_choices = null; var $_criteria = null; @@ -3914,6 +4037,10 @@ class DatetimePickerWidget extends Widget { $config = $this->field->getConfiguration(); $timezone = $this->field->getTimezone(); + + if (!isset($this->value) && ($default=$this->field->get('default'))) + $this->value = $default; + if ($this->value) { if (is_int($this->value)) diff --git a/include/class.list.php b/include/class.list.php index f10963a8f58fa9a07e05950bac92b21a1e3ba1e8..dbda898deedf46e5d65f209c213aa3108187b9be 100644 --- a/include/class.list.php +++ b/include/class.list.php @@ -785,6 +785,9 @@ class DynamicListItem extends VerySimpleModel implements CustomListItem { } function display() { + + return $this->getValue(); + //TODO: Allow for display mode (edit, preview or both) return sprintf('<a class="preview" href="#" data-preview="#list/%d/items/%d/preview">%s</a>', $this->getListId(), diff --git a/include/class.ticket.php b/include/class.ticket.php index a1ed8355d6caa1819e75388c14b4a6b408c8a097..7dfe783f5e140392608f4fac888baeea6768659d 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -484,18 +484,18 @@ implements RestrictedAccess, Threadable, Searchable { function getPriorityId() { global $cfg; - if (($a = $this->getAnswer('priority')) - && ($b = $a->getValue()) - ) { - return $b->getId(); - } + if (($priority = $this->getPriority())) + return $priority->getId(); + return $cfg->getDefaultPriorityId(); } function getPriority() { - if (($a = $this->getAnswer('priority')) && ($b = $a->getValue())) - return $b->getDesc(); - return ''; + + if (($a = $this->getAnswer('priority'))) + return $a->getValue(); + + return null; } function getPhoneNumber() { @@ -926,6 +926,70 @@ implements RestrictedAccess, Threadable, Searchable { return TransferForm::instantiate($source); } + function getField($fid) { + + if (is_numeric($fid)) + return $this->getDymanicFieldById($fid); + + // Special fields + switch ($fid) { + case 'priority': + if (($a = $this->_answers['priority'])) + return $a->getField(); + break; + case 'sla': + return ChoiceField::init(array( + 'id' => $fid, + 'name' => "{$fid}_id", + 'label' => __('SLA Plan'), + 'default' => $this->getSLAId(), + 'choices' => SLA::getSLAs() + )); + break; + case 'topic': + return ChoiceField::init(array( + 'id' => $fid, + 'name' => "{$fid}_id", + 'label' => __('Help Topic'), + 'default' => $this->getTopicId(), + 'choices' => Topic::getHelpTopics() + )); + break; + case 'source': + return ChoiceField::init(array( + 'id' => $fid, + 'name' => 'source', + 'label' => __('Ticket Source'), + 'default' => $this->getSource(), + 'choices' => Ticket::getSources() + )); + break; + case 'duedate': + + $hint = sprintf(__('Setting a %s will override %s'), + __('Due Date'), __('SLA Plan')); + return DateTimeField::init(array( + 'id' => $fid, + 'name' => $fid, + 'default' => Misc::db2gmtime($this->getDueDate()), + 'label' => __('Due Date'), + 'hint' => $hint, + 'configuration' => array( + 'time' => true, + 'future' => true, + ) + )); + } + } + + function getDymanicFieldById($fid) { + foreach (DynamicFormEntry::forTicket($this->getId()) as $form) { + foreach ($form->getFields() as $field) + if ($field->getId() == $fid) + return $field; + } + } + function getDynamicFields($criteria=array()) { $fields = DynamicFormField::objects()->filter(array( @@ -3166,6 +3230,65 @@ implements RestrictedAccess, Threadable, Searchable { return $this->save(); } + + function updateField($form, &$errors) { + global $thisstaff; + + if (!($field = $form->getField('field'))) + return null; + + if (!($changes = $field->getChanges())) + $errors['field'] = sprintf(__('%s is already assigned this value'), + __($field->getLabel())); + else { + if ($field->answer) { + if (!$field->save()) + $errors['field'] = __('Unable to update field'); + $changes['fields'] = array($field->getId() => $changes); + } else { + $val = $field->getClean(); + $fid = $field->get('name'); + $this->{$fid} = $val; + + if ($fid == 'duedate') + $this->isoverdue = 0; + + $changes = array(); + foreach ($this->dirty as $F=>$old) { + switch ($F) { + case 'topic_id': + case 'user_id': + case 'source': + case 'duedate': + case 'sla_id': + $changes[$F] = array($old, $this->{$F}); + } + } + + if (!$this->save()) + $errors['field'] = __('Unable to update field'); + } + } + + if ($errors) + return false; + + // Record the changes + $this->logEvent('edited', $changes); + // Log comments (if any) + + if (($comments = $form->getField('comments')->getClean())) { + $title = sprintf(__('%s updated'), __($field->getLabel())); + $_errors = array(); + $this->postNote( + array('note' => $comments, 'title' => $title), + $_errors, $thisstaff, false); + } + + return true; + } + + /*============== Static functions. Use Ticket::function(params); =============nolint*/ static function getIdByNumber($number, $email=null, $ticket=false) { diff --git a/include/staff/templates/field-edit.tmpl.php b/include/staff/templates/field-edit.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..2b936842148e3ebb96cfd751f6e66b69258f2cc3 --- /dev/null +++ b/include/staff/templates/field-edit.tmpl.php @@ -0,0 +1,61 @@ +<?php +global $cfg; + +$options = array('template' => 'simple', 'form_id' => $form->getId()); + +?> +<h3 class="drag-handle"><?php echo $info[':title']; ?></h3> +<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> +<div class="clear"></div> +<hr/> +<?php +if ($info['error']) { + echo sprintf('<p id="msg_error">%s</p>', $info['error']); +} elseif ($info['warn']) { + echo sprintf('<p id="msg_warning">%s</p>', $info['warn']); +} elseif ($info['msg']) { + echo sprintf('<p id="msg_notice">%s</p>', $info['msg']); +} elseif ($info['notice']) { + echo sprintf('<p id="msg_info"><i class="icon-info-sign"></i> %s</p>', + $info['notice']); +} + +$action = $info[':action'] ?: ('#'); +?> +<div style="display:block; margin:5px;"> +<form method="post" name="inline_update" id="inline_update" + class="mass-action" + action="<?php echo $action; ?>"> + <table width="100%"> + <?php + if ($info[':extra']) { + ?> + <tbody> + <tr><td colspan="2"><strong><?php echo $info[':extra']; + ?></strong></td> </tr> + </tbody> + <?php + } + ?> + <tbody> + <tr><td colspan=2> + <?php + include 'dynamic-form-simple.tmpl.php'; + ?> + </td> </tr> + </tbody> + </table> + <hr> + <p class="full-width"> + <span class="buttons pull-left"> + <input type="button" name="cancel" class="close" + value="<?php echo __('Cancel'); ?>"> + </span> + <span class="buttons pull-right"> + <input type="submit" value="<?php + echo $verb ?: __('Update'); ?>"> + </span> + </p> +</form> +</div> +<div class="clear"></div> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index 258dd9985fd2ed08ae0a1db1f3534ae95a631324..e7440d13a1cdc1103c8df31a68ad731ddb716ea6 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -266,11 +266,33 @@ if($ticket->isOverdue()) </tr> <tr> <th><?php echo __('Priority');?>:</th> - <td><?php echo $ticket->getPriority(); ?></td> + <?php + if ($role->hasPerm(Ticket::PERM_EDIT)) {?> + <td> + <a class="ticket-action" id="inline-update" data-placement="bottom" data-toggle="tooltip" title="<?php echo __('Update'); ?>" + data-redirect="tickets.php?id=<?php echo $ticket->getId(); ?>" + href="#tickets/<?php echo $ticket->getId(); ?>/field/priority/edit"> + <?php echo $ticket->getPriority(); ?> + </a> + </td> + <?php } else { ?> + <td><?php echo $ticket->getPriority(); ?></td> + <?php } ?> </tr> <tr> <th><?php echo __('Department');?>:</th> + <?php + if ($role->hasPerm(Ticket::PERM_TRANSFER)) {?> + <td> + <a class="ticket-action" id="ticket-transfer" data-placement="bottom" data-toggle="tooltip" title="<?php echo __('Transfer'); ?>" + data-redirect="tickets.php?id=<?php echo $ticket->getId(); ?>" + href="#tickets/<?php echo $ticket->getId(); ?>/transfer"><?php echo Format::htmlchars($ticket->getDeptName()); ?> + </a> + </td> + <?php + }else {?> <td><?php echo Format::htmlchars($ticket->getDeptName()); ?></td> + <?php } ?> </tr> <tr> <th><?php echo __('Create Date');?>:</th> @@ -388,14 +410,26 @@ if($ticket->isOverdue()) </tr> <?php } # end if (user->org) ?> <tr> - <th><?php echo __('Source'); ?>:</th> - <td><?php - echo Format::htmlchars($ticket->getSource()); + <th><?php echo __('Source'); ?>:</th> + <td> + <?php + if ($role->hasPerm(Ticket::PERM_EDIT)) {?> + <a class="ticket-action" id="inline-update" data-placement="bottom" data-toggle="tooltip" title="<?php echo __('Update'); ?>" + data-redirect="tickets.php?id=<?php echo $ticket->getId(); ?>" - if (!strcasecmp($ticket->getSource(), 'Web') && $ticket->getIP()) - echo ' <span class="faded">('.Format::htmlchars($ticket->getIP()).')</span>'; + href="#tickets/<?php echo $ticket->getId(); ?>/field/source/edit"> + <?php echo Format::htmlchars($ticket->getSource()); ?> - </td> + </a> + <?php + } else { + echo Format::htmlchars($ticket->getSource()); + } + + if (!strcasecmp($ticket->getSource(), 'Web') && $ticket->getIP()) + echo ' <span class="faded">('.Format::htmlchars($ticket->getIP()).')</span>'; + ?> + </td> </tr> </table> </td> @@ -410,14 +444,32 @@ if($ticket->isOverdue()) if($ticket->isOpen()) { ?> <tr> <th width="100"><?php echo __('Assigned To');?>:</th> + <?php + if ($role->hasPerm(Ticket::PERM_ASSIGN)) {?> <td> - <?php - if($ticket->isAssigned()) - echo Format::htmlchars(implode('/', $ticket->getAssignees())); - else - echo '<span class="faded">— '.__('Unassigned').' —</span>'; - ?> + <a class="ticket-action" id="ticket-assign" + data-redirect="tickets.php?id=<?php echo $ticket->getId(); ?>" + href="#tickets/<?php echo $ticket->getId(); ?>/assign"> + <?php + if($ticket->isAssigned()) + echo Format::htmlchars(implode('/', $ticket->getAssignees())); + else + echo '<span class="faded">— '.__('Unassigned').' —</span>'; + ?> + </a> </td> + <?php + } else { ?> + <td> + <?php + if($ticket->isAssigned()) + echo Format::htmlchars(implode('/', $ticket->getAssignees())); + else + echo '<span class="faded">— '.__('Unassigned').' —</span>'; + ?> + </td> + <?php + } ?> </tr> <?php } else { ?> @@ -436,13 +488,38 @@ if($ticket->isOverdue()) } ?> <tr> <th><?php echo __('SLA Plan');?>:</th> - <td><?php echo $sla?Format::htmlchars($sla->getName()):'<span class="faded">— '.__('None').' —</span>'; ?></td> + <td> + <?php + if ($role->hasPerm(Ticket::PERM_EDIT)) {?> + <a class="ticket-action" id="inline-update" data-placement="bottom" data-toggle="tooltip" title="<?php echo __('Update'); ?>" + data-redirect="tickets.php?id=<?php echo $ticket->getId(); ?>" + + href="#tickets/<?php echo $ticket->getId(); ?>/field/sla/edit"> + <?php echo $sla?Format::htmlchars($sla->getName()):'<span class="faded">— '.__('None').' —</span>'; ?> + </a> + <?php } else { ?> + <?php echo $sla?Format::htmlchars($sla->getName()):'<span class="faded">— '.__('None').' —</span>'; ?> + <?php } ?> + </td> </tr> <?php if($ticket->isOpen()){ ?> <tr> <th><?php echo __('Due Date');?>:</th> - <td><?php echo Format::datetime($ticket->getEstDueDate()); ?></td> + <?php + if ($role->hasPerm(Ticket::PERM_EDIT)) {?> + <td> + <a class="ticket-action" id="inline-update" data-placement="bottom" data-toggle="tooltip" title="<?php echo __('Update'); ?>" + data-redirect="tickets.php?id=<?php echo $ticket->getId(); ?>" + + href="#tickets/<?php echo $ticket->getId(); + ?>/field/duedate/edit"> + <?php echo Format::datetime($ticket->getEstDueDate()); ?> + </a> + <td> + <?php } else { ?> + <td><?php echo Format::datetime($ticket->getEstDueDate()); ?></td> + <?php } ?> </tr> <?php }else { ?> @@ -459,7 +536,19 @@ if($ticket->isOverdue()) <table cellspacing="0" cellpadding="4" width="100%" border="0"> <tr> <th width="100"><?php echo __('Help Topic');?>:</th> - <td><?php echo Format::htmlchars($ticket->getHelpTopic()); ?></td> + <?php + if ($role->hasPerm(Ticket::PERM_EDIT)) {?> + <td> + <a class="ticket-action" id="inline-update" data-placement="bottom" + data-toggle="tooltip" title="<?php echo __('Update'); ?>" + data-redirect="tickets.php?id=<?php echo $ticket->getId(); ?>" + href="#tickets/<?php echo $ticket->getId(); ?>/field/topic/edit"> + <?php echo $ticket->getHelpTopic() ?: __('None'); ?> + </a> + </td> + <?php } else { ?> + <td><?php echo Format::htmlchars($ticket->getHelpTopic()); ?></td> + <?php } ?> </tr> <tr> <th nowrap><?php echo __('Last Message');?>:</th> @@ -486,9 +575,7 @@ foreach (DynamicFormEntry::forTicket($ticket->getId()) as $form) { ))); $displayed = array(); foreach($answers as $a) { - if (!($v = $a->display())) - continue; - $displayed[] = array($a->getLocal('label'), $v); + $displayed[] = array($a->getLocal('label'), $a->display() ?: __('Enter'), $a->getLocal('id')); } if (count($displayed) == 0) continue; @@ -503,12 +590,18 @@ foreach (DynamicFormEntry::forTicket($ticket->getId()) as $form) { list($label, $v) = $stuff; ?> <tr> - <td width="200"><?php -echo Format::htmlchars($label); - ?>:</th> - <td><?php -echo $v; - ?></td> + <td width="200"><?php echo Format::htmlchars($label); ?>:</td> + <td> + <?php if ($role->hasPerm(Ticket::PERM_EDIT)) {?> + <a class="ticket-action" id="inline-update" data-placement="bottom" data-toggle="tooltip" title="<?php echo __('Update'); ?>" + data-redirect="tickets.php?id=<?php echo $ticket->getId(); ?>" + href="#tickets/<?php echo $ticket->getId(); ?>/field/<?php echo $id; ?>/edit"> + <?php echo $v; ?> + </a> + <?php } else { + echo $v; + } ?> + </td> </tr> <?php } ?> </tbody> diff --git a/scp/ajax.php b/scp/ajax.php index b935414c1de4544a0c448b7b448770b17f05db2e..cdb5fa561107fca49e874137ae3adc02a938f412 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -164,6 +164,8 @@ $dispatcher = patterns('', url_get('^lookup', 'lookup'), url('^mass/(?P<action>\w+)(?:/(?P<what>\w+))?', 'massProcess'), url('^(?P<tid>\d+)/transfer$', 'transfer'), + url('^(?P<tid>\d+)/field/(?P<fid>\d+)/edit$', 'editField'), + url('^(?P<tid>\d+)/field/(?P<field>\w+)/edit$', 'editField'), url('^(?P<tid>\d+)/assign(?:/(?P<to>\w+))?$', 'assign'), url('^(?P<tid>\d+)/refer(?:/(?P<to>\w+))?$', 'refer'), url('^(?P<tid>\d+)/referrals$', 'referrals'),