</type>
<!-- /moderation type -->
+ <!-- Visit Tracker -->
+ <type>
+ <name>com.woltlab.wcf.moderation.queue</name>
+ <definitionname>com.woltlab.wcf.visitTracker.objectType</definitionname>
+ </type>
+ <!-- /Visit Tracker -->
+
<!-- activity points -->
<type>
<name>com.woltlab.wcf.like.activityPointEvent.receivedLikes</name>
<title>{lang}wcf.moderation.moderation{/lang} {if $pageNo > 1}- {lang}wcf.page.pageNo{/lang} {/if}- {PAGE_TITLE|language}</title>
{include file='headInclude'}
+
+ <script data-relocate="true">
+ //<![CDATA[
+ $(function() {
+ new WCF.Moderation.Queue.MarkAsRead();
+ new WCF.Moderation.Queue.MarkAllAsRead();
+ });
+ //]]>
+ </script>
</head>
<body id="tpl{$templateName|ucfirst}" data-template="{$templateName}" data-application="{$templateNameApplication}">
{event name='sidebarBoxes'}
{/capture}
+{capture assign='headerNavigation'}
+ <li class="jsOnly"><a href="#" title="{lang}wcf.moderation.markAllAsRead{/lang}" class="markAllAsReadButton jsTooltip"><span class="icon icon16 icon-ok"></span> <span class="invisible">{lang}wcf.moderation.markAllAsRead{/lang}</span></a></li>
+{/capture}
+
{include file='header' sidebarOrientation='left'}
<header class="boxHeadline">
<table class="table">
<thead>
<tr>
- <th class="columnID{if $sortField == 'queueID'} active {@$sortOrder}{/if}"><a href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID={@$assignedUserID}&status={@$status}&pageNo={@$pageNo}&sortField=queueID&sortOrder={if $sortField == 'queueID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
<th class="columnText columnTitle" colspan="2">{lang}wcf.moderation.title{/lang}</th>
<th class="columnText columnAssignedUserID{if $sortField == 'assignedUsername'} active {@$sortOrder}{/if}"><a href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID={@$assignedUserID}&status={@$status}&pageNo={@$pageNo}&sortField=assignedUsername&sortOrder={if $sortField == 'assignedUsername' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.moderation.assignedUser{/lang}</a></th>
<th class="columnDigits columnComments{if $sortField == 'comments'} active {@$sortOrder}{/if}"><a href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID={@$assignedUserID}&status={@$status}&pageNo={@$pageNo}&sortField=comments&sortOrder={if $sortField == 'comments' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.moderation.comments{/lang}</a></th>
<tbody>
{foreach from=$objects item=entry}
- <tr>
- <td class="columnID">{@$entry->queueID}</td>
- <td class="columnIcon"><p class="framed">{@$entry->getUserProfile()->getAvatar()->getImageTag(32)}</p></td>
+ <tr class="moderationQueueEntry{if $entry->isNew()} new{/if}" data-queue-id="{@$entry->queueID}">
+ <td class="columnIcon columnAvatar">
+ <div>
+ <p class="framed"{if $entry->isNew()} title="{lang}wcf.moderation.markAsRead.doubleClick{/lang}"{/if}>{@$entry->getUserProfile()->getAvatar()->getImageTag(32)}</p>
+ </div>
+ </td>
<td class="columnText columnSubject">
<h3>
<span class="badge label">{lang}wcf.moderation.type.{@$definitionNames[$entry->objectTypeID]}{/lang}</span>
<a href="{link controller='ModerationList'}{/link}">
<span class="icon icon16 icon-warning-sign"></span>
<span>{lang}wcf.moderation.moderation{/lang}</span>
- {if $__wcf->getModerationQueueManager()->getOutstandingModerationCount()}<span class="badge badgeInverse">{#$__wcf->getModerationQueueManager()->getOutstandingModerationCount()}</span>{/if}
+ {if $__wcf->getModerationQueueManager()->getUnreadModerationCount()}<span class="badge badgeInverse">{#$__wcf->getModerationQueueManager()->getUnreadModerationCount()}</span>{/if}
</a>
{if !OFFLINE || $__wcf->session->getPermission('admin.general.canViewPageDuringOfflineMode')}
<script data-relocate="true">
}
});
+/**
+ * Namespace for moderation queue related classes.
+ */
+WCF.Moderation.Queue = { };
+
+/**
+ * Marks one moderation queue entry as read.
+ */
+WCF.Moderation.Queue.MarkAsRead = Class.extend({
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * Initializes the mark as read for queue entries.
+ */
+ init: function() {
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ $(document).on('dblclick', '.moderationList .new .columnAvatar', $.proxy(this._dblclick, this));
+ },
+
+ /**
+ * Handles double clicks on avatar.
+ *
+ * @param object event
+ */
+ _dblclick: function(event) {
+ this._proxy.setOption('data', {
+ actionName: 'markAsRead',
+ className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction',
+ objectIDs: [ $(event.currentTarget).parents('tr:eq(0)').data('queueID') ]
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Handles successful AJAX requests.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ $('.moderationList .new').each(function(index, element) {
+ var $element = $(element);
+ if (WCF.inArray($element.data('queueID'), data.objectIDs)) {
+ // remove new class
+ $element.removeClass('new');
+
+ // remove event
+ $element.find('.columnAvatar').off('dblclick');
+ }
+ });
+ }
+});
+
+/**
+ * Marks all moderation queue entries as read.
+ */
+WCF.Moderation.Queue.MarkAllAsRead = Class.extend({
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * Initializes the WCF.Moderation.Queue.MarkAllAsRead class.
+ */
+ init: function() {
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ $('.markAllAsReadButton').click($.proxy(this._click, this));
+ },
+
+ /**
+ * Handles clicks.
+ *
+ * @param object event
+ */
+ _click: function(event) {
+ event.preventDefault();
+
+ this._proxy.setOption('data', {
+ actionName: 'markAllAsRead',
+ className: 'wcf\\data\\moderation\\queue\\ModerationQueueAction'
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Marks all queue entries as read.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ // @todo fix dropdown
+
+ // @todo remove badge in userpanel
+
+ // fix moderation list
+ var $moderationList = $('.moderationList');
+ $moderationList.find('.new').removeClass('new');
+ $moderationList.find('.columnAvatar').off('dblclick');
+ }
+});
+
/**
* Namespace for activation related classes.
*/
* @return boolean
*/
public function isDone() {
- return ($this->status == self::STATUS_DONE);
+ return ($this->status == self::STATUS_DONE || $this->status == self::STATUS_CONFIRMED || $this->status == self::STATUS_REJECTED);
}
/**
use wcf\system\moderation\queue\ModerationQueueManager;
use wcf\system\request\LinkHandler;
use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\visitTracker\VisitTracker;
use wcf\system\WCF;
/**
'username' => $username
);
}
+
+ /**
+ * Marks queue entries as read.
+ */
+ public function markAsRead() {
+ if (empty($this->parameters['visitTime'])) {
+ $this->parameters['visitTime'] = TIME_NOW;
+ }
+
+ if (empty($this->objects)) {
+ $this->readObjects();
+ }
+
+ foreach ($this->objects as $queue) {
+ VisitTracker::getInstance()->trackObjectVisit('com.woltlab.wcf.moderation.queue', $queue->queueID, $this->parameters['visitTime']);
+ }
+
+ // reset storage
+ UserStorageHandler::getInstance()->reset(array(WCF::getUser()->userID), 'unreadModerationCount');
+ }
+
+ /**
+ * @see \wcf\data\IVisitableObjectAction::validateMarkAsRead()
+ */
+ public function validateMarkAsRead() {
+ if (empty($this->objects)) {
+ $this->readObjects();
+ }
+
+ foreach ($this->objects as $queue) {
+ if (!$queue->canEdit()) {
+ throw new PermissionDeniedException();
+ }
+ }
+ }
+
+ /**
+ * Marks all queue entries as read.
+ */
+ public function markAllAsRead() {
+ VisitTracker::getInstance()->trackTypeVisit('com.woltlab.wcf.moderation.queue');
+
+ // reset storage
+ UserStorageHandler::getInstance()->reset(array(WCF::getUser()->userID), 'unreadModerationCount');
+ }
+
+ /**
+ * Validates the mark all as read action.
+ */
+ public function validateMarkAllAsRead() {
+ // does nothing
+ }
}
use wcf\data\DatabaseObjectDecorator;
use wcf\data\IUserContent;
use wcf\system\moderation\queue\ModerationQueueManager;
+use wcf\system\visitTracker\VisitTracker;
/**
* Represents a viewable moderation queue entry.
$objectType = ObjectTypeCache::getInstance()->getObjectType($this->objectTypeID);
return $objectType->objectType;
}
+
+ /**
+ * Returns true if this queue item is new for the active user.
+ *
+ * @return boolean
+ */
+ public function isNew() {
+ if ($this->time > max(VisitTracker::getInstance()->getVisitTime('com.woltlab.wcf.moderation.queue'), VisitTracker::getInstance()->getObjectVisitTime('com.woltlab.wcf.moderation.queue', $this->queueID))) return true;
+
+ return false;
+ }
}
<?php
namespace wcf\form;
+use wcf\data\moderation\queue\ModerationQueueAction;
use wcf\data\moderation\queue\ViewableModerationQueue;
use wcf\system\breadcrumb\Breadcrumb;
use wcf\system\comment\CommentHandler;
$this->commentObjectTypeID = CommentHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.moderation.queue');
$this->commentManager = CommentHandler::getInstance()->getObjectType($this->commentObjectTypeID)->getProcessor();
$this->commentList = CommentHandler::getInstance()->getCommentList($this->commentManager, $this->commentObjectTypeID, $this->queueID);
+
+ // update queue visit
+ if ($this->queue->isNew()) {
+ $action = new ModerationQueueAction(array($this->queue->getDecoratedObject()), 'markAsRead', array(
+ 'visitTime' => TIME_NOW
+ ));
+ $action->executeAction();
+ }
}
/**
use wcf\system\database\util\PreparedStatementConditionBuilder;
use wcf\system\exception\SystemException;
use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\visitTracker\VisitTracker;
use wcf\system\SingletonFactory;
use wcf\system\WCF;
// cache does not exist or is outdated
if ($count === null) {
// force update of non-tracked queues for this user
- $queueList = new ModerationQueueList();
- $queueList->sqlJoins = "LEFT JOIN wcf".WCF_N."_moderation_queue_to_user moderation_queue_to_user ON (moderation_queue_to_user.queueID = moderation_queue.queueID AND moderation_queue_to_user.userID = ".WCF::getUser()->userID.")";
- $queueList->getConditionBuilder()->add("moderation_queue_to_user.queueID IS NULL");
- $queueList->readObjects();
-
- if (count($queueList)) {
- $queues = array();
- foreach ($queueList as $queue) {
- if (!isset($queues[$queue->objectTypeID])) {
- $queues[$queue->objectTypeID] = array();
- }
-
- $queues[$queue->objectTypeID][$queue->queueID] = $queue;
- }
-
- foreach ($this->objectTypeNames as $definitionName => $objectTypeIDs) {
- foreach ($objectTypeIDs as $objectTypeID) {
- if (isset($queues[$objectTypeID])) {
- $this->moderationTypes[$definitionName]->getProcessor()->assignQueues($objectTypeID, $queues[$objectTypeID]);
- }
- }
- }
- }
+ $this->forceUserAssignment();
// count outstanding and assigned queues
$conditions = new PreparedStatementConditionBuilder();
return $count;
}
+ /**
+ * Returns the count of unread moderation queue items.
+ *
+ * @return integer
+ */
+ public function getUnreadModerationCount() {
+ // get count
+ $count = UserStorageHandler::getInstance()->getField('unreadModerationCount');
+
+ // cache does not exist or is outdated
+ if ($count === null) {
+ // force update of non-tracked queues for this user
+ $this->forceUserAssignment();
+
+ // count outstanding and assigned queues
+ $conditions = new PreparedStatementConditionBuilder();
+ $conditions->add("moderation_queue_to_user.userID = ?", array(WCF::getUser()->userID));
+ $conditions->add("moderation_queue_to_user.isAffected = ?", array(1));
+ $conditions->add("moderation_queue.status IN (?)", array(array(ModerationQueue::STATUS_OUTSTANDING, ModerationQueue::STATUS_PROCESSING)));
+ $conditions->add("moderation_queue.time > ?", array(VisitTracker::getInstance()->getVisitTime('com.woltlab.wcf.moderation.queue')));
+ $conditions->add("(moderation_queue.time > tracked_visit.visitTime OR tracked_visit.visitTime IS NULL)");
+
+ $sql = "SELECT COUNT(*) AS count
+ FROM wcf".WCF_N."_moderation_queue_to_user moderation_queue_to_user
+ LEFT JOIN wcf".WCF_N."_moderation_queue moderation_queue
+ ON (moderation_queue.queueID = moderation_queue_to_user.queueID)
+ LEFT JOIN wcf".WCF_N."_tracked_visit tracked_visit
+ ON (tracked_visit.objectTypeID = ".VisitTracker::getInstance()->getObjectTypeID('com.woltlab.wcf.moderation.queue')." AND tracked_visit.objectID = moderation_queue.queueID AND tracked_visit.userID = ".WCF::getUser()->userID.")
+ ".$conditions;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditions->getParameters());
+ $row = $statement->fetchArray();
+ $count = $row['count'];
+
+ // update storage data
+ UserStorageHandler::getInstance()->update(WCF::getUser()->userID, 'unreadModerationCount', $count);
+ }
+
+ return $count;
+ }
+
+ /**
+ * Forces the update of non-tracked queues for this user.
+ */
+ protected function forceUserAssignment() {
+ $queueList = new ModerationQueueList();
+ $queueList->sqlJoins = "LEFT JOIN wcf".WCF_N."_moderation_queue_to_user moderation_queue_to_user ON (moderation_queue_to_user.queueID = moderation_queue.queueID AND moderation_queue_to_user.userID = ".WCF::getUser()->userID.")";
+ $queueList->getConditionBuilder()->add("moderation_queue_to_user.queueID IS NULL");
+ $queueList->readObjects();
+
+ if (count($queueList)) {
+ $queues = array();
+ foreach ($queueList as $queue) {
+ if (!isset($queues[$queue->objectTypeID])) {
+ $queues[$queue->objectTypeID] = array();
+ }
+
+ $queues[$queue->objectTypeID][$queue->queueID] = $queue;
+ }
+
+ foreach ($this->objectTypeNames as $definitionName => $objectTypeIDs) {
+ foreach ($objectTypeIDs as $objectTypeID) {
+ if (isset($queues[$objectTypeID])) {
+ $this->moderationTypes[$definitionName]->getProcessor()->assignQueues($objectTypeID, $queues[$objectTypeID]);
+ }
+ }
+ }
+ }
+ }
+
/**
* Saves moderation queue assignments.
*
public function resetModerationCount($userID = null) {
if ($userID === null) {
UserStorageHandler::getInstance()->resetAll('outstandingModerationCount');
+ UserStorageHandler::getInstance()->resetAll('unreadModerationCount');
}
else {
UserStorageHandler::getInstance()->reset(array($userID), 'outstandingModerationCount');
+ UserStorageHandler::getInstance()->reset(array($userID), 'unreadModerationCount');
}
}
<item name="wcf.moderation.comments"><![CDATA[Kommentare]]></item>
<item name="wcf.moderation.comments.description"><![CDATA[Diese Kommentare sind intern und nur für Moderatoren einsehbar]]></item>
<item name="wcf.moderation.jumpToContent"><![CDATA[Zum Inhalt gehen]]></item>
+ <item name="wcf.moderation.markAllAsRead"><![CDATA[Alle Einträge als gelesen markieren]]></item>
+ <item name="wcf.moderation.markAsRead.doubleClick"><![CDATA[Eintrag durch Doppelklick als gelesen markieren]]></item>
</category>
<category name="wcf.moderation.activation">
<item name="wcf.moderation.comments"><![CDATA[Comments]]></item>
<item name="wcf.moderation.comments.description"><![CDATA[All comments are internal and will not be exposed to non-moderators]]></item>
<item name="wcf.moderation.jumpToContent"><![CDATA[Go to Related Content]]></item>
+ <item name="wcf.moderation.markAllAsRead"><![CDATA[Mark All Items Read]]></item>
+ <item name="wcf.moderation.markAsRead.doubleClick"><![CDATA[Double-Click to Mark This Item Read]]></item>
</category>
<category name="wcf.moderation.activation">