diff --git a/include/class.thread.php b/include/class.thread.php index 3cf9308025ffed40b4ca3744dd538c16d9e4715f..e715b6f465273901d62e2d63aeebf57651708e41 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -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() ); } diff --git a/include/class.thread_actions.php b/include/class.thread_actions.php index ce6ac82469df253fe5aecb942759de7f4a814252..503578ec46ba4f4e9cceced3e9e52583f1047ff6 100644 --- a/include/class.thread_actions.php +++ b/include/class.thread_actions.php @@ -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'); diff --git a/include/staff/templates/thread-entry-edit.tmpl.php b/include/staff/templates/thread-entry-edit.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..8ebeca0ac9bc24e2789333c23efccbaa5ae189e9 --- /dev/null +++ b/include/staff/templates/thread-entry-edit.tmpl.php @@ -0,0 +1,32 @@ +<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> diff --git a/include/staff/templates/thread-entry-view.tmpl.php b/include/staff/templates/thread-entry-view.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..500aefed15df408eb8762d93baf9c1393b445078 --- /dev/null +++ b/include/staff/templates/thread-entry-view.tmpl.php @@ -0,0 +1,18 @@ +<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> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index e2cc27c5f3441b18d42b9c6b227b6ae0583158dc..8823977dae020f16d792e772cedb25947c2676d7 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -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> diff --git a/js/redactor-osticket.js b/js/redactor-osticket.js index bd32edda61afbc875b2aa08d3fef296f6912db39..69dab8e76f7b1022317710e96efe13d581e4d493 100644 --- a/js/redactor-osticket.js +++ b/js/redactor-osticket.js @@ -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') diff --git a/scp/css/scp.css b/scp/css/scp.css index 64735d3de27b7959e24fe40d1179aa2aff0764e1..7817e76560c9610788831f445bd6caa6f4c743e5 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -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; }