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

Add concept of thread editing

Threads can be edited by marking the original as hidden and setting it's PID to
the id of the new entry. The new entry has cloned data from the original and
sets the `updated` timestamp to reflect the time of last edit. An edited flag
is added to the new entry to reflect its origin.

This patch suggests that agents can edit their own posts, department managers
can edit posts while the ticket is in their department, and that help desk
admins can edit anything.

If a post is edited more than once, only the most recent edit is kept.
parent 9ab9f3e4
No related branches found
No related tags found
No related merge requests found
......@@ -82,6 +82,7 @@ class Thread extends VerySimpleModel {
'has_attachments' => SqlAggregate::COUNT('attachments', false,
new Q(array('attachments__inline'=>0)))
));
$base->exclude(array('flags__hasbit'=>ThreadEntry::FLAG_HIDDEN));
if ($criteria)
$base->filter($criteria);
return $base;
......@@ -398,7 +399,8 @@ class ThreadEntry extends VerySimpleModel {
static $meta = array(
'table' => THREAD_ENTRY_TABLE,
'pk' => array('id'),
'select_related' => array('staff', 'user'),
'select_related' => array('staff', 'user', 'email_info'),
'ordering' => array('created'),
'joins' => array(
'thread' => array(
'constraint' => array('thread_id' => 'Thread.id'),
......@@ -430,6 +432,8 @@ class ThreadEntry extends VerySimpleModel {
);
const FLAG_ORIGINAL_MESSAGE = 0x0001;
const FLAG_EDITED = 0x0002;
const FLAG_HIDDEN = 0x0004;
var $_headers;
var $_thread;
......@@ -1734,7 +1738,7 @@ abstract class ThreadEntryAction {
static $id; // Unique identifier used for plumbing
static $icon = 'cog';
var $thread;
var $entry;
function getName() {
$class = get_class($this);
......@@ -1751,13 +1755,13 @@ abstract class ThreadEntryAction {
}
function __construct(ThreadEntry $thread) {
$this->thread = $thread;
$this->entry = $thread;
}
abstract function trigger();
function getTicket() {
return $this->thread->getTicket();
return $this->entry->getObject();
}
function isEnabled() {
......@@ -1791,8 +1795,8 @@ abstract class ThreadEntryAction {
function getAjaxUrl($dialog=false) {
return sprintf('%stickets/%d/thread/%d/%s',
$dialog ? '#' : 'ajax.php/',
$this->thread->getThread()->getObjectId(),
$this->thread->getId(),
$this->entry->getThread()->getObjectId(),
$this->entry->getId(),
static::getId()
);
}
......
......@@ -23,14 +23,13 @@ class TEA_ShowEmailHeaders extends ThreadEntryAction {
static $name = /* trans */ 'View Email Headers';
static $icon = 'envelope';
function isEnabled() {
function isVisible() {
global $thisstaff;
return $thisstaff && $thisstaff->isAdmin();
}
if (!$this->entry->getEmailHeader())
return false;
function isVisible() {
return (bool) $this->thread->getEmailHeader();
return $thisstaff && $thisstaff->isAdmin();
}
function getJsStub() {
......@@ -47,9 +46,159 @@ class TEA_ShowEmailHeaders extends ThreadEntryAction {
}
private function trigger__get() {
$headers = $this->thread->getEmailHeader();
$headers = $this->entry->getEmailHeader();
include STAFFINC_DIR . 'templates/thread-email-headers.tmpl.php';
}
}
ThreadEntry::registerAction(/* trans */ 'E-Mail', 'TEA_ShowEmailHeaders');
class TEA_EditThreadEntry extends ThreadEntryAction {
static $id = 'edit';
static $name = /* trans */ 'Edit';
static $icon = 'pencil';
function isVisible() {
// Can't edit system posts
return $this->entry->staff_id || $this->entry->user_id;
}
function isEnabled() {
global $thisstaff;
// You can edit your own posts or posts by your department members
// if your a manager, or everyone's if your an admin
return $thisstaff && (
$thisstaff->isAdmin()
|| (($T = $this->entry->getThread()->getObject())
&& $T instanceof Ticket
&& $T->getDept()->getManagerId() == $thisstaff->getId()
)
|| ($this->entry->getStaffId() == $thisstaff->getId())
);
}
function getJsStub() {
return sprintf(<<<JS
var url = '%s';
$.dialog(url, [201], function(xhr, resp) {
var json = JSON.parse(resp);
if (!json || !json.thread_id)
return;
$('#thread-id-'+json.thread_id)
.attr('id', 'thread-id-' + json.new_id)
.find('div')
.html(json.body)
.closest('td')
.effect('highlight')
}, {size:'large'});
JS
, $this->getAjaxUrl());
}
function trigger() {
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
return $this->trigger__get();
case 'POST':
return $this->trigger__post();
}
}
private function trigger__get() {
global $cfg;
include STAFFINC_DIR . 'templates/thread-entry-edit.tmpl.php';
}
private function trigger__post() {
global $thisstaff;
$old = $this->entry;
$type = ($old->format == 'html')
? 'HtmlThreadEntryBody' : 'TextThreadEntryBody';
$new = new $type($_POST['body']);
if ($new->getClean() == $old->body)
// No update was performed
Http::response(201);
$entry = ThreadEntry::create(array(
// Copy most information from the old entry
'poster' => $old->poster,
'userId' => $old->user_id,
'staffId' => $old->staff_id,
'type' => $old->type,
'threadId' => $old->thread_id,
// Add in new stuff
'title' => $_POST['title'],
'body' => $new,
'ip_address' => $_SERVER['REMOTE_ADDR'],
));
if (!$entry)
return $this->trigger__get();
// Note, anything that points to the $old entry as PID should remain
// that way for email header lookups and such to remain consistent
if ($old->flags & ThreadEntry::FLAG_EDITED) {
// Second and further edit ---------------
$original = ThreadEntry::lookup(array('pid'=>$old->id));
// Drop the previous edit, and base this edit off the original
$old->delete();
$old = $original;
}
// Mark the new entry as editited (but not hidden)
$entry->flags = ($old->flags & ~ThreadEntry::FLAG_HIDDEN)
| ThreadEntry::FLAG_EDITED;
$entry->created = $old->created;
$entry->updated = SqlFunction::NOW();
$entry->save();
// Hide the old entry from the object thread
$old->pid = $entry->id;
$old->flags |= ThreadEntry::FLAG_HIDDEN;
$old->save();
Http::response('201', JsonDataEncoder::encode(array(
'thread_id' => $this->entry->id,
'new_id' => $entry->id,
'body' => $entry->getBody()->toHtml(),
)));
}
}
ThreadEntry::registerAction(/* trans */ 'Manage', 'TEA_EditThreadEntry');
class TEA_OrigThreadEntry extends ThreadEntryAction {
static $id = 'previous';
static $name = /* trans */ 'View Original';
static $icon = 'undo';
function isVisible() {
// Can't edit system posts
return $this->entry->flags & ThreadEntry::FLAG_EDITED;
}
function getJsStub() {
return sprintf("$.dialog('%s');",
$this->getAjaxUrl()
);
}
function trigger() {
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
return $this->trigger__get();
}
}
private function trigger__get() {
$entry = ThreadEntry::lookup(array('pid'=>$this->entry->getId()));
include STAFFINC_DIR . 'templates/thread-entry-view.tmpl.php';
}
}
ThreadEntry::registerAction(/* trans */ 'Manage', 'TEA_OrigThreadEntry');
<h3><?php echo __('Edit Thread Entry'); ?></h3>
<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b>
<hr/>
<form method="post" action="<?php
echo str_replace('ajax.php/','#',$this->getAjaxUrl()); ?>">
<input type="text" style="width:100%;font-size:14px" placeholder="<?php
echo __('Title'); ?>" name="title" value="<?php
echo Format::htmlchars($this->entry->title); ?>"/>
<hr style="height:0"/>
<textarea style="display: block; width: 100%; height: auto; min-height: 150px;"
name="body"
class="large <?php
if ($cfg->isHtmlThreadEnabled() && $this->entry->format == 'html')
echo 'richtext';
?>"><?php echo Format::viewableImages($this->entry->body);
?></textarea>
<hr>
<p class="full-width">
<span class="buttons pull-left">
<input type="button" name="cancel" class="close"
value="<?php echo __('Cancel'); ?>">
</span>
<span class="buttons pull-right">
<input type="submit" name="save"
value="<?php echo __('Save Changes'); ?>">
</span>
</p>
</form>
<h3><?php echo __('Original Thread Entry'); ?></h3>
<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b>
<hr/>
<div><strong><?php echo Format::htmlchars($entry->title); ?></strong></div>
<div class="thread-body" style="background-color:transparent">
<?php echo $entry->getBody()->toHtml(); ?>
</div>
<hr>
<p class="full-width">
<span class="buttons pull-right">
<input type="button" name="cancel" class="close"
value="<?php echo __('Close'); ?>">
</span>
</p>
</form>
......@@ -434,7 +434,13 @@ $tcount = $ticket->getThreadEntries($types)->count();
</div>
<?php } ?>
<span style="vertical-align:middle">
<span style="vertical-align:middle;" class="textra"></span>
<span style="vertical-align:middle;" class="textra">
<?php if ($entry->flags & ThreadEntry::FLAG_EDITED) { ?>
<span class="label label-bare" title="<?php
echo sprintf(__('Edited on %s by %s'), Format::datetime($entry->updated), 'You');
?>"><?php echo __('Edited'); ?></span>
<?php } ?>
</span>
<span style="vertical-align:middle;"
class="tmeta faded title"><?php
echo Format::htmlchars($entry->getName()); ?></span>
......
......@@ -251,7 +251,8 @@ $(function() {
'file', 'table', 'link', '|', 'alignment', '|',
'horizontalrule'],
'buttonSource': !el.hasClass('no-bar'),
'autoresize': !el.hasClass('no-bar'),
'autoresize': !el.hasClass('no-bar') && !el.closest('.dialog').length,
'maxHeight': el.closest('.dialog').length ? selectedSize : false,
'minHeight': selectedSize,
'focus': false,
'plugins': el.hasClass('no-bar')
......
......@@ -1947,7 +1947,7 @@ tr.disabled th {
.label {
font-size: 11px;
padding: 1px 4px 2px;
padding: 1px 4px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
......@@ -1959,6 +1959,13 @@ tr.disabled th {
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
background-color: #999999;
}
.label-bare {
background-color: transparent;
background-color: rgba(0,0,0,0);
border: 1px solid #999999;
color: #999999;
text-shadow: none;
}
.label-info {
background-color: #3a87ad;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment