Newer
Older
<?php
/*********************************************************************
ajax.tickets.php
AJAX interface for tickets
Peter Rotich <peter@osticket.com>
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');
class TicketsAjaxAPI extends AjaxController {
$limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25;
$sql='SELECT DISTINCT ticketID, email.address AS email'
.' LEFT JOIN '.USER_TABLE.' user ON user.id = ticket.user_id'
.' LEFT JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id'
.' WHERE ticketID LIKE \''.db_input($_REQUEST['q'], false).'%\'';
$sql.=' AND ( staff_id='.db_input($thisstaff->getId());
if(($teams=$thisstaff->getTeams()) && count(array_filter($teams)))
$sql.=' OR team_id IN('.implode(',', db_input(array_filter($teams))).')';
if(!$thisstaff->showAssignedOnly() && ($depts=$thisstaff->getDepts()))
$sql.=' OR dept_id IN ('.implode(',', db_input($depts)).')';
if(($res=db_query($sql)) && db_num_rows($res)) {
while(list($id, $email)=db_fetch_row($res)) {
$info = "$id - $email";
$tickets[] = array('id'=>$id, 'email'=>$email, 'value'=>$id,
'info'=>$info, 'matches'=>$_REQUEST['q']);
}
if (!$tickets)
return self::lookupByEmail();
return $this->json_encode($tickets);
}
$limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25;
$tickets=array();
$sql='SELECT email.address AS email, count(ticket.ticket_id) as tickets '
.' JOIN '.USER_TABLE.' user ON user.id = ticket.user_id'
.' JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id'
.' LEFT JOIN '.FORM_ENTRY_TABLE.' entry ON (entry.object_id = user.id
AND entry.object_type=\'U\')
LEFT JOIN '.FORM_ANSWER_TABLE.' data ON (data.entry_id = entry.id)'
.' WHERE (email.address LIKE \'%'.db_input(strtolower($_REQUEST['q']), false).'%\'
OR data.value LIKE \'%'.db_input($_REQUEST['q'], false).'%\')';
$sql.=' AND ( staff_id='.db_input($thisstaff->getId());
if(($teams=$thisstaff->getTeams()) && count(array_filter($teams)))
$sql.=' OR team_id IN('.implode(',', db_input(array_filter($teams))).')';
if(!$thisstaff->showAssignedOnly() && ($depts=$thisstaff->getDepts()))
$sql.=' OR dept_id IN ('.implode(',', db_input($depts)).')';
if(($res=db_query($sql)) && db_num_rows($res)) {
while(list($email, $count)=db_fetch_row($res))
$tickets[] = array('email'=>$email, 'value'=>$email,
'info'=>"$email ($count)", 'matches'=>$_REQUEST['q']);
Peter Rotich
committed
global $thisstaff, $cfg;
$select = 'SELECT DISTINCT ticket.ticket_id';
$from = ' FROM '.TICKET_TABLE.' ticket ';
$where = ' WHERE 1 ';
$where.=' AND ( ticket.staff_id='.db_input($thisstaff->getId());
if(($teams=$thisstaff->getTeams()) && count(array_filter($teams)))
$where.=' OR ticket.team_id IN('.implode(',', db_input(array_filter($teams))).')';
if(!$thisstaff->showAssignedOnly() && ($depts=$thisstaff->getDepts()))
$where.=' OR ticket.dept_id IN ('.implode(',', db_input($depts)).')';
if($req['deptId'])
$where.=' AND ticket.dept_id='.db_input($req['deptId']);
if($req['topicId'])
$where.=' AND ticket.topic_id='.db_input($req['topicId']);
switch(strtolower($req['status'])) {
Peter Rotich
committed
case 'open':
case 'answered':
$where.=' AND ticket.status="open" AND ticket.isanswered=1 ';
break;
$where.=' AND ticket.status="open" AND ticket.isoverdue=1 ';
if(isset($req['assignee']) && strcasecmp($req['status'], 'closed')) {
$id=preg_replace("/[^0-9]/", "", $req['assignee']);
$assignee = $req['assignee'];
$where.= ' AND ( ( ticket.status="open" ';
$where.=' AND ticket.team_id='.db_input($id);
$where.=' AND ticket.staff_id='.db_input($id);
elseif(is_numeric($id))
$where.=' AND ticket.staff_id='.db_input($id);
$where.=')';
if($req['staffId'] && !$req['status']) //Assigned TO + Closed By
$where.= ' OR (ticket.staff_id='.db_input($req['staffId']). ' AND ticket.status="closed") ';
elseif(isset($req['staffId'])) // closed by any
Peter Rotich
committed
$where.= ' OR ticket.status="closed" ';
} elseif($req['staffId']) {
$where.=' AND (ticket.staff_id='.db_input($req['staffId']).' AND ticket.status="closed") ';
$startTime =($req['startDate'] && (strlen($req['startDate'])>=8))?strtotime($req['startDate']):0;
$endTime =($req['endDate'] && (strlen($req['endDate'])>=8))?strtotime($req['endDate']):0;
if( ($startTime && $startTime>time()) or ($startTime>$endTime && $endTime>0))
$startTime=$endTime=0;
$where.=' AND ticket.created>=FROM_UNIXTIME('.$startTime.')';
$where.=' AND ticket.created<=FROM_UNIXTIME('.$endTime.')';
if($req['query']) {
$queryterm=db_real_escape($req['query'], false);
$from.=' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON (ticket.ticket_id=thread.ticket_id )'
.' LEFT JOIN '.FORM_ENTRY_TABLE.' tentry ON (tentry.object_id = ticket.ticket_id
AND tentry.object_type="T")
LEFT JOIN '.FORM_ANSWER_TABLE.' tans ON (tans.entry_id = tentry.id
AND tans.value_id IS NULL)
LEFT JOIN '.FORM_ENTRY_TABLE.' uentry ON (uentry.object_id = ticket.user_id
AND uentry.object_type="U")
LEFT JOIN '.FORM_ANSWER_TABLE.' uans ON (uans.entry_id = uentry.id
AND uans.value_id IS NULL)
LEFT JOIN '.USER_TABLE.' user ON (ticket.user_id = user.id)
LEFT JOIN '.USER_EMAIL_TABLE.' uemail ON (user.id = uemail.user_id)';
$where.=" AND ( uemail.address LIKE '%$queryterm%'"
." OR user.name LIKE '%$queryterm%'"
." OR tans.value LIKE '%$queryterm%'"
." OR uans.value LIKE '%$queryterm%'"
." OR thread.body LIKE '%$queryterm%'"
$dynfields='(SELECT entry.object_id, %s '.
'FROM '.FORM_ANSWER_TABLE.' ans '.
'JOIN '.FORM_ENTRY_TABLE.' entry ON entry.id=ans.entry_id '.
'JOIN '.FORM_FIELD_TABLE.' field ON field.id=ans.field_id '.
'WHERE entry.object_type="T" GROUP BY entry.object_id)';
$vals = array();
foreach (TicketForm::getInstance()->getFields() as $f) {
if ($f->get('name') && isset($req[$f->getFormName()])
&& ($val = $req[$f->getFormName()])) {
$vals[] = "MAX(IF(field.name = '$name', ans.value_id, NULL)) as `{$name}_id`";
$vals[] = "MAX(IF(field.name = '$name', ans.value, NULL)) as `$name`";
$where .= " AND (dyn.`{$name}_id` = ".db_input($val)
. " OR dyn.`$name` LIKE '%".db_real_escape($val)."%')";
if ($vals)
$from .= ' LEFT JOIN '.sprintf($dynfields, implode(',', $vals))
." dyn ON (dyn.object_id = ticket.ticket_id)";
while (list($tickets[]) = db_fetch_row($res));
$tickets = array_filter($tickets);
return $tickets;
}
function search() {
$tickets = self::_search($_REQUEST);
if (count($tickets)) {
$uid = md5($_SERVER['QUERY_STRING']);
$_SESSION["adv_$uid"] = $tickets;
$result['success'] =sprintf(
"Search criteria matched %d %s - <a href='tickets.php?%s'>view</a>",
count($tickets), (count($tickets)>1?"tickets":"ticket"),
'advsid='.$uid
);
$result['fail']='No tickets found matching your search criteria.';
return $this->json_encode($result);
}
function acquireLock($tid) {
global $cfg,$thisstaff;
if(!$tid or !is_numeric($tid) or !$thisstaff or !$cfg or !$cfg->getLockTime())
Peter Rotich
committed
if(!($ticket = Ticket::lookup($tid)) || !$ticket->checkStaffAccess($thisstaff))
return $this->json_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'=>'Unable to acquire lock.'));
//Ticket already locked by staff...try renewing it.
$lock->renew(); //New clock baby!
Peter Rotich
committed
} 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));
Peter Rotich
committed
return $this->json_encode(array('id'=>$lock->getId(), 'time'=>$lock->getTime()));
}
function renewLock($tid, $id) {
global $thisstaff;
if(!$id or !is_numeric($id) or !$thisstaff)
return $this->json_encode(array('id'=>0, 'retry'=>true));
$lock= TicketLock::lookup($id);
if(!$lock || !$lock->getStaffId() || $lock->isExpired()) //Said lock doesn't exist or is is expired
return self::acquireLock($tid); //acquire the lock
if($lock->getStaffId()!=$thisstaff->getId()) //user doesn't own the lock anymore??? sorry...try to next time.
return $this->json_encode(array('id'=>0, 'retry'=>false)); //Give up...
//Renew the lock.
$lock->renew(); //Failure here is not an issue since the lock is not expired yet.. client need to check time!
return $this->json_encode(array('id'=>$lock->getId(), 'time'=>$lock->getTime()));
}
function releaseLock($tid, $id=0) {
global $thisstaff;
if($id && is_numeric($id)){ //Lock Id provided!
$lock = TicketLock::lookup($id, $tid);
//Already gone?
if(!$lock || !$lock->getStaffId() || $lock->isExpired()) //Said lock doesn't exist or is is expired
return 1;
//make sure the user actually owns the lock before releasing it.
return ($lock->getStaffId()==$thisstaff->getId() && $lock->release())?1:0;
}elseif($tid){ //release all the locks the user owns on the ticket.
return TicketLock::removeStaffLocks($thisstaff->getId(),$tid)?1:0;
}
return 0;
}
function previewTicket ($tid) {
global $thisstaff;
if(!$thisstaff || !($ticket=Ticket::lookup($tid)) || !$ticket->checkStaffAccess($thisstaff))
Http::response(404, 'No such ticket');
$staff=$ticket->getStaff();
$lock=$ticket->getLock();
$error=$msg=$warn=null;
if($lock && $lock->getStaffId()==$thisstaff->getId())
$warn.=' <span class="Icon lockedTicket">Ticket is locked by '.$lock->getStaffName().'</span>';
elseif($ticket->isOverdue())
$warn.=' <span class="Icon overdueTicket">Marked overdue!</span>';
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
ob_start();
echo sprintf(
'<div style="width:500px; padding: 2px 2px 0 5px;">
<h2>%s</h2><br>',Format::htmlchars($ticket->getSubject()));
if($error)
echo sprintf('<div id="msg_error">%s</div>',$error);
elseif($msg)
echo sprintf('<div id="msg_notice">%s</div>',$msg);
elseif($warn)
echo sprintf('<div id="msg_warning">%s</div>',$warn);
echo '<table border="0" cellspacing="" cellpadding="1" width="100%" class="ticket_info">';
$ticket_state=sprintf('<span>%s</span>',ucfirst($ticket->getStatus()));
if($ticket->isOpen()) {
if($ticket->isOverdue())
$ticket_state.=' — <span>Overdue</span>';
else
$ticket_state.=sprintf(' — <span>%s</span>',$ticket->getPriority());
}
echo sprintf('
<tr>
<th width="100">Ticket State:</th>
<td>%s</td>
</tr>
<tr>
<th>Create Date:</th>
<td>%s</td>
</tr>',$ticket_state,
Format::db_datetime($ticket->getCreateDate()));
if($ticket->isClosed()) {
echo sprintf('
<tr>
<th>Close Date:</th>
<td>%s <span class="faded">by %s</span></td>
</tr>',
Format::db_datetime($ticket->getCloseDate()),
($staff?$staff->getName():'staff')
);
} elseif($ticket->getEstDueDate()) {
echo sprintf('
<tr>
<th>Due Date:</th>
<td>%s</td>
</tr>',
Format::db_datetime($ticket->getEstDueDate()));
echo '<hr>
<table border="0" cellspacing="" cellpadding="1" width="100%" class="ticket_info">';
if($ticket->isOpen()) {
echo sprintf('
<tr>
<th width="100">Assigned To:</th>
<td>%s</td>
</tr>',$ticket->isAssigned()?implode('/', $ticket->getAssignees()):' <span class="faded">— Unassigned —</span>');
}
echo sprintf(
' <tr>
<th width="100">Department:</th>
<td>%s</td>
</tr>
<tr>
<th>Help Topic:</th>
<td>%s</td>
</tr>
<tr>
<th>From:</th>
<td>%s <span class="faded">%s</span></td>
</tr>',
Format::htmlchars($ticket->getDeptName()),
Format::htmlchars($ticket->getHelpTopic()),
Format::htmlchars($ticket->getName()),
$ticket->getEmail());
echo '
</table>';
$options[]=array('action'=>'Thread ('.$ticket->getThreadCount().')','url'=>"tickets.php?id=$tid");
if($ticket->getNumNotes())
$options[]=array('action'=>'Notes ('.$ticket->getNumNotes().')','url'=>"tickets.php?id=$tid#notes");
if($ticket->isOpen())
$options[]=array('action'=>'Reply','url'=>"tickets.php?id=$tid#reply");
if($thisstaff->canAssignTickets())
$options[]=array('action'=>($ticket->isAssigned()?'Reassign':'Assign'),'url'=>"tickets.php?id=$tid#assign");
if($thisstaff->canTransferTickets())
$options[]=array('action'=>'Transfer','url'=>"tickets.php?id=$tid#transfer");
$options[]=array('action'=>'Post Note','url'=>"tickets.php?id=$tid#note");
if($thisstaff->canEditTickets())
$options[]=array('action'=>'Edit Ticket','url'=>"tickets.php?id=$tid&a=edit");
echo '<ul class="tip_menu">';
foreach($options as $option)
echo sprintf('<li><a href="%s">%s</a></li>',$option['url'],$option['action']);
echo '</ul>';
echo '</div>';
$resp = ob_get_contents();
ob_end_clean();
//Collaborators utils
function addCollaborator($tid) {
global $thisstaff;
if (!($ticket=Ticket::lookup($tid))
|| !$ticket->checkStaffAccess($thisstaff))
Http::response(404, 'No such ticket');
//If not a post then assume new collaborator form
if(!$_POST)
return self::_addcollaborator($ticket);
$user = $form = null;
if (isset($_POST['id']) && $_POST['id']) { //Existing user/
$user = User::lookup($_POST['id']);
} else { //We're creating a new user!
$form = UserForm::getUserForm()->getForm($_POST);
$user = User::fromForm($form);
}
if ($user && ($c=$ticket->addCollaborator($user, $errors))) {
return self::_collaborators($ticket, $info);
}
if($errors && $errors['err']) {
$info +=array('error' => $errors['err']);
$info +=array('error' =>'Unable to add collaborator - try again');
return self::_addcollaborator($ticket, $user, $form, $info);
}
function updateCollaborator($cid) {
global $thisstaff;
if(!($c=Collaborator::lookup($cid))
|| !($user=$c->getUser())
|| !($ticket=$c->getTicket())
|| !$ticket->checkStaffAccess($thisstaff)
)
Http::response(404, 'Unknown collaborator');
$errors = array();
if(!$user->updateInfo($_POST, $errors))
return self::_collaborator($c ,$user->getForms($_POST), $errors);
$info = array('msg' => sprintf('%s updated successfully',
$c->getName()));
}
function viewCollaborator($cid) {
global $thisstaff;
if(!($collaborator=Collaborator::lookup($cid))
|| !($ticket=$collaborator->getTicket())
|| !$ticket->checkStaffAccess($thisstaff))
Http::response(404, 'Unknown collaborator');
return self::_collaborator($collaborator);
}
function showCollaborators($tid) {
global $thisstaff;
if(!($ticket=Ticket::lookup($tid))
|| !$ticket->checkStaffAccess($thisstaff))
Http::response(404, 'No such ticket');
if($ticket->getCollaborators())
return self::_collaborators($ticket);
return self::_addcollaborator($ticket);
}
function _addcollaborator($ticket, $user=null, $form=null, $info=array()) {
$info += array(
'title' => sprintf('Ticket #%s: Add a collaborator', $ticket->getNumber()),
'action' => sprintf('#tickets/%d/add-collaborator', $ticket->getId())
);
return self::_userlookup($user, $form, $info);
}
function updateCollaborators($tid) {
global $thisstaff;
if(!($ticket=Ticket::lookup($tid))
|| !$ticket->checkStaffAccess($thisstaff))
Http::response(404, 'No such ticket');
$errors = $info = array();
if ($ticket->updateCollaborators($_POST, $errors)) {
$info +=array('msg' => 'Collaborators updated successfully');
} elseif($errors && $errors['err']) {
$info +=array('error' => $errors['err']);
}
function _collaborator($collaborator, $form=null, $info=array()) {
$info += array('action' => '#collaborators/'.$collaborator->getId());
$user = $collaborator->getUser();
$resp = ob_get_contents();
ob_end_clean();
return $resp;
}
ob_start();
include(STAFFINC_DIR . 'templates/collaborators.tmpl.php');
$resp = ob_get_contents();
ob_end_clean();
return $resp;
}
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
function viewUser($tid) {
global $thisstaff;
if(!$thisstaff
|| !($ticket=Ticket::lookup($tid))
|| !$ticket->checkStaffAccess($thisstaff))
Http::response(404, 'No such ticket');
if(!($user = $ticket->getOwner()))
Http::response(404, 'Unknown user');
$info = array(
'title' => sprintf('Ticket #%s: %s', $ticket->getNumber(), $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->checkStaffAccess($thisstaff)
|| ! ($user = $ticket->getOwner()))
Http::response(404, 'No such ticket/user');
$errors = array();
if($user->updateInfo($_POST, $errors))
Http::response(201, $user->to_json());
$forms = $user->getForms();
$info = array(
'title' => sprintf('Ticket #%s: %s', $ticket->getNumber(), $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->checkStaffAccess($thisstaff))
Http::response(404, 'No such ticket');
$user = $ticket->getOwner();
$info = array(
'title' => sprintf('Change user for ticket #%s', $ticket->getNumber())
);
return self::_userlookup($user, $info);
}
function _userlookup($user, $form, $info) {
ob_start();
include(STAFFINC_DIR . 'templates/user-lookup.tmpl.php');
$resp = ob_get_contents();
ob_end_clean();
return $resp;
}