Skip to content
Snippets Groups Projects
Commit 3421d6b0 authored by Jared Hancock's avatar Jared Hancock
Browse files

Introduce the idea of actions for threads

parent 505257a9
No related branches found
No related tags found
No related merge requests found
......@@ -467,9 +467,6 @@ table.thread-entry {
table.thread-entry th div span {
vertical-align: middle;
}
table.thread-entry th div :not(.title) {
font-weight: 600;
}
table.thread-entry th div .title {
font-weight: 400;
}
......
......@@ -780,6 +780,25 @@ class TicketsAjaxAPI extends AjaxController {
return self::_changeSelectedTicketsStatus($state, $info, $errors);
}
function triggerThreadAction($ticket_id, $thread_id, $action) {
$thread = ThreadEntry::lookup($thread_id, $ticket_id);
if (!$thread)
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 function _changeSelectedTicketsStatus($state, $info=array(), $errors=array()) {
$count = $_REQUEST['count'] ?:
......
......@@ -208,8 +208,10 @@ class AttachmentFile {
if ($bk->sendRedirectUrl('inline'))
return;
$this->makeCacheable();
Http::download($this->getName(), $this->getType() ?: 'application/octet-stream',
null, 'inline');
$type = $this->getType() ?: 'application/octet-stream';
if (isset($_REQUEST['overridetype']))
$type = $_REQUEST['overridetype'];
Http::download($this->getName(), $type, null, 'inline');
header('Content-Length: '.$this->getSize());
$this->sendData(false);
exit();
......
......@@ -1079,6 +1079,65 @@ class ThreadEntry {
static function add($vars) {
return ($entry=self::create($vars)) ? $entry->getId() : 0;
}
// Extensible thread entry actions ------------------------
/**
* getActions
*
* Retrieve a list of possible actions. This list is shown to the agent
* via drop-down list at the top-right of the thread entry when rendered
* in the UI.
*/
function getActions() {
if (!isset($this->_actions)) {
$this->_actions = array();
foreach (self::$action_registry as $group=>$list) {
$T = array();
$this->_actions[__($group)] = &$T;
foreach ($list as $id=>$action) {
$A = new $action($this);
if ($A->isVisible()) {
$T[$id] = $A;
}
}
unset($T);
}
}
return $this->_actions;
}
function hasActions() {
foreach ($this->getActions() as $group => $list) {
if (count($list))
return true;
}
return false;
}
function triggerAction($name) {
foreach ($this->getActions() as $group=>$list) {
foreach ($list as $id=>$action) {
if (0 === strcasecmp($id, $name)) {
if (!$action->isEnabled())
return false;
$action->trigger();
return true;
}
}
}
return false;
}
static $action_registry = array();
static function registerAction($group, $action) {
if (!isset(self::$action_registry[$group]))
self::$action_registry[$group] = array();
self::$action_registry[$group][$action::getId()] = $action;
}
}
......@@ -1253,7 +1312,7 @@ class HtmlThreadEntryBody extends ThreadEntryBody {
function getSearchable() {
// <br> -> \n
$body = preg_replace(array('`<br(\s*)?/?>`i', '`</div>`i'), "\n", $this->body);
$body = preg_replace(array('`<br(\s*)?/?>`i', '`</div>`i'), "\n", $this->body); # <?php
$body = Format::htmldecode(Format::striptags($body));
return Format::searchable($body);
}
......@@ -1558,4 +1617,69 @@ class TicketThread extends ObjectThread {
));
}
}
/**
* Class: ThreadEntryAction
*
* Defines a simple action to be performed on a thread entry item, such as
* viewing the raw email headers used to generate the message, resend the
* confirmation emails, etc.
*/
abstract class ThreadEntryAction {
static $name; // Friendly, translatable name
static $id; // Unique identifier used for plumbing
static $icon = 'cog';
var $thread;
function getName() {
$class = get_class($this);
return __($class::$name);
}
static function getId() {
return static::$id;
}
function getIcon() {
$class = get_class($this);
return 'icon-' . $class::$icon;
}
function __construct(ThreadEntry $thread) {
$this->thread = $thread;
}
abstract function trigger();
function getTicket() {
return $this->thread->getTicket();
}
function isEnabled() {
return $this->isVisible();
}
function isVisible() {
return true;
}
/**
* getJsStub
*
* Retrieves a small JavaScript snippet to insert into the rendered page
* which should, via an AJAX callback, trigger this action to be
* performed. The URL for this sort of activity is already provided for
* you via the ::getAjaxUrl() method in this class.
*/
abstract function getJsStub();
function getAjaxUrl($dialog=false) {
return sprintf('%stickets/%d/thread/%d/%s',
$dialog ? '#' : 'ajax.php/',
$this->thread->getTicketId(),
$this->thread->getId(),
static::getId()
);
}
}
?>
......@@ -393,7 +393,8 @@ $tcount+= $ticket->getNumNotes();
/* -------- Messages & Responses & Notes (if inline)-------------*/
$types = array('M', 'R', 'N');
if(($thread=$ticket->getThreadEntries($types))) {
foreach($thread as $entry) { ?>
foreach($thread as $entry) {
$tentry = $ticket->getThreadEntry($entry['id']); ?>
<table class="thread-entry <?php echo $threadTypes[$entry['type']]; ?>" cellspacing="0" cellpadding="1" width="940" border="0">
<tr>
<th colspan="4" width="100%">
......@@ -404,6 +405,28 @@ $tcount+= $ticket->getNumNotes();
<span style="display:inline-block;padding:0 1em" class="faded title"><?php
echo Format::truncate($entry['title'], 100); ?></span>
</span>
<?php if ($tentry->hasActions()) {
$actions = $tentry->getActions(); ?>
<div class="pull-right">
<span class="action-button" data-dropdown="#entry-action-more-<?php echo $entry['id']; ?>">
<i class="icon-caret-down"></i>
<span ><i class="icon-cog"></i></span>
</span>
<div id="entry-action-more-<?php echo $entry['id']; ?>" class="action-dropdown anchor-right">
<ul class="title">
<?php foreach ($actions as $group => $list) {
foreach ($list as $id => $action) { ?>
<li>
<a class="no-pjax" href="#" onclick="javascript:
<?php echo str_replace('"', '\\"', $action->getJsStub()); ?>; return false;">
<i class="<?php echo $action->getIcon(); ?>"></i> <?php
echo $action->getName();
?></a></li>
<?php }
} ?>
</ul>
</div>
<?php } ?>
<span class="pull-right" style="white-space:no-wrap;display:inline-block">
<span style="vertical-align:middle;" class="textra"></span>
<span style="vertical-align:middle;"
......@@ -419,7 +442,6 @@ $tcount+= $ticket->getNumNotes();
<?php
$urls = null;
if($entry['attachments']
&& ($tentry = $ticket->getThreadEntry($entry['id']))
&& ($urls = $tentry->getAttachmentUrls())
&& ($links = $tentry->getAttachmentsLinks())) {?>
<tr>
......
......@@ -142,6 +142,7 @@ $dispatcher = patterns('',
url_get('^(?P<tid>\d+)/canned-resp/(?P<cid>\w+).(?P<format>json|txt)', 'cannedResponse'),
url_get('^(?P<tid>\d+)/status/(?P<status>\w+)(?:/(?P<sid>\d+))?$', 'changeTicketStatus'),
url_post('^(?P<tid>\d+)/status$', 'setTicketStatus'),
url('^(?P<tid>\d+)/thread/(?P<thread_id>\d+)/(?P<action>\w+)$', 'triggerThreadAction'),
url_get('^status/(?P<status>\w+)(?:/(?P<sid>\d+))?$', 'changeSelectedTicketsStatus'),
url_post('^status/(?P<state>\w+)$', 'setSelectedTicketsStatus'),
url_get('^(?P<tid>\d+)/tasks$', 'tasks'),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment