diff --git a/include/class.ticket.php b/include/class.ticket.php index 6518d12eae5722401bb1f96a4932ed00e14473c0..2535f16be9f8dfa81297559ecc4422b43b8e6c12 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -43,6 +43,7 @@ class Ticket { var $lastMsgId; + var $status; var $dept; //Dept obj var $sla; // SLA obj var $staff; //Staff obj @@ -91,6 +92,7 @@ class Ticket { $this->loadDynamicData(); //Reset the sub classes (initiated ondemand)...good for reloads. + $this->status= null; $this->staff = null; $this->client = null; $this->team = null; @@ -118,16 +120,32 @@ class Ticket { return $this->load(); } + function hasState($state) { + return (strcasecmp($this->getState(), $state)==0); + } + function isOpen() { - return (strcasecmp($this->getStatus(),'Open')==0); + return $this->hasState('open'); } function isReopened() { return ($this->getReopenDate()); } + function isResolved() { + return $this->hasState('resolved'); + } + function isClosed() { - return (strcasecmp($this->getStatus(),'Closed')==0); + return $this->hasState('closed'); + } + + function isArchived() { + return $this->hasState('archived'); + } + + function isDeleted() { + return $this->hasState('deleted'); } function isAssigned() { @@ -291,8 +309,24 @@ class Ticket { return $this->ht['closed']; } + function getStatusId() { + return $this->ht['status_id']; + } + function getStatus() { - return $this->ht['status']; + + if (!$this->status && $this->getStatusId()) + $this->status = TicketStatus::lookup($this->getStatusId()); + + return $this->status; + } + + function getState() { + + if (!$this->getStatus()) + return ''; + + return $this->getStatus()->getState(); } function getDeptId() { @@ -785,20 +819,61 @@ class Ticket { //Status helper. function setStatus($status) { + global $thisstaff; - if(strcasecmp($this->getStatus(), $status)==0) - return true; //No changes needed. + if ($status && is_numeric($status)) + $status = TicketStatus::lookup($status); - switch(strtolower($status)) { - case 'open': - return $this->reopen(); - break; + if (!$status || !$status instanceof TicketStatus) + return false; + + if ($this->getStatusId() == $status->getId()) + return true; + + $sql = 'UPDATE '.TICKET_TABLE.' SET updated=NOW() '. + ' ,status_id='.db_input($status->getId()); + + $ecb = null; + switch($status->getState()) { case 'closed': - return $this->close(); + $sql.=', closed=NOW(), duedate=NULL '; + if ($thisstaff) + $sql.=', staff_id='.db_input($thisstaff->getId()); + + $ecb = function($t) { + $t->reload(); + $t->logEvent('closed'); + $t->deleteDrafts(); + }; + break; + case 'open': + if ($this->isClosed()) { + $sql.= ',closed=NULL, reopened=NOW() '; + + $ecb = function ($t) { + $t->logEvent('reopened', 'closed'); + }; + } break; } - return false; + $sql.=' WHERE ticket_id='.db_input($this->getId()); + + if (!db_query($sql) || !db_affected_rows()) + return false; + + // Log status change b4 reload + $note= sprintf('Status changed from %s to %s by %s', + $this->getStatus(), + $status, + $thisstaff ?: 'SYSTEM'); + + $this->logNote('Status Changed', $note, $thisstaff, false); + + // Log events via callback + if ($ecb) $ecb($this); + + return true; } function setState($state, $alerts=false) { @@ -840,42 +915,11 @@ class Ticket { return (db_query($sql) && db_affected_rows()); } - //Close the ticket - function close() { - global $thisstaff; - - $sql='UPDATE '.TICKET_TABLE.' SET closed=NOW(),isoverdue=0, duedate=NULL, updated=NOW(), status='.db_input('closed'); - if($thisstaff) //Give the closing staff credit. - $sql.=', staff_id='.db_input($thisstaff->getId()); - - $sql.=' WHERE ticket_id='.db_input($this->getId()); - - if(!db_query($sql) || !db_affected_rows()) - return false; - - $this->reload(); - $this->logEvent('closed'); - $this->deleteDrafts(); - - return true; - } - //set status to open on a closed ticket. function reopen($isanswered=0) { + global $cfg; - $sql='UPDATE '.TICKET_TABLE.' SET closed=NULL, updated=NOW(), reopened=NOW() ' - .' ,status='.db_input('open') - .' ,isanswered='.db_input($isanswered) - .' WHERE ticket_id='.db_input($this->getId()); - - if (!db_query($sql) || !db_affected_rows()) - return false; - - $this->logEvent('reopened', 'closed'); - $this->ht['status'] = 'open'; - $this->ht['isanswerd'] = $isanswered; - - return true; + return $this->setStatus($cfg->getDefaultTicketStatusId()); } function onNewTicket($message, $autorespond=true, $alertstaff=true) { @@ -1732,9 +1776,10 @@ class Ticket { if(!($response = $this->getThread()->addResponse($vars, $errors))) return null; - //Set status - if checked. - if(isset($vars['reply_ticket_status']) && $vars['reply_ticket_status']) - $this->setStatus($vars['reply_ticket_status']); + // Set status - if checked. + if ($vars['reply_status_id'] + && $vars['reply_status_id'] != $this->getStatusId()) + $this->setStatus($vars['reply_status_id']); if($thisstaff && $this->isOpen() && !$this->getStaffId() && $cfg->autoClaimTickets()) @@ -1742,7 +1787,7 @@ class Ticket { $this->onResponse(); //do house cleaning.. - /* email the user?? - if disabled - the bail out */ + /* email the user?? - if disabled - then bail out */ if(!$alert) return $response; $dept = $this->getDept(); @@ -1861,9 +1906,9 @@ class Ticket { // Get assigned staff just in case the ticket is closed. $assignee = $this->getStaff(); - //Set state: Error on state change not critical! - if(isset($vars['state']) && $vars['state']) { - if($this->setState($vars['state'])) + if ($vars['note_status_id'] + && ($status=TicketStatus::lookup($vars['note_status_id']))) { + if ($this->setStatus($status)) $this->reload(); } @@ -2401,6 +2446,7 @@ class Ticket { // members can always add more forms now // OK...just do it. + $statusId = $vars['statusId']; $deptId = $vars['deptId']; //pre-selected Dept if any. $source = ucfirst($vars['source']); @@ -2431,6 +2477,7 @@ class Ticket { // Intenal mapping magic...see if we need to override anything if (isset($topic)) { $deptId = $deptId ?: $topic->getDeptId(); + $statusId = $statusId ?: $topic->getStatusId(); $priority = $form->getAnswer('priority'); if (!$priority || !$priority->getIdValue()) $form->setAnswer('priority', null, $topic->getPriorityId()); @@ -2466,6 +2513,7 @@ class Ticket { if (!$priority || !$priority->getIdValue()) $form->setAnswer('priority', null, $cfg->getDefaultPriorityId()); $deptId = $deptId ?: $cfg->getDefaultDeptId(); + $statusId = $statusId ?: $cfg->getDefaultTicketStatusId(); $topicId = $vars['topicId'] ?: 0; $ipaddress = $vars['ip'] ?: $_SERVER['REMOTE_ADDR']; @@ -2477,6 +2525,7 @@ class Ticket { .' ,`number`='.db_input($number) .' ,dept_id='.db_input($deptId) .' ,topic_id='.db_input($topicId) + .' ,status_id='.db_input($statusId) .' ,ip_address='.db_input($ipaddress) .' ,source='.db_input($source); diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index a63df79ea83538377ed3c84512c1fddac1077807..e8a78540f68aa5c0b4c9d532eaa6b51913666c4d 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -585,28 +585,32 @@ $tcount+= $ticket->getNumNotes(); } ?> </td> </tr> - <?php - if($ticket->isClosed() || $thisstaff->canCloseTickets()) { ?> <tr> <td width="120"> <label><strong>Ticket Status:</strong></label> </td> <td> + <select name="reply_status_id"> <?php - $statusChecked=isset($info['reply_ticket_status'])?'checked="checked"':''; - if($ticket->isClosed()) { ?> - <label><input type="checkbox" name="reply_ticket_status" id="reply_ticket_status" value="Open" - <?php echo $statusChecked; ?>> Reopen on Reply</label> - <?php - } elseif($thisstaff->canCloseTickets()) { ?> - <label><input type="checkbox" name="reply_ticket_status" id="reply_ticket_status" value="Closed" - <?php echo $statusChecked; ?>> Close on Reply</label> - <?php - } ?> + $statusId = $info['reply_status_id'] ?: $ticket->getStatusId(); + $states = array('open', 'resolved'); + if ($thisstaff->canCloseTickets()) + $states = array_merge($states, + array('closed', 'archived')); + + foreach (TicketStatusList::getAll($states) as $s) { + if (!$s->isEnabled()) continue; + echo sprintf('<option value="%d" %s>%s</option>', + $s->getId(), + ($statusId == $s->getId()) + ? 'selected="selected"' : '', + $s->getName() + ); + } + ?> + </select> </td> </tr> - <?php - } ?> </tbody> </table> <p style="padding-left:165px;"> @@ -677,47 +681,26 @@ $tcount+= $ticket->getNumNotes(); </td> <td> <div class="faded"></div> - <select name="state"> + <select name="note_status_id"> <option value="" selected="selected">— unchanged —</option> <?php - $state = $info['state']; - if($ticket->isClosed()){ - echo sprintf('<option value="open" %s>Reopen Ticket</option>', - ($state=='reopen')?'selected="selelected"':''); - } else { - if($thisstaff->canCloseTickets()) - echo sprintf('<option value="closed" %s>Close Ticket</option>', - ($state=='closed')?'selected="selelected"':''); - - /* Ticket open - states */ - echo '<option value="" disabled="disabled">— Ticket States —</option>'; - - //Answer - state - if($ticket->isAnswered()) - echo sprintf('<option value="unanswered" %s>Mark As Unanswered</option>', - ($state=='unanswered')?'selected="selelected"':''); - else - echo sprintf('<option value="answered" %s>Mark As Answered</option>', - ($state=='answered')?'selected="selelected"':''); - - //overdue - state - // Only department manager can set/clear overdue flag directly. - // Staff with edit perm. can still set overdue date & change SLA. - if($dept && $dept->isManager($thisstaff)) { - if(!$ticket->isOverdue()) - echo sprintf('<option value="overdue" %s>Flag As Overdue</option>', - ($state=='answered')?'selected="selelected"':''); - else - echo sprintf('<option value="notdue" %s>Clear Overdue Flag</option>', - ($state=='notdue')?'selected="selelected"':''); - - if($ticket->isAssigned()) - echo sprintf('<option value="unassigned" %s>Release (Unassign) Ticket</option>', - ($state=='unassigned')?'selected="selelected"':''); - } - }?> + $statusId = $info['note_status_id'] ?: $ticket->getStatusId(); + $states = array('open', 'resolved'); + if ($thisstaff->canCloseTickets()) + $states = array_merge($states, + array('closed', 'archived')); + foreach (TicketStatusList::getAll($states) as $s) { + if (!$s->isEnabled()) continue; + echo sprintf('<option value="%d" %s>%s</option>', + $s->getId(), + ($statusId == $s->getId()) + ? 'selected="selected"' : '', + $s->getName() + ); + } + ?> </select> - <span class='error'>* <?php echo $errors['state']; ?></span> + <span class='error'>* <?php echo $errors['note_status_id']; ?></span> </td> </tr> </div> @@ -927,12 +910,41 @@ $tcount+= $ticket->getNumNotes(); <h3><?php echo sprintf('%s Ticket #%s', ($ticket->isClosed()?'Reopen':'Close'), $ticket->getNumber()); ?></h3> <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> - <?php echo sprintf('Are you sure you want to <b>%s</b> this ticket?', $ticket->isClosed()?'REOPEN':'CLOSE'); ?> + <?php + echo sprintf('Are you sure you want to <b>%s</b> this ticket?', + $ticket->isClosed()?'REOPEN':'CLOSE'); + $action = $ticket->isClosed() ? 'reopen': 'close'; + ?> + <br><br> <form action="tickets.php?id=<?php echo $ticket->getId(); ?>" method="post" id="status-form" name="status-form"> <?php csrf_token(); ?> <input type="hidden" name="id" value="<?php echo $ticket->getId(); ?>"> <input type="hidden" name="a" value="process"> - <input type="hidden" name="do" value="<?php echo $ticket->isClosed()?'reopen':'close'; ?>"> + <input type="hidden" name="do" value="<?php echo $action; ?>"> + <fieldset> + <div><strong>Ticket Status </strong> + <select name="status_id"> + <?php + $statusId = $info['status_id'] ?: 0; + if (!$ticket->isOpen()) + $states = array('open'); + else + $states = array('resolved', 'closed'); + + foreach (TicketStatusList::getAll($states) as $s) { + if (!$s->isEnabled()) continue; + echo sprintf('<option value="%d" %s>%s</option>', + $s->getId(), + ($statusId == $s->getId()) + ? 'selected="selected"' : '', + $s->getName() + ); + } + ?> + </select> + <span class='error'>* <?php echo $errors['status_id']; ?></span> + </div> + </fieldset> <fieldset> <div style="margin-bottom:0.5em"> <em>Reasons for status change (internal note). Optional but highly recommended.</em> diff --git a/include/upgrader/streams/core/8f99b8bf-00000000.patch.sql b/include/upgrader/streams/core/8f99b8bf-00000000.patch.sql index 337a974067b8f3c4cfa5911c0f820ca341698f97..be64fdfbb82cba0f3ecebfed6131d63e0964dd6a 100644 --- a/include/upgrader/streams/core/8f99b8bf-00000000.patch.sql +++ b/include/upgrader/streams/core/8f99b8bf-00000000.patch.sql @@ -21,7 +21,8 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%ticket_status` ( `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `name` (`name`) + UNIQUE KEY `name` (`name`), + KEY `state` ( `state` ) ) DEFAULT CHARSET=utf8; ALTER TABLE `%TABLE_PREFIX%help_topic` @@ -30,6 +31,10 @@ ALTER TABLE `%TABLE_PREFIX%help_topic` ALTER TABLE `%TABLE_PREFIX%filter` ADD `status_id` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `email_id`; +ALTER TABLE `%TABLE_PREFIX%ticket` + ADD `status_id` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `user_email_id`, + ADD INDEX (`status_id`); + UPDATE `%TABLE_PREFIX%config` SET `value` = 'cbf8c933d6d2eaaa971042eb2efce247' WHERE `key` = 'schema_signature' AND `namespace` = 'core'; diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql index f3b41bf656b2931fec8a3f9a4e1fa651b2238880..1e83cb45482cd9a4b92dd7c6f3f498a675e47e2e 100644 --- a/setup/inc/streams/core/install-mysql.sql +++ b/setup/inc/streams/core/install-mysql.sql @@ -573,12 +573,14 @@ CREATE TABLE `%TABLE_PREFIX%ticket` ( `number` varchar(20), `user_id` int(11) unsigned NOT NULL default '0', `user_email_id` int(11) unsigned NOT NULL default '0', + `status_id` int(10) unsigned NOT NULL default '0', `dept_id` int(10) unsigned NOT NULL default '0', `sla_id` int(10) unsigned NOT NULL default '0', `topic_id` int(10) unsigned NOT NULL default '0', `staff_id` int(10) unsigned NOT NULL default '0', `team_id` int(10) unsigned NOT NULL default '0', `email_id` int(11) unsigned NOT NULL default '0', + `flags` int(10) unsigned NOT NULL default '0', `ip_address` varchar(64) NOT NULL default '', `status` enum('open','closed') NOT NULL default 'open', `source` enum('Web','Email','Phone','API','Other') NOT NULL default 'Other', @@ -595,8 +597,9 @@ CREATE TABLE `%TABLE_PREFIX%ticket` ( KEY `user_id` (`user_id`), KEY `dept_id` (`dept_id`), KEY `staff_id` (`staff_id`), - KEY `team_id` (`staff_id`), KEY `status` (`status`), + KEY `team_id` (`team_id`), + KEY `status_id` (`status_id`), KEY `created` (`created`), KEY `closed` (`closed`), KEY `duedate` (`duedate`), @@ -667,7 +670,8 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%ticket_status` ( `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `name` (`name`) + UNIQUE KEY `name` (`name`), + KEY `state` (`state`) ) DEFAULT CHARSET=utf8;