Server IP : 184.154.167.98 / Your IP : 3.145.166.178 Web Server : Apache System : Linux pink.dnsnetservice.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 User : puertode ( 1767) PHP Version : 7.2.34 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /home/puertode/www/mesa/include/ |
Upload File : |
<?php /********************************************************************* ajax.tickets.php AJAX interface for tickets Peter Rotich <peter@osticket.com> Copyright (c) 2006-2013 osTicket http://www.osticket.com Released under the GNU General Public License WITHOUT ANY WARRANTY. See LICENSE.TXT for details. vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ if(!defined('INCLUDE_DIR')) die('403'); include_once(INCLUDE_DIR.'class.ticket.php'); require_once(INCLUDE_DIR.'class.ajax.php'); require_once(INCLUDE_DIR.'class.note.php'); include_once INCLUDE_DIR . 'class.thread_actions.php'; class TicketsAjaxAPI extends AjaxController { function lookup() { global $thisstaff; $limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25; $tickets=array(); // Bail out of query is empty if (!$_REQUEST['q']) return $this->json_encode($tickets); $visibility = $thisstaff->getTicketsVisibility(); $hits = Ticket::objects() ->filter($visibility) ->values('user__default_email__address', 'cdata__subject', 'user__name', 'ticket_id', 'thread__id', 'flags') ->annotate(array( 'number' => new SqlCode('null'), 'tickets' => SqlAggregate::COUNT('ticket_id', true), 'tasks' => SqlAggregate::COUNT('tasks__id', true), 'collaborators' => SqlAggregate::COUNT('thread__collaborators__id', true), 'entries' => SqlAggregate::COUNT('thread__entries__id', true), )) ->order_by(SqlAggregate::SUM(new SqlCode('Z1.relevance')), QuerySet::DESC) ->distinct('user__default_email__address') ->limit($limit); $q = $_REQUEST['q']; if (strlen(Format::searchable($q)) < 3) return $this->encode(array()); global $ost; $hits = $ost->searcher->find($q, $hits, false); $T = array(); if (preg_match('/\d{2,}[^*]/', $q, $T)) { $hits = $this->lookupByNumber($limit, $visibility, $hits); } elseif (preg_match('/(.*@.{2,})|(.{2,}@.*)/', $q, $T)) { $hits = $this->lookupByEmail($limit, $visibility, $hits); } elseif (!count($hits) && preg_match('`\w$`u', $q)) { // Do wild-card fulltext search $_REQUEST['q'] = $q.'*'; return $this->lookup(); } // TODO: Why not aggregate based on relationship? foreach ($hits as $T) { $email = $T['user__default_email__address']; $count = $T['tickets']; if ($T['number']) { $tickets[$T['number']] = array('id'=>$T['number'], 'value'=>$T['number'], 'ticket_id'=>$T['ticket_id'], 'info'=>"{$T['number']} — {$email}", 'subject'=>$T['cdata__subject'], 'user'=>$T['user__name'], 'tasks'=>$T['tasks'], 'thread_id'=>$T['thread__id'], 'collaborators'=>$T['collaborators'], 'entries'=>$T['entries'], 'mergeType'=>Ticket::getMergeTypeByFlag($T['flags']), 'children'=>count(Ticket::getChildTickets($T['ticket_id'])) > 0 ? true : false, 'matches'=>$_REQUEST['q']); } else { $tickets[$email] = array('email'=>$email, 'value'=>$email, 'info'=>"$email ($count)", 'matches'=>$_REQUEST['q']); } } $tickets = array_values($tickets); return $this->json_encode($tickets); } function lookupByNumber($limit=false, $visibility=false, $matches=false) { global $thisstaff; if (!$limit) $limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25; $tickets=array(); // Bail out of query is empty if (!$_REQUEST['q']) return $this->json_encode($tickets); $q = trim($_REQUEST['q']); if (!$visibility) $visibility = $thisstaff->getTicketsVisibility(); $hits = Ticket::objects() ->values('user__default_email__address', 'cdata__subject', 'user__name', 'ticket_id', 'thread__id', 'flags', 'number') ->annotate(array( 'tickets' => new SqlCode('1'), 'tasks' => SqlAggregate::COUNT('tasks__id', true), 'collaborators' => SqlAggregate::COUNT('thread__collaborators__id', true), 'entries' => SqlAggregate::COUNT('thread__entries__id', true), )) ->filter($visibility) ->filter(array('number__startswith' => $q)) ->order_by('number') ->limit($limit); if ($matches && is_a($matches, 'QuerySet')) return $hits->union($matches); foreach ($hits as $T) { $email = $T['user__default_email__address']; $tickets[$T['number']] = array('id'=>$T['number'], 'value'=>$T['number'], 'ticket_id'=>$T['ticket_id'], 'info'=>"{$T['number']} — {$email}", 'subject'=>$T['cdata__subject'], 'user'=>$T['user__name'], 'tasks'=>$T['tasks'], 'thread_id'=>$T['thread__id'], 'collaborators'=>$T['collaborators'], 'entries'=>$T['entries'], 'mergeType'=>Ticket::getMergeTypeByFlag($T['flags']), 'children'=>count(Ticket::getChildTickets($T['ticket_id'])) > 0 ? true : false, 'matches'=>$_REQUEST['q']); } $tickets = array_values($tickets); return $this->json_encode($tickets); } function lookupByEmail($limit=false, $visibility=false, $matches=false) { global $thisstaff; if (!$limit) $limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25; $tickets=array(); // Bail out of query is empty if (!$_REQUEST['q']) return $this->json_encode($tickets); $q = trim($_REQUEST['q']); if (!$visibility) $visibility = $thisstaff->getTicketsVisibility(); $hits = Ticket::objects() ->values('user__default_email__address', 'cdata__subject', 'user__name', 'ticket_id', 'thread__id', 'flags') ->annotate(array( 'number' => new SqlCode('null'), 'tickets' => SqlAggregate::COUNT('ticket_id', true), 'tasks' => SqlAggregate::COUNT('tasks__id', true), 'collaborators' => SqlAggregate::COUNT('thread__collaborators__id', true), 'entries' => SqlAggregate::COUNT('thread__entries__id', true), )) ->filter($visibility) ->filter(array('user__default_email__address__contains' => $q)) ->order_by('user__default_email__address') ->limit($limit); if ($matches && is_a($matches, 'QuerySet')) return $hits->union($matches); foreach ($hits as $T) { $count = $T['tickets']; $email = $T['user__default_email__address']; $tickets[$email] = array('email'=>$email, 'value'=>$email, 'info'=>"$email ($count)", 'matches'=>$_REQUEST['q']); } $tickets = array_values($tickets); return $this->json_encode($tickets); } function acquireLock($tid) { global $cfg, $thisstaff; if(!$cfg || !$cfg->getLockTime() || $cfg->getTicketLockMode() == Lock::MODE_DISABLED) Http::response(418, $this->encode(array('id'=>0, 'retry'=>false))); if(!$tid || !is_numeric($tid) || !$thisstaff) return 0; if (!($ticket = Ticket::lookup($tid)) || !$ticket->checkStaffPerm($thisstaff)) return $this->encode(array('id'=>0, 'retry'=>false, 'msg'=>__('Lock denied!'))); //is the ticket already locked? if ($ticket->isLocked() && ($lock=$ticket->getLock()) && !$lock->isExpired()) { /*Note: Ticket->acquireLock does the same logic...but we need it here since we need to know who owns the lock up front*/ //Ticket is locked by someone else.?? if ($lock->getStaffId() != $thisstaff->getId()) return $this->json_encode(array('id'=>0, 'retry'=>false, 'msg' => sprintf(__('Currently locked by %s'), $lock->getStaff()->getAvatarAndName()) )); //Ticket already locked by staff...try renewing it. $lock->renew(); //New clock baby! } elseif(!($lock=$ticket->acquireLock($thisstaff->getId(),$cfg->getLockTime()))) { //unable to obtain the lock..for some really weired reason! //Client should watch for possible loop on retries. Max attempts? return $this->json_encode(array('id'=>0, 'retry'=>true)); } return $this->json_encode(array( 'id'=>$lock->getId(), 'time'=>$lock->getTime(), 'code' => $lock->getCode() )); } function renewLock($id, $ticketId) { global $thisstaff; if (!$id || !is_numeric($id) || !$thisstaff) Http::response(403, $this->encode(array('id'=>0, 'retry'=>false))); if (!($lock = Lock::lookup($id))) Http::response(404, $this->encode(array('id'=>0, 'retry'=>'acquire'))); if (!($ticket = Ticket::lookup($ticketId)) || $ticket->lock_id != $lock->lock_id) // Ticket / Lock mismatch Http::response(400, $this->encode(array('id'=>0, 'retry'=>false))); if (!$lock->getStaffId() || $lock->isExpired()) // Said lock doesn't exist or is is expired — fetch a new lock return self::acquireLock($ticket->getId()); if ($lock->getStaffId() != $thisstaff->getId()) // user doesn't own the lock anymore??? sorry...try to next time. Http::response(403, $this->encode(array('id'=>0, 'retry'=>false, 'msg' => sprintf(__('Currently locked by %s'), $lock->getStaff->getAvatarAndName()) ))); //Give up... // Ensure staff still has access if (!$ticket->checkStaffPerm($thisstaff)) Http::response(403, $this->encode(array('id'=>0, 'retry'=>false, 'msg' => sprintf(__('You no longer have access to #%s.'), $ticket->getNumber()) ))); // Renew the lock. // Failure here is not an issue since the lock is not expired yet.. client need to check time! $lock->renew(); return $this->encode(array('id'=>$lock->getId(), 'time'=>$lock->getTime(), 'code' => $lock->getCode())); } function releaseLock($id) { global $thisstaff; if (!$id || !is_numeric($id) || !$thisstaff) Http::response(403, $this->encode(array('id'=>0, 'retry'=>true))); if (!($lock = Lock::lookup($id))) Http::response(404, $this->encode(array('id'=>0, 'retry'=>true))); // You have to own the lock if ($lock->getStaffId() != $thisstaff->getId()) { return 0; } // Can't be expired if ($lock->isExpired()) { return 1; } return $lock->release() ? 1 : 0; } function previewTicket ($tid) { global $thisstaff; if(!$thisstaff || !($ticket=Ticket::lookup($tid)) || !$ticket->checkStaffPerm($thisstaff)) Http::response(404, __('No such ticket')); include STAFFINC_DIR . 'templates/ticket-preview.tmpl.php'; } function viewUser($tid) { global $thisstaff; if(!$thisstaff || !($ticket=Ticket::lookup($tid)) || !$ticket->checkStaffPerm($thisstaff)) Http::response(404, 'No such ticket'); if(!($user = User::lookup($ticket->getOwnerId()))) Http::response(404, 'Unknown user'); $info = array( 'title' => sprintf(__('Ticket #%s: %s'), $ticket->getNumber(), Format::htmlchars($user->getName())) ); ob_start(); include(STAFFINC_DIR . 'templates/user.tmpl.php'); $resp = ob_get_contents(); ob_end_clean(); return $resp; } function updateUser($tid) { global $thisstaff; if(!$thisstaff || !($ticket=Ticket::lookup($tid)) || !$ticket->checkStaffPerm($thisstaff) || !($user = User::lookup($ticket->getOwnerId()))) Http::response(404, 'No such ticket/user'); $errors = array(); if($user->updateInfo($_POST, $errors, true)) Http::response(201, $user->to_json()); $forms = $user->getForms(); $info = array( 'title' => sprintf(__('Ticket #%s: %s'), $ticket->getNumber(), Format::htmlchars($user->getName())) ); ob_start(); include(STAFFINC_DIR . 'templates/user.tmpl.php'); $resp = ob_get_contents(); ob_end_clean(); return $resp; } function changeUserForm($tid) { global $thisstaff; if(!$thisstaff || !($ticket=Ticket::lookup($tid)) || !$ticket->checkStaffPerm($thisstaff)) Http::response(404, 'No such ticket'); $user = User::lookup($ticket->getOwnerId()); $info = array( 'title' => sprintf(__('Change user for ticket #%s'), $ticket->getNumber()) ); return self::_userlookup($user, null, $info); } static function _userlookup($user, $form, $info) { global $thisstaff; ob_start(); include(STAFFINC_DIR . 'templates/user-lookup.tmpl.php'); $resp = ob_get_contents(); ob_end_clean(); return $resp; } function manageForms($ticket_id) { global $thisstaff; if (!$thisstaff) Http::response(403, "Login required"); elseif (!($ticket = Ticket::lookup($ticket_id))) Http::response(404, "No such ticket"); elseif (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_EDIT)) Http::response(403, "Access Denied"); $forms = DynamicFormEntry::forTicket($ticket->getId()); $info = array('action' => '#tickets/'.$ticket->getId().'/forms/manage'); include(STAFFINC_DIR . 'templates/form-manage.tmpl.php'); } function updateForms($ticket_id) { global $thisstaff; if (!$thisstaff) Http::response(403, "Login required"); elseif (!($ticket = Ticket::lookup($ticket_id))) Http::response(404, "No such ticket"); elseif (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_EDIT)) Http::response(403, "Access Denied"); elseif (!isset($_POST['forms'])) Http::response(422, "Send updated forms list"); // Add new forms $forms = DynamicFormEntry::forTicket($ticket_id); foreach ($_POST['forms'] as $sort => $id) { $found = false; foreach ($forms as $e) { if ($e->get('form_id') == $id) { $e->set('sort', $sort); $e->save(); $found = true; break; } } // New form added if (!$found && ($new = DynamicForm::lookup($id))) { $f = $new->instanciate(); $f->set('sort', $sort); $f->setTicketId($ticket_id); $f->save(); } } // Deleted forms foreach ($forms as $idx => $e) { if (!in_array($e->get('form_id'), $_POST['forms'])) $e->delete(); } Http::response(201, 'Successfully managed'); } function mergeTickets($ticket_id) { global $thisstaff; if (!$thisstaff) Http::response(403, "Login required"); elseif (!($ticket = Ticket::lookup($ticket_id))) Http::response(404, "No such ticket"); elseif (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_EDIT)) Http::response(403, "Access Denied"); //retrieve the parent and child tickets $parent = Ticket::objects() ->filter(array('ticket_id'=>$ticket_id)) ->values_flat('ticket_id', 'number', 'ticket_pid', 'sort', 'thread__id', 'user_id', 'cdata__subject', 'user__name', 'flags') ->annotate(array('tasks' => SqlAggregate::COUNT('tasks__id', true), 'collaborators' => SqlAggregate::COUNT('thread__collaborators__id', true), 'entries' => SqlAggregate::COUNT('thread__entries__id', true),)); $tickets = Ticket::getChildTickets($ticket_id); $tickets = $parent->union($tickets); //fix sort of tickets $sql = sprintf('SELECT * FROM (%s) a ORDER BY sort', $tickets->getQuery()); $res = db_query($sql); $tickets = db_assoc_array($res); $info = array('action' => '#tickets/'.$ticket->getId().'/merge'); return self::_updateMerge($ticket, $tickets, $info); } function updateMerge($ticket_id) { global $thisstaff; $info = array(); $errors = array(); if ($_POST['dtids']) { foreach($_POST['dtids'] as $key => $value) { if (is_numeric($key) && $ticket = Ticket::lookup($value)) $ticket->unlink(); } Http::response(201, 'Successfully managed'); } elseif ($_POST['tids']) { if ($parent = Ticket::merge($_POST)) Http::response(201, 'Successfully managed'); else Http::response(404, 'Unable to manage ticket'); } $parentModel = Ticket::objects() ->filter(array('ticket_id'=>$ticket_id)) ->values_flat('ticket_id', 'number', 'ticket_pid', 'sort', 'thread__id', 'user_id', 'cdata__subject', 'user__name', 'flags') ->annotate(array('tasks' => SqlAggregate::COUNT('tasks__id', true), 'collaborators' => SqlAggregate::COUNT('thread__collaborators__id', true), 'entries' => SqlAggregate::COUNT('thread__entries__id', true),)); if ($parent->getMergeType() == 'visual') { $tickets = Ticket::getChildTickets($ticket_id); $tickets = $parentModel->union($tickets); } else $tickets = $parentModel; return self::_updateMerge($parent, $tickets, $info); } static private function _updateMerge($ticket, $tickets, $info) { include(STAFFINC_DIR . 'templates/merge-tickets.tmpl.php'); } function previewMerge($tid) { global $thisstaff; if (!($ticket=Ticket::lookup($tid)) || !$ticket->checkStaffPerm($thisstaff)) Http::response(404, 'No such ticket'); ob_start(); include STAFFINC_DIR . 'templates/merge-preview.tmpl.php'; $resp = ob_get_contents(); ob_end_clean(); return $resp; } function cannedResponse($tid, $cid, $format='text') { global $thisstaff, $cfg; if (!($ticket = Ticket::lookup($tid)) || !$ticket->checkStaffPerm($thisstaff)) Http::response(404, 'Unknown ticket ID'); if ($cid && !is_numeric($cid)) { if (!($response=$ticket->getThread()->getVar($cid))) Http::response(422, 'Unknown ticket variable'); // Ticket thread variables are assumed to be quotes $response = "<br/><blockquote>{$response->asVar()}</blockquote><br/>"; // Return text if html thread is not enabled if (!$cfg->isRichTextEnabled()) $response = Format::html2text($response, 90); else $response = Format::viewableImages($response); // XXX: assuming json format for now. return Format::json_encode(array('response' => $response)); } if (!$cfg->isRichTextEnabled()) $format.='.plain'; $varReplacer = function (&$var) use($ticket) { return $ticket->replaceVars($var, array('recipient' => $ticket->getOwner())); }; include_once(INCLUDE_DIR.'class.canned.php'); if (!$cid || !($canned=Canned::lookup($cid)) || !$canned->isEnabled()) Http::response(404, 'No such premade reply'); return $canned->getFormattedResponse($format, $varReplacer); } function transfer($tid) { global $thisstaff; if (!($ticket=Ticket::lookup($tid))) Http::response(404, __('No such ticket')); if (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_TRANSFER)) Http::response(403, __('Permission denied')); $errors = array(); $info = array( ':title' => sprintf(__('Ticket #%s: %s'), $ticket->getNumber(), __('Transfer')), ':action' => sprintf('#tickets/%d/transfer', $ticket->getId()) ); $form = $ticket->getTransferForm($_POST); if ($_POST && $form->isValid()) { if ($ticket->transfer($form, $errors)) { $_SESSION['::sysmsgs']['msg'] = sprintf( __('%s successfully'), sprintf( __('%s transferred to %s department'), __('Ticket'), $ticket->getDept() ) ); Http::response(201, $ticket->getId()); } $form->addErrors($errors); $info['error'] = $errors['err'] ?: __('Unable to transfer ticket'); } $info['dept_id'] = $info['dept_id'] ?: $ticket->getDeptId(); include STAFFINC_DIR . 'templates/transfer.tmpl.php'; } function referrals($tid) { return $this->refer($tid); } function refer($tid, $target=null) { global $thisstaff; if (!($ticket=Ticket::lookup($tid))) Http::response(404, __('No such ticket')); if (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_ASSIGN) || !($form = $ticket->getReferralForm($_POST, array('target' => $target)))) Http::response(403, __('Permission denied')); $errors = array(); $info = array( ':title' => sprintf(__('Ticket #%s: %s'), $ticket->getNumber(), __('Refer') ), ':action' => sprintf('#tickets/%d/refer%s', $ticket->getId(), ($target ? "/$target": '')), ); if ($_POST) { switch ($_POST['do']) { case 'refer': if ($form->isValid() && $ticket->refer($form, $errors)) { $clean = $form->getClean(); if ($clean['comments']) $ticket->logNote('Referral', $clean['comments'], $thisstaff); $_SESSION['::sysmsgs']['msg'] = sprintf( __('%s successfully'), sprintf( __('%s referred to %s'), sprintf(__('Ticket #%s'), sprintf('<a href="tickets.php?id=%d"><b>%s</b></a>', $ticket->getId(), $ticket->getNumber())) , $form->getReferee()) ); Http::response(201, $ticket->getId()); } $form->addErrors($errors); $info['error'] = $errors['err'] ?: __('Unable to refer ticket'); break; case 'manage': $remove = array(); if (is_array($_POST['referrals'])) { $remove = array(); foreach ($_POST['referrals'] as $k => $v) if ($v[0] == '-') $remove[] = substr($v, 1); if (count($remove)) { $num = $ticket->getThread()->referrals ->filter(array('id__in' => $remove)) ->delete(); if ($num) { $info['msg'] = sprintf( __('%s successfully'), sprintf(__('Removed %d referrals'), $num ) ); } //TODO: log removal } } break; default: $errors['err'] = __('Unknown Action'); } } $thread = $ticket->getThread(); include STAFFINC_DIR . 'templates/refer.tmpl.php'; } function editField($tid, $fid) { global $cfg, $thisstaff; if (!($ticket=Ticket::lookup($tid))) Http::response(404, __('No such ticket')); elseif (!$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)) { $msg = sprintf( __('%s successfully'), sprintf( __('%s updated'), __($field->getLabel()) ) ); switch (true) { case $field instanceof DateTime: case $field instanceof DatetimeField: $clean = Format::datetime((string) $field->getClean()); break; case $field instanceof FileUploadField: $field->save(); $answer = $field->getAnswer(); $clean = $answer->display() ?: '—' . __('Empty') . '—'; break; case $field instanceof DepartmentField: $clean = (string) Dept::lookup($field->getClean()); break; case $field instanceof TextareaField: $clean = (string) $field->getClean(); $clean = Format::striptags($clean) ?: '—' . __('Empty') . '—'; if (strlen($clean) > 200) $clean = Format::truncate(Format::striptags($clean), 200); break; case $field instanceof BooleanField: $clean = $field->toString($field->getClean()); break; default: $clean = $field->getClean(); $clean = is_array($clean) ? implode(',', $clean) : (string) $clean; } // Set basic response data $response = array( 'value' => $clean ?: '—' . __('Empty') . '—', 'id' => $fid, 'msg' => $msg ); // If we require HT to close, the ticket is open, the staff has permission // to close, and we set a HT - ensure we provide all available statuses if ($cfg->requireTopicToClose() && $ticket->isOpen() && $ticket->checkStaffPerm($thisstaff, Ticket::PERM_CLOSE) && ($field instanceof TopicField) && $clean) { $statuses = array(); foreach (TicketStatusList::getStatuses( array('states' => array('closed'))) as $s) { if (!$s->isEnabled()) continue; $statuses[$s->getId()] = $s->getName(); } if (!is_null($statuses)) $response['statuses'] = $statuses; } $clean = is_array($clean) ? $clean[0] : $clean; Http::response(201, $this->json_encode($response)); } $form->addErrors($errors); $info['error'] = $errors['err'] ?: __('Unable to update field'); } include STAFFINC_DIR . 'templates/field-edit.tmpl.php'; } function viewField($tid, $fid) { global $cfg, $thisstaff; if (!($ticket=Ticket::lookup($tid))) Http::response(404, __('No such ticket')); elseif (!($field=$ticket->getField($fid))) Http::response(404, __('No such field')); $errors = array(); $info = array( ':title' => sprintf(__('Ticket #%s: %s %s'), $ticket->getNumber(), __('View'), $field->getLabel() ), ':action' => sprintf('#tickets/%d/field/%s/edit', $ticket->getId(), $field->getId()) ); $form = $field->getEditForm(); include STAFFINC_DIR . 'templates/field-view.tmpl.php'; } function assign($tid, $target=null) { global $thisstaff; if (!($ticket=Ticket::lookup($tid))) Http::response(404, __('No such ticket')); if (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_ASSIGN) || !($form = $ticket->getAssignmentForm($_POST, array('target' => $target)))) Http::response(403, __('Permission denied')); $errors = array(); $info = array( ':title' => sprintf(__('Ticket #%s: %s'), $ticket->getNumber(), sprintf('%s %s', $ticket->isAssigned() ? __('Reassign') : __('Assign'), !strcasecmp($target, 'agents') ? __('to an Agent') : __('to a Team') )), ':action' => sprintf('#tickets/%d/assign%s', $ticket->getId(), ($target ? "/$target": '')), ); if ($ticket->isAssigned()) { if ($ticket->getStaffId() == $thisstaff->getId()) $assigned = __('you'); else $assigned = $ticket->getAssigned(); $info['notice'] = sprintf(__('%s is currently assigned to <b>%s</b>'), __('This ticket'), Format::htmlchars($assigned) ); } if ($_POST && $form->isValid()) { if ($ticket->assign($form, $errors)) { $msg = sprintf( __('%s successfully'), sprintf( __('%s assigned to %s'), __('Ticket'), $form->getAssignee()) ); $assignee = $ticket->isAssigned() ? Format::htmlchars(implode('/', $ticket->getAssignees())) : '<span class="faded">— '.__('Unassigned').' —'; Http::response(201, $this->json_encode(['value' => $assignee, 'id' => 'assign', 'msg' => $msg])); } $form->addErrors($errors); $info['error'] = $errors['err'] ?: __('Unable to assign ticket'); } include STAFFINC_DIR . 'templates/assign.tmpl.php'; } function claim($tid) { global $thisstaff; if (!($ticket=Ticket::lookup($tid))) Http::response(404, __('No such ticket')); // Check for premissions and such if (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_ASSIGN) || !$ticket->isOpen() // Claim only open || $ticket->getStaff() // cannot claim assigned ticket || !($form = $ticket->getClaimForm($_POST))) Http::response(403, __('Permission denied')); $errors = array(); $info = array( ':title' => sprintf(__('Ticket #%s: %s'), $ticket->getNumber(), __('Claim')), ':action' => sprintf('#tickets/%d/claim', $ticket->getId()), ); if ($ticket->isAssigned()) { if ($ticket->getStaffId() == $thisstaff->getId()) $assigned = __('you'); else $assigned = $ticket->getAssigned(); $info['error'] = sprintf(__('%s is currently assigned to <b>%s</b>'), __('This ticket'), $assigned); } else { $info['warn'] = sprintf(__('Are you sure you want to CLAIM %s?'), __('this ticket')); } if ($_POST && $form->isValid()) { if ($ticket->claim($form, $errors)) { $_SESSION['::sysmsgs']['msg'] = sprintf( __('%s successfully'), sprintf( __('%s assigned to %s'), __('Ticket'), __('you')) ); Http::response(201, $ticket->getId()); } $form->addErrors($errors); $info['error'] = $errors['err'] ?: __('Unable to claim ticket'); } $verb = sprintf('%s, %s', __('Yes'), __('Claim')); include STAFFINC_DIR . 'templates/assign.tmpl.php'; } function release($tid) { global $thisstaff; if (!($ticket=Ticket::lookup($tid))) Http::response(404, __('No such ticket')); if (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_RELEASE) && !$thisstaff->isManager()) Http::response(403, __('Permission denied')); $errors = array(); if (!$ticket->isAssigned()) $errors['err'] = __('Ticket is not assigned!'); $info = array(':title' => sprintf(__('Ticket #%s: %s'), $ticket->getNumber(), __('Release Confirmation'))); $form = ReleaseForm::instantiate($_POST); $hasData = ($_POST['sid'] || $_POST['tid']); $staff = $ticket->getStaff(); $team = $ticket->getTeam(); if ($_POST) { if ($hasData && $ticket->release($_POST, $errors)) { $data = array(); if ($staff && !$ticket->getStaff()) $data['staff'] = array($staff->getId(), (string) $staff->getName()->getOriginal()); if ($team && !$ticket->getTeam()) $data['team'] = $team->getId(); $ticket->logEvent('released', $data); $comments = $form->getComments(); if ($comments) { $title = __('Assignment Released'); $_errors = array(); $ticket->postNote( array('note' => $comments, 'title' => $title), $_errors, $thisstaff, false); } $_SESSION['::sysmsgs']['msg'] = __('Ticket assignment released successfully'); Http::response(201, $ticket->getId()); } if (!$hasData) $errors['err'] = __('Please check an assignee to release assignment'); $form->addErrors($errors); $info['error'] = $errors['err'] ?: __('Unable to release ticket assignment'); } if($errors && $errors['err']) $info['error'] = $errors['err'] ?: __('Unable to release ticket'); include STAFFINC_DIR . 'templates/release.tmpl.php'; } function massProcess($action, $w=null) { global $thisstaff, $cfg; $actions = array( 'transfer' => array( 'verbed' => __('transferred'), ), 'assign' => array( 'verbed' => __('assigned'), ), 'refer' => array( 'verbed' => __('referred'), ), 'merge' => array( 'verbed' => __('merged'), ), 'link' => array( 'verbed' => __('linked'), ), 'claim' => array( 'verbed' => __('assigned'), ), 'delete' => array( 'verbed' => __('deleted'), ), 'reopen' => array( 'verbed' => __('reopen'), ), 'close' => array( 'verbed' => __('closed'), ), ); if (!isset($actions[$action])) Http::response(404, __('Unknown action')); $info = $errors = $e = array(); $inc = null; $i = $count = 0; if ($_POST) { if (!$_POST['tids'] || !($count=count($_POST['tids']))) $errors['err'] = sprintf( __('You must select at least %s.'), __('one ticket')); } else { $count = $_REQUEST['count']; } switch ($action) { case 'merge': case 'link': $inc = 'merge-tickets.tmpl.php'; $ticketIds = $_GET ? explode(',', $_GET['tids']) : explode(',', $_POST['tids']); $tickets = array(); $title = strpos($_SERVER['PATH_INFO'], 'link') !== false ? 'link' : 'merge'; $eventName = ($title && $title == 'link') ? 'linked' : 'merged'; $permission = ($title && $title == 'link') ? (Ticket::PERM_LINK) : (Ticket::PERM_MERGE); $hasPermission = array(); $parent = false; $tickets = Ticket::objects() ->filter(array('ticket_id__in'=>$ticketIds)) ->values_flat('ticket_id', 'flags', 'dept_id', 'ticket_pid'); foreach ($tickets as $ticket) { list($ticket_id, $flags, $dept_id, $ticket_pid) = $ticket; $mergeType = Ticket::getMergeTypeByFlag($flags); $isParent = Ticket::isParentStatic($flags); $role = $thisstaff->getRole($dept_id); $hasPermission[] = $role->hasPerm($permission); if (!$ticket) continue; elseif (!$parent && $isParent && $mergeType != 'visual') { $parent = $ticket; $parentMergeType = $mergeType; } if ($mergeType != 'visual' && $title == 'link') $info['error'] = sprintf( __('One or more Tickets selected is part of a merge. Merged Tickets cannot be %s.'), __($eventName) ); if ($parent && ($isParent && $mergeType != 'visual') && $parent[0] != $ticket_id) $info['error'] = sprintf( __('More than one Parent Ticket selected. %1$s cannot be %2$s.'), _N('The selected Ticket', 'The selected Tickets', $count), __($eventName) ); if ($ticket_pid && $mergeType != 'visual' && $title == 'merge') $info['error'] = sprintf( __('One or more Tickets selected is a merged child. %1$s cannot be %2$s.'), _N('The selected Ticket', 'The selected Tickets', $count), __($eventName) ); } //move parent ticket to top of list if (count($ticketIds) > 1) { $ticketIdsSorted = $ticketIds; if ($parent && $parentMergeType != 'visual') { foreach ($ticketIdsSorted as $key => $value) { if ($value == $parent[0]) { unset($ticketIdsSorted[$key]); array_unshift($ticketIdsSorted, $value); array_unshift($ticketIdsSorted, new SqlField('ticket_id')); } } } $expr = call_user_func_array(array('SqlFunction', 'FIELD'), $ticketIdsSorted); } $tickets = Ticket::objects() ->filter(array('ticket_id__in'=>$ticketIds)) ->values_flat('ticket_id', 'number', 'ticket_pid', 'sort', 'thread__id', 'user_id', 'cdata__subject', 'user__name', 'flags') ->annotate(array('tasks' => SqlAggregate::COUNT('tasks__id', true), 'collaborators' => SqlAggregate::COUNT('thread__collaborators__id'), 'entries' => SqlAggregate::COUNT('thread__entries__id'),)) ->order_by($expr ?: 'sort'); $ticket = Ticket::lookup($parent[0] ?: $ticket[0]); // Generic permission check. if (in_array(false, $hasPermission) && !$ticket->getThread()->isReferred()) { $info['error'] = sprintf( __('You do not have permission to %1$s %2$s'), __($title), _N('the selected Ticket', 'the selected Tickets', $count)); $info = array_merge($info, Format::htmlchars($_POST)); } else $info['action'] = sprintf('#tickets/%s/merge', $ticket->getId()); break; case 'claim': $w = 'me'; case 'assign': $inc = 'assign.tmpl.php'; $info[':action'] = "#tickets/mass/assign/$w"; $info[':title'] = sprintf('Assign %s', _N('selected ticket', 'selected tickets', $count)); $form = AssignmentForm::instantiate($_POST); $assignCB = function($t, $f, $e) { return $t->assign($f, $e); }; $assignees = null; switch ($w) { case 'agents': $depts = array(); $tids = $_POST['tids'] ?: array_filter(explode(',', $_REQUEST['tids'])); if ($tids) { $tickets = Ticket::objects() ->distinct('dept_id') ->filter(array('ticket_id__in' => $tids)); $depts = $tickets->values_flat('dept_id'); } //agents in depts $thisstaff can access $members = $thisstaff->getDeptAgents(array('available' => true)); if ($depts) { $all_agent_depts = Dept::objects()->filter( Q::all( array('id__in' => $depts, Q::not(array('flags__hasbit' => Dept::FLAG_ASSIGN_MEMBERS_ONLY)), Q::not(array('flags__hasbit' => Dept::FLAG_ASSIGN_PRIMARY_ONLY)) )))->values_flat('id'); if (!count($all_agent_depts)) { $members->filter(Q::any( array( 'dept_id__in' => $depts, Q::all(array( 'dept_access__dept__id__in' => $depts, Q::not(array('dept_access__dept__flags__hasbit' => Dept::FLAG_ASSIGN_MEMBERS_ONLY, 'dept_access__dept__flags__hasbit' => Dept::FLAG_ASSIGN_PRIMARY_ONLY)) )) ))); } } switch ($cfg->getAgentNameFormat()) { case 'last': case 'lastfirst': case 'legal': $members->order_by('lastname', 'firstname'); break; default: $members->order_by('firstname', 'lastname'); } $prompt = __('Select an Agent'); $assignees = array(); foreach ($members as $member) $assignees['s'.$member->getId()] = $member->getName(); if (!$assignees) $info['warn'] = __('No agents available for assignment'); break; case 'teams': $assignees = array(); $prompt = __('Select a Team'); foreach (Team::getActiveTeams() as $id => $name) $assignees['t'.$id] = $name; if (!$assignees) $info['warn'] = __('No teams available for assignment'); break; case 'me': $info[':action'] = '#tickets/mass/claim'; $info[':title'] = sprintf('Claim %s', _N('selected ticket', 'selected tickets', $count)); $info['warn'] = sprintf( __('Are you sure you want to CLAIM %s?'), _N('selected ticket', 'selected tickets', $count)); $verb = sprintf('%s, %s', __('Yes'), __('Claim')); $id = sprintf('s%s', $thisstaff->getId()); $assignees = array($id => $thisstaff->getName()); $vars = $_POST ?: array('assignee' => array($id)); $form = ClaimForm::instantiate($vars); $assignCB = function($t, $f, $e) { return $t->claim($f, $e); }; break; } if ($assignees != null) $form->setAssignees($assignees); if ($prompt && ($f=$form->getField('assignee'))) $f->configure('prompt', $prompt); if ($_POST && $form->isValid()) { // we need to clear this relation so we can create a fresh // query that will get roles for all depts this agent can access unset($thisstaff->ht['dept_access']); foreach ($_POST['tids'] as $tid) { if (($t=Ticket::lookup($tid)) // Make sure the agent is allowed to // access and assign the task. && $t->checkStaffPerm($thisstaff, Ticket::PERM_ASSIGN) // Do the assignment && $assignCB($t, $form, $e) ) $i++; } if (!$i) { $info['error'] = sprintf( __('Unable to %1$s %2$s'), __('assign'), _N('selected ticket', 'selected tickets', $count)); } } break; case 'transfer': $inc = 'transfer.tmpl.php'; $info[':action'] = '#tickets/mass/transfer'; $info[':title'] = sprintf(__('Transfer %s'), _N('selected ticket', 'selected tickets', $count)); $form = TransferForm::instantiate($_POST); $form->hideDisabled(); if ($_POST && $form->isValid()) { foreach ($_POST['tids'] as $tid) { if (($t=Ticket::lookup($tid)) // Make sure the agent is allowed to // access and transfer the task. && $t->checkStaffPerm($thisstaff, Ticket::PERM_TRANSFER) // Do the transfer && $t->transfer($form, $e) ) $i++; } if (!$i) { $info['error'] = sprintf( __('Unable to %1$s %2$s'), __('transfer'), _N('selected ticket', 'selected tickets', $count)); } } break; case 'delete': $inc = 'delete.tmpl.php'; $info[':action'] = '#tickets/mass/delete'; $info[':title'] = sprintf('Delete %s', _N('selected ticket', 'selected tickets', $count)); $info[':placeholder'] = sprintf(__( 'Optional reason for deleting %s'), _N('selected ticket', 'selected tickets', $count)); $info['warn'] = sprintf(__( 'Are you sure you want to DELETE %s?'), _N('selected ticket', 'selected tickets', $count)); $info[':extra'] = sprintf('<strong>%s</strong>', __('Deleted tickets CANNOT be recovered, including any associated attachments.') ); // Generic permission check. if (!$thisstaff->hasPerm(Ticket::PERM_DELETE, false)) $errors['err'] = sprintf( __('You do not have permission %s'), __('to delete tickets')); if ($_POST && !$errors) { foreach ($_POST['tids'] as $tid) { if (($t=Ticket::lookup($tid)) && $t->checkStaffPerm($thisstaff, Ticket::PERM_DELETE) && $t->delete($_POST['comments'], $e) ) $i++; } if (!$i) { $info['error'] = sprintf( __('Unable to %1$s %2$s'), __('delete'), _N('selected ticket', 'selected tickets', $count)); } } break; default: Http::response(404, __('Unknown action')); } if ($_POST && $i) { // Assume success if ($i==$count) { $msg = sprintf(__('Successfully %1$s %2$s.' /* Tokens are <actioned> <x selected ticket(s)> */ ), $actions[$action]['verbed'], sprintf('%1$d %2$s', $count, _N('selected ticket', 'selected tickets', $count)) ); $_SESSION['::sysmsgs']['msg'] = $msg; } else { $warn = sprintf( __('%1$d of %2$d %3$s %4$s' /* Tokens are <x> of <y> <selected ticket(s)> <actioned> */), $i, $count, _N('selected ticket', 'selected tickets', $count), $actions[$action]['verbed']); $_SESSION['::sysmsgs']['warn'] = $warn; } Http::response(201, 'processed'); } elseif($_POST && !isset($info['error'])) { $info['error'] = $errors['err'] ?: sprintf( __('Unable to %1$s %2$s'), __('process'), _N('selected ticket', 'selected tickets', $count)); } if ($_POST) $info = array_merge($info, Format::htmlchars($_POST)); include STAFFINC_DIR . "templates/$inc"; // Copy checked tickets to the form. echo " <script type=\"text/javascript\"> $(function() { $('form#tickets input[name=\"tids[]\"]:checkbox:checked') .each(function() { $('<input>') .prop('type', 'hidden') .attr('name', 'tids[]') .val($(this).val()) .appendTo('form.mass-action'); }); }); </script>"; } function changeTicketStatus($tid, $status, $id=0) { global $thisstaff; if (!$thisstaff) Http::response(403, 'Access denied'); elseif (!$tid || !($ticket=Ticket::lookup($tid)) || !$ticket->checkStaffPerm($thisstaff)) Http::response(404, 'Unknown ticket #'); $role = $ticket->getRole($thisstaff); $info = array(); $state = null; switch($status) { case 'open': case 'reopen': $state = 'open'; break; case 'close': if (!$role->hasPerm(Ticket::PERM_CLOSE)) Http::response(403, 'Access denied'); $state = 'closed'; // Check if ticket is closeable if (is_string($closeable=$ticket->isCloseable())) $info['warn'] = $closeable; break; case 'delete': if (!$role->hasPerm(Ticket::PERM_DELETE)) Http::response(403, 'Access denied'); $state = 'deleted'; break; default: $state = $ticket->getStatus()->getState(); $info['warn'] = sprintf(__('%s: Unknown or invalid'), __('status')); } $info['status_id'] = $id ?: $ticket->getStatusId(); return self::_changeTicketStatus($ticket, $state, $info); } function setTicketStatus($tid) { global $thisstaff, $ost; if (!$thisstaff) Http::response(403, 'Access denied'); elseif (!$tid || !($ticket=Ticket::lookup($tid)) || !$ticket->checkStaffPerm($thisstaff)) Http::response(404, 'Unknown ticket #'); $errors = $info = array(); if (!$_POST['status_id'] || !($status= TicketStatus::lookup($_POST['status_id']))) $errors['status_id'] = sprintf('%s %s', __('Unknown or invalid'), __('status')); elseif ($status->getId() == $ticket->getStatusId()) $errors['err'] = sprintf(__('Ticket already set to %s status'), __($status->getName())); elseif (($role = $ticket->getRole($thisstaff))) { // Make sure the agent has permission to set the status switch(mb_strtolower($status->getState())) { case 'open': if (!$role->hasPerm(Ticket::PERM_CLOSE) && !$role->hasPerm(Ticket::PERM_CREATE)) $errors['err'] = sprintf(__('You do not have permission %s'), __('to reopen tickets')); break; case 'closed': if (!$role->hasPerm(Ticket::PERM_CLOSE)) $errors['err'] = sprintf(__('You do not have permission %s'), __('to resolve/close tickets')); break; case 'deleted': if (!$role->hasPerm(Ticket::PERM_DELETE)) $errors['err'] = sprintf(__('You do not have permission %s'), __('to archive/delete tickets')); break; default: $errors['err'] = sprintf('%s %s', __('Unknown or invalid'), __('status')); } } else { $errors['err'] = __('Access denied'); } $state = strtolower($status->getState()); if (!$errors && $ticket->setStatus($status, $_REQUEST['comments'], $errors)) { $failures = array(); // Set children statuses (if applicable) if ($_REQUEST['children']) { $children = $ticket->getChildren(); foreach ($children as $cid) { $child = Ticket::lookup($cid[0]); if (!$child->setStatus($status, '', $errors)) $failures[$cid[0]] = $child->getNumber(); } } if (!$failures) { if ($state == 'deleted') { $msg = sprintf('%s %s', sprintf(__('Ticket #%s'), $ticket->getNumber()), __('deleted sucessfully') ); } elseif ($state != 'open') { $msg = sprintf(__('%s status changed to %s'), sprintf(__('Ticket #%s'), $ticket->getNumber()), $status->getName()); } else { $msg = sprintf( __('%s status changed to %s'), __('Ticket'), $status->getName()); } } else { $tickets = array(); foreach ($failures as $id=>$num) { $tickets[] = sprintf('<a href="tickets.php?id=%d"><b>#%s</b></a>', $id, $num); } $info['warn'] = sprintf(__('Error updating ticket status for %s'), ($tickets) ? implode(', ', $tickets) : __('child tickets') ); } $_SESSION['::sysmsgs']['msg'] = $msg; Http::response(201, 'Successfully processed'); } elseif (!$errors['err']) { $errors['err'] = __('Error updating ticket status'); } $state = $state ?: $ticket->getStatus()->getState(); $info['status_id'] = $status ? $status->getId() : $ticket->getStatusId(); return self::_changeTicketStatus($ticket, $state, $info, $errors); } function changeSelectedTicketsStatus($status, $id=0) { global $thisstaff, $cfg; if (!$thisstaff) Http::response(403, 'Access denied'); $state = null; $info = array(); switch($status) { case 'open': case 'reopen': $state = 'open'; break; case 'close': if (!$thisstaff->hasPerm(Ticket::PERM_CLOSE, false)) Http::response(403, 'Access denied'); $state = 'closed'; break; case 'delete': if (!$thisstaff->hasPerm(Ticket::PERM_DELETE, false)) Http::response(403, 'Access denied'); $state = 'deleted'; break; default: $info['warn'] = sprintf('%s %s', __('Unknown or invalid'), __('status')); } $info['status_id'] = $id; return self::_changeSelectedTicketsStatus($state, $info); } function setSelectedTicketsStatus($state) { global $thisstaff, $ost; $errors = $info = array(); if (!$thisstaff || !$thisstaff->canManageTickets()) $errors['err'] = sprintf('%s %s', sprintf(__('You do not have permission %s'), __('to mass manage tickets')), __('Contact admin for such access')); elseif (!$_REQUEST['tids'] || !count($_REQUEST['tids'])) $errors['err']=sprintf(__('You must select at least %s.'), __('one ticket')); elseif (!($status= TicketStatus::lookup($_REQUEST['status_id']))) $errors['status_id'] = sprintf('%s %s', __('Unknown or invalid'), __('status')); elseif (!$errors) { // Make sure the agent has permission to set the status switch(mb_strtolower($status->getState())) { case 'open': if (!$thisstaff->hasPerm(Ticket::PERM_CLOSE, false) && !$thisstaff->hasPerm(Ticket::PERM_CREATE, false)) $errors['err'] = sprintf(__('You do not have permission %s'), __('to reopen tickets')); break; case 'closed': if (!$thisstaff->hasPerm(Ticket::PERM_CLOSE, false)) $errors['err'] = sprintf(__('You do not have permission %s'), __('to resolve/close tickets')); break; case 'deleted': if (!$thisstaff->hasPerm(Ticket::PERM_DELETE, false)) $errors['err'] = sprintf(__('You do not have permission %s'), __('to archive/delete tickets')); break; default: $errors['err'] = sprintf('%s %s', __('Unknown or invalid'), __('status')); } } $count = count($_REQUEST['tids']); if (!$errors) { $i = 0; $comments = $_REQUEST['comments']; foreach ($_REQUEST['tids'] as $tid) { if (($ticket=Ticket::lookup($tid)) && $ticket->getStatusId() != $status->getId() && $ticket->checkStaffPerm($thisstaff) && $ticket->setStatus($status, $comments, $errors)) $i++; } if (!$i) { $errors['err'] = $errors['err'] ?: sprintf(__('Unable to change status for %s'), _N('selected ticket', 'selected tickets', $count)); } else { // Assume success if ($i==$count) { if (!strcasecmp($status->getState(), 'deleted')) { $msg = sprintf(__( 'Successfully deleted %s.'), _N('selected ticket', 'selected tickets', $count)); } else { $msg = sprintf( __( /* 1$ will be 'selected ticket(s)', 2$ is the new status */ 'Successfully changed status of %1$s to %2$s'), _N('selected ticket', 'selected tickets', $count), $status->getName()); } $_SESSION['::sysmsgs']['msg'] = $msg; } else { if (!strcasecmp($status->getState(), 'deleted')) { $warn = sprintf(__('Successfully deleted %s.'), sprintf(__('%1$d of %2$d selected tickets'), $i, $count) ); } else { $warn = sprintf( __('%1$d of %2$d %3$s status changed to %4$s'),$i, $count, _N('selected ticket', 'selected tickets', $count), $status->getName()); } $_SESSION['::sysmsgs']['warn'] = $warn; } Http::response(201, 'Successfully processed'); } } return self::_changeSelectedTicketsStatus($state, $info, $errors); } function markAs($tid, $action='') { global $thisstaff; // Standard validation if (!($ticket=Ticket::lookup($tid))) Http::response(404, __('No such ticket')); if (!$ticket->checkStaffPerm($thisstaff, Ticket::PERM_MARKANSWERED) && !$thisstaff->isManager($ticket->getDept())) Http::response(403, __('Permission denied')); $errors = array(); $info = array(':title' => __('Please Confirm')); // Instantiate form for comment field $form = MarkAsForm::instantiate($_POST); // Mark as answered or unanswered if ($_POST) { switch($action) { case 'answered': if($ticket->isAnswered()) $errors['err'] = __('Ticket is already marked as answered'); elseif (!$ticket->markAnswered()) $errors['err'] = __('Cannot mark ticket as answered'); break; case 'unanswered': if(!$ticket->isAnswered()) $errors['err'] = __('Ticket is already marked as unanswered'); elseif (!$ticket->markUnAnswered()) $errors['err'] - __('Cannot mark ticket as unanswered'); break; default: Http::response(404, __('Unknown action')); } // Retrun errors to form (if any) if($errors) { $info['error'] = $errors['err'] ?: sprintf(__('Unable to mark ticket as %s'), $action); $form->addErrors($errors); } else { // Add comment (if provided) $comments = $form->getComments(); if ($comments) { $title = __(sprintf('Ticket Marked %s', ucfirst($action))); $_errors = array(); $ticket->postNote( array('note' => $comments, 'title' => $title), $_errors, $thisstaff, false); } // Add success messages and log activity $_SESSION['::sysmsgs']['msg'] = sprintf(__('Ticket marked as %s successfully'), $action); $msg = sprintf(__('Ticket flagged as %s by %s'), $action, $thisstaff->getName()); $ticket->logActivity(sprintf(__('Ticket Marked %s'), ucfirst($action)), $msg); Http::response(201, $ticket->getId()); } } include STAFFINC_DIR . 'templates/mark-as.tmpl.php'; } function triggerThreadAction($ticket_id, $thread_id, $action) { $thread = ThreadEntry::lookup($thread_id); if (!$thread) Http::response(404, 'No such ticket thread entry'); if ($thread->getThread()->getObjectId() != $ticket_id) Http::response(404, 'No such ticket thread entry'); $valid = false; foreach ($thread->getActions() as $group=>$list) { foreach ($list as $name=>$A) { if ($A->getId() == $action) { $valid = true; break; } } } if (!$valid) Http::response(400, 'Not a valid action for this thread'); $thread->triggerAction($action); } private static function _changeSelectedTicketsStatus($state, $info=array(), $errors=array()) { $count = $_REQUEST['count'] ?: ($_REQUEST['tids'] ? count($_REQUEST['tids']) : 0); $info['title'] = sprintf(__('Change Status — %1$d %2$s selected'), $count, _N('ticket', 'tickets', $count) ); if (!strcasecmp($state, 'deleted')) { $info['warn'] = sprintf(__( 'Are you sure you want to DELETE %s?'), _N('selected ticket', 'selected tickets', $count) ); $info['extra'] = sprintf('<strong>%s</strong>', __( 'Deleted tickets CANNOT be recovered, including any associated attachments.') ); $info['placeholder'] = sprintf(__( 'Optional reason for deleting %s'), _N('selected ticket', 'selected tickets', $count)); } $info['status_id'] = $info['status_id'] ?: $_REQUEST['status_id']; $info['comments'] = Format::htmlchars($_REQUEST['comments']); return self::_changeStatus($state, $info, $errors); } private static function _changeTicketStatus($ticket, $state, $info=array(), $errors=array()) { $verb = TicketStateField::getVerb($state); $info['action'] = sprintf('#tickets/%d/status', $ticket->getId()); $info['title'] = sprintf(__( /* 1$ will be a verb, like 'open', 2$ will be the ticket number */ '%1$s Ticket #%2$s'), $verb ?: $state, $ticket->getNumber() ); // Deleting? if (!strcasecmp($state, 'deleted')) { $info['placeholder'] = sprintf(__( 'Optional reason for deleting %s'), __('this ticket')); $info[ 'warn'] = sprintf(__( 'Are you sure you want to DELETE %s?'), __('this ticket')); //TODO: remove message below once we ship data retention plug $info[ 'extra'] = sprintf('<strong>%s</strong>', __('Deleted tickets CANNOT be recovered, including any associated attachments.') ); } $info['status_id'] = $info['status_id'] ?: $ticket->getStatusId(); $info['comments'] = Format::htmlchars($_REQUEST['comments']); // Has Children? $info['children'] = count($ticket->getChildren()); return self::_changeStatus($state, $info, $errors); } private static function _changeStatus($state, $info=array(), $errors=array()) { if ($info && isset($info['errors'])) $errors = array_merge($errors, $info['errors']); if (!$info['error'] && isset($errors['err'])) $info['error'] = $errors['err']; include(STAFFINC_DIR . 'templates/ticket-status.tmpl.php'); } function tasks($tid) { global $thisstaff; if (!($ticket=Ticket::lookup($tid)) || !$ticket->checkStaffPerm($thisstaff)) Http::response(404, 'Unknown ticket'); include STAFFINC_DIR . 'ticket-tasks.inc.php'; } function relations($tid) { global $thisstaff; if (!($ticket=Ticket::lookup($tid)) || !$ticket->checkStaffPerm($thisstaff)) Http::response(404, 'Unknown ticket'); include STAFFINC_DIR . 'ticket-relations.inc.php'; } function addTask($tid, $vars=array()) { global $thisstaff; if (!($ticket=Ticket::lookup($tid))) Http::response(404, 'Unknown ticket'); if (!$ticket->checkStaffPerm($thisstaff, Task::PERM_CREATE)) Http::response(403, 'Permission denied'); $info=$errors=array(); // Internal form $iform = TaskForm::getInternalForm($_POST); // Due date must be before tickets due date if ($ticket && $ticket->getEstDueDate() && Misc::db2gmtime($ticket->getEstDueDate()) > Misc::gmtime() && ($f=$iform->getField('duedate'))) { $f->configure('max', Misc::db2gmtime($ticket->getEstDueDate())); } $vars = array_merge($_SESSION[':form-data'] ?: array(), $vars); if ($_POST) { Draft::deleteForNamespace( sprintf('ticket.%d.task', $ticket->getId()), $thisstaff->getId()); // Default form $form = TaskForm::getInstance(); $form->setSource($_POST); $isvalid = true; if (!$iform->isValid()) $isvalid = false; if (!$form->isValid()) $isvalid = false; if ($isvalid) { $vars = $_POST; $vars['object_id'] = $ticket->getId(); $vars['object_type'] = ObjectModel::OBJECT_TYPE_TICKET; $vars['default_formdata'] = $form->getClean(); $vars['internal_formdata'] = $iform->getClean(); $desc = $form->getField('description'); $vars['description'] = $desc->getClean(); if ($desc && $desc->isAttachmentsEnabled() && ($attachments=$desc->getWidget()->getAttachments())) $vars['files'] = $attachments->getFiles(); $vars['staffId'] = $thisstaff->getId(); $vars['poster'] = $thisstaff; $vars['ip_address'] = $_SERVER['REMOTE_ADDR']; if (($task=Task::create($vars, $errors))) { if ($_SESSION[':form-data']['eid']) { //add internal note to ticket: $taskLink = sprintf('<a href="tasks.php?id=%d"><b>#%s</b></a>', $task->getId(), $task->getNumber()); $entryLink = sprintf('<a href="#entry-%d"><b>%s</b></a>', $_SESSION[':form-data']['eid'], Format::datetime($_SESSION[':form-data']['timestamp'])); $note = array( 'title' => __('Task Created From Thread Entry'), 'body' => sprintf(__( // %1$s is the task ID number and %2$s is the thread // entry date 'Task %1$s<br/> Thread Entry: %2$s'), $taskLink, $entryLink) ); $ticket->logNote($note['title'], $note['body'], $thisstaff); //add internal note to task: $ticketLink = sprintf('<a href="tickets.php?id=%d"><b>#%s</b></a>', $ticket->getId(), $ticket->getNumber()); $note = array( 'title' => __('Task Created From Thread Entry'), 'note' => sprintf(__('This Task was created from Ticket %1$s'), $ticketLink), ); $task->postNote($note, $errors, $thisstaff); } } Http::response(201, $task->getId()); } $info['error'] = sprintf('%s - %s', __('Error adding task'), __('Please try again!')); } $info['action'] = sprintf('#tickets/%d/add-task', $ticket->getId()); $info['title'] = sprintf( __( 'Ticket #%1$s: %2$s'), $ticket->getNumber(), __('Add New Task') ); include STAFFINC_DIR . 'templates/task.tmpl.php'; } function task($tid, $id) { global $thisstaff; if (!($ticket=Ticket::lookup($tid)) || !$ticket->checkStaffPerm($thisstaff)) Http::response(404, 'Unknown ticket'); // Lookup task and check access if (!($task=Task::lookup($id)) || !$task->checkStaffPerm($thisstaff)) Http::response(404, 'Unknown task'); $info = $errors = array(); $note_attachments_form = new SimpleForm(array( 'attachments' => new FileUploadField(array('id'=>'attach', 'name'=>'attach:note', 'configuration' => array('extensions'=>''))) )); $reply_attachments_form = new SimpleForm(array( 'attachments' => new FileUploadField(array('id'=>'attach', 'name'=>'attach:reply', 'configuration' => array('extensions'=>''))) )); if ($_POST) { $vars = $_POST; switch ($_POST['a']) { case 'postnote': $attachments = $note_attachments_form->getField('attachments')->getFiles(); $vars['files'] = array_merge( $vars['files'] ?: array(), $attachments); if (($note=$task->postNote($vars, $errors, $thisstaff))) { $msg=__('Note posted successfully'); // Clear attachment list $note_attachments_form->setSource(array()); $note_attachments_form->getField('attachments')->reset(); Draft::deleteForNamespace('task.note.'.$task->getId(), $thisstaff->getId()); } else { if (!$errors['err']) $errors['err'] = __('Unable to post the note - missing or invalid data.'); } break; case 'postreply': $attachments = $reply_attachments_form->getField('attachments')->getFiles(); $vars['files'] = array_merge( $vars['files'] ?: array(), $attachments); if (($response=$task->postReply($vars, $errors))) { $msg=__('Update posted successfully'); // Clear attachment list $reply_attachments_form->setSource(array()); $reply_attachments_form->getField('attachments')->reset(); Draft::deleteForNamespace('task.response.'.$task->getId(), $thisstaff->getId()); } else { if (!$errors['err']) $errors['err'] = __('Unable to post the reply - missing or invalid data.'); } break; default: $errors['err'] = __('Unknown action'); } } include STAFFINC_DIR . 'templates/task-view.tmpl.php'; } function export($id) { global $thisstaff; if (is_numeric($id)) $queue = SavedSearch::lookup($id); else $queue = AdhocSearch::load($id); if (!$queue) Http::response(404, 'Unknown Queue'); return $this->queueExport($queue); } function queueExport(CustomQueue $queue) { global $thisstaff; if (!$thisstaff) Http::response(403, 'Agent login is required'); elseif (!$queue || !$queue->checkAccess($thisstaff)) Http::response(404, 'No such saved queue'); $errors = array(); if ($_POST && is_array($_POST['fields'])) { // Cache export preferences $id = $queue->getId(); $_SESSION['Export:Q'.$id]['fields'] = $_POST['fields']; $_SESSION['Export:Q'.$id]['filename'] = $_POST['filename']; $_SESSION['Export:Q'.$id]['delimiter'] = $_POST['csv-delimiter']; // Save fields selection if requested if ($queue->isSaved() && isset($_POST['save-changes'])) $queue->updateExports(array_flip($_POST['fields'])); // Filename of the report if (isset($_POST['filename']) && ($parts = pathinfo($_POST['filename']))) { $filename = $_POST['filename']; if (strcasecmp($parts['extension'], 'csv')) $filename ="$filename.csv"; } else { $filename = sprintf('%s Tickets-%s.csv', $queue->getName(), date('Ymd')); } try { $interval = 5; $options = ['filename' => $filename, 'interval' => $interval, 'delimiter' => $_POST['csv-delimiter']]; // Create desired exporter $exporter = new CsvExporter($options); // Acknowledge the export $exporter->ack(); // Phew... now we're free to do the export // Ask the queue to export to the exporter $queue->export($exporter); $exporter->finalize(); // Email the export if it exists $exporter->email($thisstaff); // Delete the file. @$exporter->delete(); exit; } catch (Exception $ex) { $errors['err'] = __('Unable to prepare the export'); } } include STAFFINC_DIR . 'templates/queue-export.tmpl.php'; } } ?>