1
0
mirror of https://github.com/wbbaddons/Tims-Chat.git synced 2024-10-31 14:10:08 +00:00

Merge branch 'master' into protocol

Conflicts:
	acpMenu.xml
This commit is contained in:
Tim Düsterhus 2013-07-24 21:55:37 +02:00
commit db8fa89e7b
42 changed files with 1452 additions and 617 deletions

View File

@ -1,5 +1,6 @@
language: php language: php
php: php:
- 5.5
- 5.4 - 5.4
- 5.3 - 5.3
before_install: before_install:

View File

@ -23,6 +23,13 @@
<showorder>2</showorder> <showorder>2</showorder>
</acpmenuitem> </acpmenuitem>
<acpmenuitem name="chat.acp.menu.link.suspension.list">
<controller><![CDATA[chat\acp\page\ChatSuspensionListPage]]></controller>
<parent>chat.acp.menu.link.chat</parent>
<permissions>admin.chat.canManageSuspensions</permissions>
<showorder>3</showorder>
</acpmenuitem>
<acpmenuitem name="chat.acp.menu.link.log"> <acpmenuitem name="chat.acp.menu.link.log">
<controller><![CDATA[chat\acp\page\MessageLogListPage]]></controller> <controller><![CDATA[chat\acp\page\MessageLogListPage]]></controller>
<parent>chat.acp.menu.link.chat</parent> <parent>chat.acp.menu.link.chat</parent>

View File

@ -0,0 +1,167 @@
{include file='header' pageTitle='chat.acp.suspension.list'}
<script>
//<![CDATA[
$(function() {
new WCF.Search.User('#username', null, false, [ ], false);
new WCF.Search.User('#issuerUsername', null, false, [ ], false);
var proxy = new WCF.Action.Proxy({
success: function (data, textStatus, jqXHR) {
$('.jsSuspensionRow').each(function(index, row) {
var row = $(row);
if (WCF.inArray(row.data('objectID'), data.objectIDs)) {
row.find('.jsRevokeButton').addClass('disabled').removeClass('pointer').off('click');
(new WCF.System.Notification('{"chat.acp.suspension.revoke.success"|language|encodeJS}')).show();
}
});
}
});
$('.jsRevokeButton:not(.disabled)').click(function () {
var objectID = $(this).parents('.jsSuspensionRow').data('objectID');
WCF.System.Confirmation.show($(this).data('confirmMessage'), $.proxy(function (action) {
if (action === 'confirm') {
proxy.setOption('data', {
actionName: 'revoke',
className: '\\chat\\data\\suspension\\SuspensionAction',
objectIDs: [ objectID ]
});
proxy.sendRequest();
}
}, this));
});
});
//]]>
</script>
<header class="boxHeadline">
<h1>{lang}chat.acp.suspension.list{/lang}</h1>
</header>
<form method="post" action="{link controller='ChatSuspensionList' application='chat'}{/link}">
<div class="container containerPadding marginTop">
<fieldset>
<legend>{lang}wcf.global.filter{/lang}</legend>
<dl>
<dd>
<label><input type="checkbox" id="displayRevoked" name="displayRevoked" value="1"{if $displayRevoked} checked="checked"{/if} /> {lang}chat.acp.suspension.displayRevoked{/lang}</label>
</dd>
</dl>
<dl>
<dt><label for="username">{lang}wcf.user.username{/lang}</label></dt>
<dd>
<input type="text" id="username" name="username" class="medium" value="{$username}" />
</dd>
</dl>
<dl>
<dt><label for="issuerUsername">{lang}chat.acp.suspension.issuer{/lang}</label></dt>
<dd>
<input type="text" id="issuerUsername" name="issuerUsername" class="medium" value="{$issuerUsername}" />
</dd>
</dl>
<dl>
<dt><label for="roomID">{lang}chat.general.room{/lang}</label></dt>
<dd>
<select id="roomID" name="roomID">
<option value="-1"{if $roomID == -1} selected="selected"{/if}></option>
<option value="0"{if $roomID == 0} selected="selected"{/if}>{lang}chat.room.global{/lang}</option>
<option value="" disabled="disabled">-------------</option>
{foreach from=$availableRooms key=id item=room}
<option value="{$id}" {if $roomID == $id}selected="selected"{/if}>{$room}</option>
{/foreach}
</select>
</dd>
</dl>
<dl>
<dt><label for="suspensionType">{lang}chat.acp.suspension.type{/lang}</label></dt>
<dd>
<select id="suspensionType" name="suspensionType">
<option value=""{if $suspensionType == null} selected="selected"{/if}></option>
<option value="{'\chat\data\suspension\Suspension::TYPE_MUTE'|constant}"{if $suspensionType == '\chat\data\suspension\Suspension::TYPE_MUTE'|constant} selected="selected"{/if}>{lang}chat.suspension.{'\chat\data\suspension\Suspension::TYPE_MUTE'|constant}{/lang}</option>
<option value="{'\chat\data\suspension\Suspension::TYPE_BAN'|constant}"{if $suspensionType == '\chat\data\suspension\Suspension::TYPE_BAN'|constant} selected="selected"{/if}>{lang}chat.suspension.{'\chat\data\suspension\Suspension::TYPE_BAN'|constant}{/lang}</option>
</select>
</dd>
</dl>
</fieldset>
</div>
<div class="formSubmit">
<input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
</div>
</form>
{if $objects|count}
{capture assign=additionalParameters}{*
*}{if $userID}&userID={$userID}{/if}{*
*}{if $issuerUserID}&issuerUserID={$issuerUserID}{/if}{*
*}{if $roomID}&roomID={$roomID}{/if}{*
*}{if $suspensionType}&suspensionType={$suspensionType}{/if}{*
*}{if $displayRevoked}&displayRevoked={$displayRevoked}{/if}{*
*}{/capture}
<div class="contentNavigation">
{pages print=true assign=pagesLinks application="chat" controller="ChatSuspensionList" link="pageNo=%d$additionalParameters"}
</div>
<div class="tabularBox tabularBoxTitle marginTop">
<header>
<h2>{lang}chat.acp.suspension.list{/lang} <span class="badge badgeInverse">{#$items}</span></h2>
</header>
<table class="table">
<thead>
<tr>
<th class="columnID{if $sortField == 'suspensionID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link application='chat' controller='ChatSuspensionList'}pageNo={@$pageNo}&sortField=suspensionID&sortOrder={if $sortField == 'suspensionID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$additionalParameters}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
<th class="columnUsername{if $sortField == 'username'} active {@$sortOrder}{/if}"><a href="{link application='chat' controller='ChatSuspensionList'}pageNo={@$pageNo}&sortField=username&sortOrder={if $sortField == 'username' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$additionalParameters}{/link}">{lang}wcf.user.username{/lang}</a></th>
<th class="columnRoomID{if $sortField == 'roomID'} active {@$sortOrder}{/if}"><a href="{link application='chat' controller='ChatSuspensionList'}pageNo={@$pageNo}&sortField=roomID&sortOrder={if $sortField == 'roomID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$additionalParameters}{/link}">{lang}chat.general.room{/lang}</a></th>
<th class="columnSuspensionType{if $sortField == 'type'} active {@$sortOrder}{/if}"><a href="{link application='chat' controller='ChatSuspensionList'}pageNo={@$pageNo}&sortField=type&sortOrder={if $sortField == 'type' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$additionalParameters}{/link}">{lang}chat.acp.suspension.type{/lang}</a></th>
<th class="columnTime{if $sortField == 'time'} active {@$sortOrder}{/if}"><a href="{link application='chat' controller='ChatSuspensionList'}pageNo={@$pageNo}&sortField=time&sortOrder={if $sortField == 'time' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$additionalParameters}{/link}">{lang}chat.general.time{/lang}</a></th>
<th class="columnExpires{if $sortField == 'expires'} active {@$sortOrder}{/if}"><a href="{link application='chat' controller='ChatSuspensionList'}pageNo={@$pageNo}&sortField=expires&sortOrder={if $sortField == 'expires' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$additionalParameters}{/link}">{lang}chat.general.expires{/lang}</a></th>
<th class="columnIssuer{if $sortField == 'issuer'} active {@$sortOrder}{/if}"><a href="{link application='chat' controller='ChatSuspensionList'}pageNo={@$pageNo}&sortField=issuer&sortOrder={if $sortField == 'issuer' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$additionalParameters}{/link}">{lang}chat.acp.suspension.issuer{/lang}</a></th>
<th class="columnMessage{if $sortField == 'reason'} active {@$sortOrder}{/if}"><a href="{link application='chat' controller='ChatSuspensionList'}pageNo={@$pageNo}&sortField=reason&sortOrder={if $sortField == 'reason' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$additionalParameters}{/link}">{lang}chat.acp.suspension.reason{/lang}</a></th>
{event name='columnHeads'}
</tr>
</thead>
<tbody>
{foreach from=$objects item=$suspension}
<tr class="jsSuspensionRow" data-object-id="{$suspension->suspensionID}">
<td class="columnIcon">
<span class="icon icon16 icon-undo{if $suspension->expires <= TIME_NOW} disabled{else} pointer{/if} jsRevokeButton" title="{lang}chat.acp.suspension.revoked{/lang}" data-confirm-message="{lang}chat.acp.suspension.revoke.sure{/lang}"></span>
{event name='rowButtons'}
</td>
<td id="columnID">{#$suspension->suspensionID}</td>
<td id="columnUsername"><a href="{link application='chat' controller='ChatSuspensionList'}userID={$suspension->userID}{/link}">{$suspension->username}</a></td>
<td id="columnRoomID"><a href="{link application='chat' controller='ChatSuspensionList'}roomID={if $suspension->roomID}{$suspension->roomID}{else}0{/if}{/link}">{if $suspension->roomID}{$suspension->roomTitle|language}{else}{lang}chat.room.global{/lang}{/if}</a></td>
<td id="columnSuspensionType"><a href="{link application='chat' controller='ChatSuspensionList'}suspensionType={$suspension->type}{/link}">{lang}chat.suspension.{@$suspension->type}{/lang}</a></td>
<td id="columnTime">{$suspension->time|plainTime}</td>
<td id="columnExpires">
<p>{$suspension->expires|plainTime}{if $suspension->expires > TIME_NOW} ({$suspension->expires|dateDiff}){/if}</p>
{if $suspension->revoker && $suspension->expires <= TIME_NOW}<p><small>{lang}chat.acp.suspension.revokedBy{/lang}</small></p>{/if}
</td>
<td id="columnIssuer"><a href="{link application='chat' controller='ChatSuspensionList'}issuerUserID={$suspension->issuer}{/link}">{$suspension->issuerUsername}</a></td>
<td id="columnMessage"{if $suspension->reason != $suspension->reason|truncate:30} class="jsTooltip" title="{$suspension->reason}"{/if}>{$suspension->reason|truncate:30}</a></td>
{event name='columns'}
</tr>
{/foreach}
</tbody>
</table>
</div>
<div class="contentNavigation">
{@$pagesLinks}
</div>
{else}
<p class="info">{lang}wcf.global.noItems{/lang}</p>
{/if}
{include file='footer'}

View File

@ -1,7 +1,7 @@
{include file='header' pageTitle='chat.acp.room.'|concat:$action} {include file='header' pageTitle='chat.acp.room.'|concat:$action}
<script type="text/javascript" src="{@$__wcf->getPath('wcf')}js/WCF.ACL.js"></script> <script src="{@$__wcf->getPath('wcf')}js/WCF.ACL.js"></script>
<script type="text/javascript"> <script>
//<![CDATA[ //<![CDATA[
$(function() { $(function() {
new WCF.ACL.List($('#groupPermissions'), {@$objectTypeID}, ''{if $roomID|isset}, {@$roomID}{/if}); new WCF.ACL.List($('#groupPermissions'), {@$objectTypeID}, ''{if $roomID|isset}, {@$roomID}{/if});

View File

@ -1,6 +1,6 @@
{include file='header' pageTitle='chat.acp.room.list'} {include file='header' pageTitle='chat.acp.room.list'}
<script type="text/javascript"> <script>
//<![CDATA[ //<![CDATA[
$(function() { $(function() {
new WCF.Action.Delete('\\chat\\data\\room\\RoomAction', $('.chatRoomRow')); new WCF.Action.Delete('\\chat\\data\\room\\RoomAction', $('.chatRoomRow'));

View File

@ -33,7 +33,16 @@ exposed by a function if necessary.
newMessageCount = 0 newMessageCount = 0
scrollUpNotifications = off scrollUpNotifications = off
chatSession = Date.now() chatSession = Date.now()
userList =
current: {}
allTime: {}
currentRoom = {}
errorVisible = false errorVisible = false
inputErrorHidingTimer = null
lastMessage = null
openChannel = 0
remainingFailures = 3 remainingFailures = 3
@ -106,21 +115,29 @@ Make the user leave the chat when **Tims Chat** is about to be unloaded.
Insert the appropriate smiley code into the input when a smiley is clicked. Insert the appropriate smiley code into the input when a smiley is clicked.
$('#smilies').on 'click', 'img', -> $('#smilies').on 'click', 'img', ->
insertText ' ' + $(@).attr('alt') + ' ' insertText " #{$(@).attr('alt')} "
Handle private channel menu
$('#privateChannelsMenu').on 'click', '.privateChannel', ->
openPrivateChannel $(@).data 'privateChannelID'
Handle submitting the form. The message will be validated by some basic checks, passed to the `submit` eventlisteners Handle submitting the form. The message will be validated by some basic checks, passed to the `submit` eventlisteners
and afterwards sent to the server by an AJAX request. and afterwards sent to the server by an AJAX request.
$('#timsChatForm').submit (event) -> $('#timsChatForm').submit (event) ->
event.preventDefault() do event.preventDefault
text = $('#timsChatInput').val().trim() text = do $('#timsChatInput').val().trim
$('#timsChatInput').val('').focus().keyup() $('#timsChatInput').val('').focus().keyup()
return false if text.length is 0 return false if text.length is 0
unless openChannel is 0
text = "/whisper #{userList.allTime[openChannel].username}, #{text}"
# Free the fish! # Free the fish!
freeTheFish() if text.toLowerCase() is '/free the fish' do freeTheFish if text.toLowerCase() is '/free the fish'
text = do (text) -> text = do (text) ->
obj = obj =
@ -139,16 +156,13 @@ and afterwards sent to the server by an AJAX request.
enableSmilies: $('#timsChatSmilies').data 'status' enableSmilies: $('#timsChatSmilies').data 'status'
showLoadingOverlay: false showLoadingOverlay: false
success: -> success: ->
$('#timsChatInputContainer').removeClass('formError').find('.innerError').hide() do hideInputError
getMessages()
do getMessages
failure: (data) -> failure: (data) ->
return true unless (data?.returnValues?.errorType?) or (data?.message?) return true unless (data?.returnValues?.errorType?) or (data?.message?)
$('#timsChatInputContainer').addClass('formError').find('.innerError').show().html (data?.returnValues?.errorType) ? data.message showInputError (data?.returnValues?.errorType) ? data.message
setTimeout ->
$('#timsChatInputContainer').removeClass('formError').find('.innerError').hide()
, 5e3
false false
@ -157,11 +171,11 @@ The the word the caret is in will be passed to `autocomplete` and replaced if a
$('#timsChatInput').keydown (event) -> $('#timsChatInput').keydown (event) ->
if event.keyCode is $.ui.keyCode.TAB if event.keyCode is $.ui.keyCode.TAB
input = $(event.currentTarget) do event.preventDefault
event.preventDefault() input = $ @
autocomplete.value ?= input.val() autocomplete.value ?= do input.val
autocomplete.caret ?= input.getCaret() autocomplete.caret ?= do input.getCaret
beforeCaret = autocomplete.value.substring 0, autocomplete.caret beforeCaret = autocomplete.value.substring 0, autocomplete.caret
lastSpace = beforeCaret.lastIndexOf ' ' lastSpace = beforeCaret.lastIndexOf ' '
@ -177,7 +191,7 @@ The the word the caret is in will be passed to `autocomplete` and replaced if a
return if toComplete.length is 0 return if toComplete.length is 0
console.log "Autocompleting '#{toComplete}'" console.log "Autocompleting '#{toComplete}'"
if beforeComplete is '' and toComplete.substring(0, 1) is '/' if beforeComplete is '' and (toComplete.substring 0, 1) is '/'
regex = new RegExp "^#{WCF.String.escapeRegExp toComplete.substring 1}", "i" regex = new RegExp "^#{WCF.String.escapeRegExp toComplete.substring 1}", "i"
# TODO: Proper command list # TODO: Proper command list
commands = (command for command in v.config.installedCommands when regex.test command) commands = (command for command in v.config.installedCommands when regex.test command)
@ -185,7 +199,10 @@ The the word the caret is in will be passed to `autocomplete` and replaced if a
toComplete = '/' + commands[autocomplete.offset++ % commands.length] + ' ' if commands.length isnt 0 toComplete = '/' + commands[autocomplete.offset++ % commands.length] + ' ' if commands.length isnt 0
else else
regex = new RegExp "^#{WCF.String.escapeRegExp toComplete}", "i" regex = new RegExp "^#{WCF.String.escapeRegExp toComplete}", "i"
users = (username for user in $('.timsChatUser') when regex.test(username = $(user).data('username')))
users = [ ]
for userID, user of userList.current
users.push user.username if regex.test user.username
toComplete = users[autocomplete.offset++ % users.length] + ', ' if users.length isnt 0 toComplete = users[autocomplete.offset++ % users.length] + ', ' if users.length isnt 0
@ -195,7 +212,7 @@ The the word the caret is in will be passed to `autocomplete` and replaced if a
Reset autocompleter to default status, when a key is pressed that is not TAB. Reset autocompleter to default status, when a key is pressed that is not TAB.
else else
$('#timsChatInput').click() do $('#timsChatInput').click
Reset autocompleter to default status, when the input is `click`ed, as the position of the caret may have changed. Reset autocompleter to default status, when the input is `click`ed, as the position of the caret may have changed.
@ -208,14 +225,14 @@ Reset autocompleter to default status, when the input is `click`ed, as the posit
Refresh the room list when the associated button is `click`ed. Refresh the room list when the associated button is `click`ed.
$('#timsChatRoomList button').click -> $('#timsChatRoomList button').click ->
refreshRoomList() do refreshRoomList
Clear the chat by removing every single message once the clear button is `clicked`. Clear the chat by removing every single message once the clear button is `clicked`.
$('#timsChatClear').click (event) -> $('#timsChatClear').click (event) ->
event.preventDefault() do event.preventDefault
$('.timsChatMessage').remove() do $('.timsChatMessageContainer.active .timsChatMessage').remove
$('#timsChatMessageContainer').scrollTop $('#timsChatMessageContainer').prop('scrollHeight') $('.timsChatMessageContainer.active').scrollTop $('.timsChatMessageContainer.active').prop 'scrollHeight'
Handle toggling of the toggleable buttons. Handle toggling of the toggleable buttons.
@ -230,7 +247,7 @@ Handle toggling of the toggleable buttons.
element.addClass 'active' element.addClass 'active'
element.attr 'title', element.data 'disableMessage' element.attr 'title', element.data 'disableMessage'
$('#timsChatInput').focus() do $('#timsChatInput').focus
Mark smilies as disabled when they are disabled. Mark smilies as disabled when they are disabled.
@ -251,7 +268,7 @@ Toggle fullscreen mode.
else else
$('html').removeClass 'fullscreen' $('html').removeClass 'fullscreen'
Toggle checkboxes Toggle checkboxes.
$('#timsChatMark').click (event) -> $('#timsChatMark').click (event) ->
if $(@).data 'status' if $(@).data 'status'
@ -259,6 +276,14 @@ Toggle checkboxes
else else
$('.timsChatMessageContainer').removeClass 'markEnabled' $('.timsChatMessageContainer').removeClass 'markEnabled'
Hide topic container.
$('.jsTopicCloser').on 'click', ->
if $('.timsChatMessageContainer.active').data('userID') is 0
$('#timsChatTopic').addClass 'hidden'
else
closePrivateChannel $('.timsChatMessageContainer.active').data('userID')
Visibly mark the message once the associated checkbox is checked. Visibly mark the message once the associated checkbox is checked.
$(document).on 'click', '.timsChatMessage :checkbox', (event) -> $(document).on 'click', '.timsChatMessage :checkbox', (event) ->
@ -271,9 +296,9 @@ Scroll down when autoscroll is being activated.
$('#timsChatAutoscroll').click (event) -> $('#timsChatAutoscroll').click (event) ->
if $('#timsChatAutoscroll').data 'status' if $('#timsChatAutoscroll').data 'status'
$('#timsChatMessageContainer').scrollTop $('#timsChatMessageContainer').prop('scrollHeight') $('.timsChatMessageContainer.active').scrollTop $('.timsChatMessageContainer.active').prop 'scrollHeight'
$('#timsChatMessageContainer').on 'scroll', (event) -> $('.timsChatMessageContainer.active').on 'scroll', (event) ->
element = $ @ element = $ @
scrollTop = element.scrollTop() scrollTop = element.scrollTop()
scrollHeight = element.prop 'scrollHeight' scrollHeight = element.prop 'scrollHeight'
@ -282,13 +307,13 @@ Scroll down when autoscroll is being activated.
if scrollTop < scrollHeight - height - 25 if scrollTop < scrollHeight - height - 25
if $('#timsChatAutoscroll').data('status') is 1 if $('#timsChatAutoscroll').data('status') is 1
scrollUpNotifications = on scrollUpNotifications = on
$('#timsChatAutoscroll').click() do $('#timsChatAutoscroll').click
if scrollTop > scrollHeight - height - 10 if scrollTop > scrollHeight - height - 10
if $('#timsChatAutoscroll').data('status') is 0 if $('#timsChatAutoscroll').data('status') is 0
scrollUpNotifications = off scrollUpNotifications = off
$(@).removeClass 'notification' $(@).removeClass 'notification'
$('#timsChatAutoscroll').click() do $('#timsChatAutoscroll').click
Enable duplicate tab detection. Enable duplicate tab detection.
@ -321,11 +346,11 @@ load messages if the appropriate event arrives.
do -> do ->
be.bastelstu.wcf.nodePush.onConnect -> be.bastelstu.wcf.nodePush.onConnect ->
console.log 'Disabling periodic loading' console.log 'Disabling periodic loading'
pe.getMessages.stop() do pe.getMessages.stop
be.bastelstu.wcf.nodePush.onDisconnect -> be.bastelstu.wcf.nodePush.onDisconnect ->
console.log 'Enabling periodic loading' console.log 'Enabling periodic loading'
getMessages() do getMessages
pe.getMessages = new WCF.PeriodicalExecuter getMessages, v.config.reloadTime * 1e3 pe.getMessages = new WCF.PeriodicalExecuter getMessages, v.config.reloadTime * 1e3
be.bastelstu.wcf.nodePush.onMessage 'be.bastelstu.chat.newMessage', getMessages be.bastelstu.wcf.nodePush.onMessage 'be.bastelstu.chat.newMessage', getMessages
@ -334,18 +359,37 @@ load messages if the appropriate event arrives.
Finished! Enable the input now and join the chat. Finished! Enable the input now and join the chat.
join roomID join roomID
$('#timsChatInput').enable().jCounter().focus(); do $('#timsChatInput').enable().jCounter().focus
console.log "Finished initializing" console.log "Finished initializing"
true true
Shows an error message below the input.
showInputError = (message) ->
$('#timsChatInputContainer').addClass('formError').find('.innerError').show().html message
clearTimeout inputErrorHidingTimer if inputErrorHidingTimer?
inputErrorHidingTimer = setTimeout ->
do hideInputError
, 5e3
Hides the error message below the input.
hideInputError = ->
clearTimeout inputErrorHidingTimer if inputErrorHidingTimer?
inputErrorHidingTimer = null
do $('#timsChatInputContainer').removeClass('formError').find('.innerError').hide
Free the fish. Free the fish.
freeTheFish = -> freeTheFish = ->
return if $.wcfIsset 'fish' return if $.wcfIsset 'fish'
console.warn 'Freeing the fish' console.warn 'Freeing the fish'
fish = $ """<div id="fish">#{WCF.String.escapeHTML('><((((\u00B0>')}</div>""" fish = $ """<div id="fish">#{WCF.String.escapeHTML('><((((\u00B0>')}</div>"""
fish.css fish.css
position: 'absolute' position: 'absolute'
top: '150px' top: '150px'
@ -389,7 +433,7 @@ Fetch new messages from the server and pass them to `handleMessages`. The userli
error: -> error: ->
console.error "Message loading failed, #{--remainingFailures} remaining" console.error "Message loading failed, #{--remainingFailures} remaining"
if remainingFailures <= 0 if remainingFailures <= 0
freeTheFish() do freeTheFish
console.error 'To many failures, aborting' console.error 'To many failures, aborting'
showError WCF.Language.get 'chat.error.onMessageLoad' showError WCF.Language.get 'chat.error.onMessageLoad'
@ -406,12 +450,24 @@ Prevent loading messages in parallel.
Insert the given messages into the chat stream. Insert the given messages into the chat stream.
handleMessages = (messages) -> handleMessages = (messages) ->
$('#timsChatMessageContainer').trigger 'scroll' $('.timsChatMessageContainer.active').trigger 'scroll'
for message in messages for message in messages
message.isInPrivateChannel = (String(message.type) is v.config.messageTypes.WHISPER) and ($.wcfIsset("timsChatMessageContainer#{message.receiver}") or $.wcfIsset("timsChatMessageContainer#{message.sender}"))
events.newMessage.fire message events.newMessage.fire message
output = v.messageTemplate.fetch message createNewMessage = yes
if $('.timsChatMessage:last-child .text').is('ul') and lastMessage isnt null and lastMessage.type in [ 0, 7 ]
if lastMessage.type is message.type and lastMessage.sender is message.sender and lastMessage.receiver is message.receiver and lastMessage.isInPrivateChannel is message.isInPrivateChannel
createNewMessage = no
if createNewMessage
message.isFollowUp = no
output = v.messageTemplate.fetch
message: message
messageTypes: v.config.messageTypes
li = $ '<li></li>' li = $ '<li></li>'
li.addClass 'timsChatMessage' li.addClass 'timsChatMessage'
li.addClass "timsChatMessage#{message.type}" li.addClass "timsChatMessage#{message.type}"
@ -419,16 +475,40 @@ Insert the given messages into the chat stream.
li.addClass 'ownMessage' if message.sender is WCF.User.userID li.addClass 'ownMessage' if message.sender is WCF.User.userID
li.append output li.append output
li.appendTo $ '#timsChatMessageContainer > ul' if message.isInPrivateChannel and message.sender is WCF.User.userID
li.appendTo $ "#timsChatMessageContainer#{message.receiver} > ul"
else if message.isInPrivateChannel
li.appendTo $ "#timsChatMessageContainer#{message.sender} > ul"
else
li.appendTo $ '#timsChatMessageContainer0 > ul'
else
message.isFollowUp = yes
output = v.messageTemplate.fetch
message: message
messageTypes: v.config.messageTypes
$('#timsChatMessageContainer').scrollTop $('#timsChatMessageContainer').prop('scrollHeight') if $('#timsChatAutoscroll').data('status') is 1 if message.isInPrivateChannel and message.sender is WCF.User.userID
messageContainerID = message.receiver
else if message.isInPrivateChannel
messageContainerID = message.sender
else
messageContainerID = 0
$("#timsChatMessageContainer#{messageContainerID} .timsChatMessage:last-child .text").append $(output).find('.text li:last-child')
lastMessage = message
$('.timsChatMessageContainer.active').scrollTop $('.timsChatMessageContainer.active').prop('scrollHeight') if $('#timsChatAutoscroll').data('status') is 1
Rebuild the userlist based on the given `users`. Rebuild the userlist based on the given `users`.
handleUsers = (users) -> handleUsers = (users) ->
foundUsers = { } foundUsers = { }
userList.current = { }
for user in users for user in users
do (user) ->
userList.current[user.userID] = userList.allTime[user.userID] = user
id = "timsChatUser#{user.userID}" id = "timsChatUser#{user.userID}"
Move the user to the new position if he was found in the old list. Move the user to the new position if he was found in the old list.
@ -471,15 +551,18 @@ Build HTML of the user and insert it into the list, if the users was not found i
li.append v.userTemplate.fetch user li.append v.userTemplate.fetch user
menu = $ '<ul></ul>' menu = $ '<ul></ul>'
menu.addClass 'dropdownMenu' unless user.userID is WCF.User.userID
menu.append $ "<li><a>#{WCF.Language.get('chat.general.query')}</a></li>" menu.append $("<li><a>#{WCF.Language.get('chat.general.query')}</a></li>").click -> openPrivateChannel user.userID
menu.append $ "<li><a>#{WCF.Language.get('chat.general.kick')}</a></li>" menu.append $ "<li><a>#{WCF.Language.get('chat.general.kick')}</a></li>"
menu.append $ "<li><a>#{WCF.Language.get('chat.general.ban')}</a></li>" menu.append $ "<li><a>#{WCF.Language.get('chat.general.ban')}</a></li>"
menu.append $ """<li><a href="#{user.link}">#{WCF.Language.get('chat.general.profile')}</a></li>""" menu.append $ """<li><a href="#{user.link}">#{WCF.Language.get('chat.general.profile')}</a></li>"""
events.userMenu.fire user, menu events.userMenu.fire user, menu
if menu.find('li').length
li.append menu li.append menu
menu.addClass 'dropdownMenu'
li.appendTo $ '#timsChatUserList > ul' li.appendTo $ '#timsChatUserList > ul'
foundUsers[id] = true foundUsers[id] = true
@ -489,7 +572,7 @@ Remove all users that left the chat.
$('.timsChatUser').each -> $('.timsChatUser').each ->
unless foundUsers[$(@).attr('id')]? unless foundUsers[$(@).attr('id')]?
console.log "Removing User: '#{$(@).data('username')}'" console.log "Removing User: '#{$(@).data('username')}'"
$(@).remove(); do $(@).remove
$('#toggleUsers .badge').text $('.timsChatUser').length $('#toggleUsers .badge').text $('.timsChatUser').length
@ -505,18 +588,29 @@ the existing text. If `options.submit` is true the message will be sent to the s
text = $('#timsChatInput').val() + text if options.append text = $('#timsChatInput').val() + text if options.append
$('#timsChatInput').val text $('#timsChatInput').val text
$('#timsChatInput').keyup() do $('#timsChatInput').keyup
if (options.submit) if options.submit
$('#timsChatForm').submit() do $('#timsChatForm').submit
else else
$('#timsChatInput').focus() do $('#timsChatInput').focus
Send out notifications for the given `message`. The number of unread messages will be prepended to `document.title` and if available desktop notifications will be sent. Send out notifications for the given `message`. The number of unread messages will be prepended to `document.title` and if available desktop notifications will be sent.
notify = (message) -> notify = (message) ->
if scrollUpNotifications if scrollUpNotifications
$('#timsChatMessageContainer').addClass 'notification' $('.timsChatMessageContainer.active').addClass 'notification'
if message.isInPrivateChannel
if message.sender is WCF.User.userID
privateChannelID = message.receiver
else
privateChannelID = message.sender
if $('.timsChatMessageContainer.active').data('userID') isnt privateChannelID
$("#privateChannel#{privateChannelID}").addClass 'notify'
else if $('.timsChatMessageContainer.active').data('userID') isnt 0
$("#privateChannel0").addClass 'notify'
return if isActive or $('#timsChatNotify').data('status') is 0 return if isActive or $('#timsChatNotify').data('status') is 0
@ -532,9 +626,9 @@ Send out notifications for the given `message`. The number of unread messages wi
notification = new window.Notification title, notification = new window.Notification title,
body: content body: content
onclick: -> onclick: ->
notification.close() do notification.close
setTimeout -> setTimeout ->
notification.close() do notification.close
, 5e3 , 5e3
Fetch the roomlist from the server and update it in the GUI. Fetch the roomlist from the server and update it in the GUI.
@ -550,7 +644,7 @@ Fetch the roomlist from the server and update it in the GUI.
showLoadingOverlay: false showLoadingOverlay: false
suppressErrors: true suppressErrors: true
success: (data) -> success: (data) ->
$('.timsChatRoom').remove() do $('.timsChatRoom').remove
$('#toggleRooms .badge').text data.returnValues.length $('#toggleRooms .badge').text data.returnValues.length
for room in data.returnValues for room in data.returnValues
@ -561,8 +655,8 @@ Fetch the roomlist from the server and update it in the GUI.
if window.history?.replaceState? if window.history?.replaceState?
$('.timsChatRoom').click (event) -> $('.timsChatRoom').click (event) ->
event.preventDefault() do event.preventDefault
target = $(@) target = $ @
window.history.replaceState {}, '', target.attr 'href' window.history.replaceState {}, '', target.attr 'href'
@ -580,8 +674,8 @@ Shows an unrecoverable error with the given text.
loading = true loading = true
pe.refreshRoomList.stop() do pe.refreshRoomList.stop
pe.getMessages.stop() do pe.getMessages.stop
errorDialog = $(""" errorDialog = $("""
<div id="timsChatLoadingErrorDialog"> <div id="timsChatLoadingErrorDialog">
@ -592,7 +686,7 @@ Shows an unrecoverable error with the given text.
formSubmit = $("""<div class="formSubmit"></div>""").appendTo errorDialog formSubmit = $("""<div class="formSubmit"></div>""").appendTo errorDialog
reloadButton = $("""<button class="buttonPrimary">#{WCF.Language.get 'chat.error.reload'}</button>""").appendTo formSubmit reloadButton = $("""<button class="buttonPrimary">#{WCF.Language.get 'chat.error.reload'}</button>""").appendTo formSubmit
reloadButton.on 'click', -> reloadButton.on 'click', ->
window.location.reload() do window.location.reload
$('#timsChatLoadingErrorDialog').wcfDialog $('#timsChatLoadingErrorDialog').wcfDialog
closable: false closable: false
@ -612,35 +706,116 @@ Joins a room.
success: (data) -> success: (data) ->
loading = false loading = false
$('#timsChatTopic').text data.returnValues.topic $('#timsChatTopic').removeClass 'hidden'
if data.returnValues.topic.trim() is '' currentRoom = data.returnValues
currentRoom.roomID = roomID
$('#timsChatTopic > .topic').text currentRoom.topic
if currentRoom.topic.trim() is ''
$('#timsChatTopic').addClass 'empty' $('#timsChatTopic').addClass 'empty'
else else
$('#timsChatTopic').removeClass 'empty' $('#timsChatTopic').removeClass 'empty'
$('.timsChatMessage').addClass 'unloaded' $('.timsChatMessage').addClass 'unloaded'
document.title = v.titleTemplate.fetch data.returnValues document.title = v.titleTemplate.fetch currentRoom
handleMessages data.returnValues.messages handleMessages currentRoom.messages
getMessages() do getMessages
refreshRoomList() do refreshRoomList
failure: -> failure: ->
showError WCF.Language.get 'chat.error.join' showError WCF.Language.get 'chat.error.join'
Open private channel
openPrivateChannel = (userID) ->
userID = parseInt userID
console.log "Opening private channel #{userID}"
unless $.wcfIsset "timsChatMessageContainer#{userID}"
return unless userList.allTime[userID]?
div = $ '<div>'
div.attr 'id', "timsChatMessageContainer#{userID}"
div.data 'userID', userID
div.addClass 'timsChatMessageContainer'
div.addClass 'marginTop'
div.addClass 'container'
div.wrapInner '<ul>'
$('#timsChatMessageContainer0').after div
$('.privateChannel').removeClass 'active'
if userID isnt 0
$('#timsChatTopic').removeClass 'hidden empty'
$('#timsChatTopic > .topic').text WCF.Language.get 'chat.general.privateChannelTopic', {username: userList.allTime[userID].username}
$('#timsChatTopic > .jsTopicCloser').attr 'title', WCF.Language.get 'chat.general.closePrivateChannel'
unless $.wcfIsset "privateChannel#{userID}"
li = $ '<li>'
li.attr 'id', "privateChannel#{userID}"
li.data 'privateChannelID', userID
li.addClass 'privateChannel'
span = $ '<span class="userAvatar framed" />'
avatar = $ userList.allTime[userID].avatar[16]
avatar.addClass 'jsTooltip'
avatar.attr 'title', userList.allTime[userID].username
avatar.wrap span
li.append avatar.parent().addClass 'small'
avatar = $ userList.allTime[userID].avatar[32]
avatar.addClass 'jsTooltip'
avatar.attr 'title', userList.allTime[userID].username
avatar.wrap span
li.append avatar.parent().addClass 'large'
$('#privateChannelsMenu ul').append li
$('#privateChannelsMenu').addClass 'shown'
else
$('#timsChatTopic > .topic').text currentRoom.topic
$('#timsChatTopic > .jsTopicCloser').attr 'title', WCF.Language.get 'chat.general.closeTopic'
if currentRoom.topic.trim() is ''
$('#timsChatTopic').addClass 'empty'
else
$('#timsChatTopic').removeClass 'empty'
do WCF.DOMNodeInsertedHandler.execute
$('.timsChatMessageContainer').removeClass 'active'
$("#timsChatMessageContainer#{userID}").addClass 'active'
$("#privateChannel#{userID}").addClass('active').removeClass 'notify'
openChannel = userID
Close private channel
closePrivateChannel = (userID) ->
unless userID is 0
do $("#privateChannel#{userID}").remove
do $("#timsChatMessageContainer#{userID}").remove
if $('#privateChannelsMenu li').length <= 1
$('#privateChannelsMenu').removeClass 'shown'
openPrivateChannel 0
Bind the given callback to the given event. Bind the given callback to the given event.
addListener = (event, callback) -> addListener = (event, callback) ->
return false unless events[event]? return false unless events[event]?
events[event].add callback events[event].add callback
true
Remove the given callback from the given event. Remove the given callback from the given event.
removeListener = (event, callback) -> removeListener = (event, callback) ->
return false unless events[event]? return false unless events[event]?
events[event].remove callback events[event].remove callback
true
And finally export the public methods and variables. And finally export the public methods and variables.
Chat = Chat =
@ -650,6 +825,8 @@ And finally export the public methods and variables.
insertText: insertText insertText: insertText
freeTheFish: freeTheFish freeTheFish: freeTheFish
join: join join: join
closePrivateChannel: closePrivateChannel # TODO: REMOVE AFTER DEBUGGING
openPrivateChannel: openPrivateChannel # TODO: REMOVE AFTER DEBUGGING
listener: listener:
add: addListener add: addListener
remove: removeListener remove: removeListener

View File

@ -116,7 +116,7 @@ public function save() {
} }
\wcf\system\acl\ACLHandler::getInstance()->save($roomID, $this->objectTypeID); \wcf\system\acl\ACLHandler::getInstance()->save($roomID, $this->objectTypeID);
\chat\system\permission\permissionHandler::clearCache(); \chat\system\permission\PermissionHandler::clearCache();
$this->saved(); $this->saved();

View File

@ -0,0 +1,172 @@
<?php
namespace chat\acp\page;
use \wcf\system\WCF;
/**
* Lists chat suspensions.
*
* @author Maximilian Mader
* @copyright 2010-2013 Tim Düsterhus
* @license Creative Commons Attribution-NonCommercial-ShareAlike <http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode>
* @package be.bastelstu.chat
* @subpackage acp.page
*/
class ChatSuspensionListPage extends \wcf\page\SortablePage {
/**
* @see \wcf\page\AbstractPage::$activeMenuItem
*/
public $activeMenuItem = 'chat.acp.menu.link.suspension.list';
/**
* @see \wcf\page\AbstractPage::$neededPermissions
*/
public $neededPermissions = array('admin.chat.canManageSuspensions');
/**
* @see \wcf\page\SortablePage::$defaultSortField
*/
public $defaultSortField = 'expires';
/**
* @see \wcf\page\SortablePage::$validSortFields
*/
public $validSortFields = array('suspensionID', 'userID', 'username', 'roomID', 'type', 'expires', 'issuer', 'time', 'reason');
/**
* @see \wcf\page\MultipleLinkPage::$objectListClassName
*/
public $objectListClassName = 'chat\data\suspension\SuspensionList';
/**
* type filter
*
* @var integer
*/
public $filterSuspensionType = null;
/**
* user filter
*
* @var integer
*/
public $filterUserID = null;
/*
* username
*
* @var String
*/
public $filterUsername = null;
/**
* issuer filter
*
* @var integer
*/
public $filterIssuerUserID = null;
/*
* issuer username
*
* @var String
*/
public $filterIssuerUsername = null;
/**
* room filter
*
* @var integer
*/
public $filterRoomID = null;
/**
* display revoked suspensions
*
* @var integer
*/
public $displayRevoked = 0;
/**
* @see \wcf\page\IPage::readParameters()
*/
public function readParameters() {
parent::readParameters();
// get usernames
if (isset($_REQUEST['username']) && !empty($_REQUEST['username'])) $this->filterUsername = \wcf\util\StringUtil::trim($_REQUEST['username']);
if (isset($_REQUEST['issuerUsername']) && !empty($_REQUEST['issuerUsername'])) $this->filterIssuerUsername = \wcf\util\StringUtil::trim($_REQUEST['issuerUsername']);
// get user IDs by username
if ($this->filterUsername != null) $this->filterUserID = \wcf\data\user\UserProfile::getUserProfileByUsername($this->filterUsername)->userID;
if ($this->filterIssuerUsername != null) $this->filterIssuerUserID = \wcf\data\user\UserProfile::getUserProfileByUsername($this->filterIssuerUsername)->userID;
// get user IDs by request if no username was sent
if ($this->filterUserID === null && isset($_REQUEST['userID']) && !empty($_REQUEST['userID'])) $this->filterUserID = intval($_REQUEST['userID']);
if ($this->filterIssuerUserID === null && isset($_REQUEST['issuerUserID']) && !empty($_REQUEST['issuerUserID'])) $this->filterIssuerUserID = intval($_REQUEST['issuerUserID']);
// get usernames by ID if no usernames were sent
if ($this->filterUsername === null) $this->filterUsername = \wcf\data\user\UserProfile::getUserProfile($this->filterUserID);
if ($this->filterIssuerUsername === null) $this->filterIssuerUsername = \wcf\data\user\UserProfile::getUserProfile($this->filterIssuerUserID);
// get room IDs by request
if (isset($_REQUEST['roomID']) && $_REQUEST['roomID'] != -1) $this->filterRoomID = intval($_REQUEST['roomID']);
if (isset($_REQUEST['suspensionType']) && !empty($_REQUEST['suspensionType'])) $this->filterSuspensionType = intval($_REQUEST['suspensionType']);
// display revoked
if (isset($_REQUEST['displayRevoked'])) $this->displayRevoked = intval($_REQUEST['displayRevoked']);
}
/**
* @see wcf\page\IPage::assignVariables()
*/
public function assignVariables() {
parent::assignVariables();
WCF::getTPL()->assign(array(
'availableRooms' => \chat\data\room\RoomCache::getInstance()->getRooms(),
'roomID' => ($this->filterRoomID !== null) ? $this->filterRoomID : -1,
'username' => $this->filterUsername,
'issuerUsername' => $this->filterIssuerUsername,
'suspensionType' => $this->filterSuspensionType,
'userID' => $this->filterUserID,
'issuerUserID' => $this->filterIssuerUserID,
'displayRevoked' => $this->displayRevoked
));
}
/**
* @see \wcf\page\MultipleLinkPage::readObjects()
*/
protected function initObjectList() {
parent::initObjectList();
$this->objectList->sqlSelects .= "user_table.username, user_table2.username AS issuerUsername, user_table3.username AS revokerUsername, room_table.title AS roomTitle";
$this->objectList->sqlJoins .= "
LEFT JOIN wcf".WCF_N."_user user_table
ON suspension.userID = user_table.userID
LEFT JOIN wcf".WCF_N."_user user_table2
ON suspension.issuer = user_table2.userID
LEFT JOIN wcf".WCF_N."_user user_table3
ON suspension.issuer = user_table3.userID";
$conditionJoins = " LEFT JOIN chat".WCF_N."_room room_table
ON suspension.roomID = room_table.roomID";
$this->objectList->sqlConditionJoins .= $conditionJoins;
$this->objectList->sqlJoins .= $conditionJoins;
if (!$this->displayRevoked) {
$this->objectList->getConditionBuilder()->add('expires > ?', array(TIME_NOW));
}
$this->objectList->getConditionBuilder()->add('(room_table.permanent = ? OR suspension.roomID IS NULL)', array(1));
if ($this->filterSuspensionType !== null) $this->objectList->getConditionBuilder()->add('suspension.type = ?', array($this->filterSuspensionType));
if ($this->filterUserID !== null) $this->objectList->getConditionBuilder()->add('suspension.userID = ?', array($this->filterUserID));
if ($this->filterIssuerUserID !== null) $this->objectList->getConditionBuilder()->add('suspension.issuer = ?', array($this->filterIssuerUserID));
if ($this->filterRoomID !== null) {
if ($this->filterRoomID === 0) {
$this->objectList->getConditionBuilder()->add('suspension.roomID IS NULL', array());
}
else {
$this->objectList->getConditionBuilder()->add('suspension.roomID = ?', array($this->filterRoomID));
}
}
}
}

View File

@ -24,6 +24,8 @@ class MessageList extends \wcf\data\DatabaseObjectList {
* @return array<\chat\data\message\Message> * @return array<\chat\data\message\Message>
*/ */
public static function getNewestMessages(\chat\data\room\Room $room, $number = CHAT_LASTMESSAGES) { public static function getNewestMessages(\chat\data\room\Room $room, $number = CHAT_LASTMESSAGES) {
if ($number === 0) return array();
$messageList = new static(); $messageList = new static();
$messageList->sqlOrderBy = "message.messageID DESC"; $messageList->sqlOrderBy = "message.messageID DESC";
$messageList->sqlLimit = $number; $messageList->sqlLimit = $number;

View File

@ -39,26 +39,33 @@ public function __toString() {
public function canEnter(\wcf\data\user\User $user = null) { public function canEnter(\wcf\data\user\User $user = null) {
if ($user === null) $user = WCF::getUser(); if ($user === null) $user = WCF::getUser();
if (!$user->userID) return false; if (!$user->userID) return false;
$user = new \wcf\data\user\UserProfile($user);
$ph = new \chat\system\permission\PermissionHandler($user); if ($user->getPermission('admin.chat.canManageSuspensions')) return true;
$suspensions = Suspension::getSuspensionsForUser($user); if ($user->getPermission('mod.chat.canGban')) return true;
$canEnter = $ph->getPermission($this, 'user.canEnter'); $ph = new \chat\system\permission\PermissionHandler($user->getDecoratedObject());
if ($ph->getPermission($this, 'mod.canAlwaysEnter')) return true;
if ($ph->getPermission($this, 'mod.canBan')) return true;
if (!$ph->getPermission($this, 'user.canEnter')) return false;
$suspensions = Suspension::getSuspensionsForUser($user->getDecoratedObject());
// room suspension // room suspension
if ($canEnter && isset($suspensions[$this->roomID][Suspension::TYPE_BAN])) { if (isset($suspensions[$this->roomID][Suspension::TYPE_BAN])) {
if ($suspensions[$this->roomID][Suspension::TYPE_BAN]->isValid()) { if ($suspensions[$this->roomID][Suspension::TYPE_BAN]->isValid()) {
$canEnter = false; return false;
} }
} }
// global suspension // global suspension
if ($canEnter && isset($suspensions[null][Suspension::TYPE_BAN])) { if (isset($suspensions[null][Suspension::TYPE_BAN])) {
if ($suspensions[null][Suspension::TYPE_BAN]->isValid()) { if ($suspensions[null][Suspension::TYPE_BAN]->isValid()) {
$canEnter = false; return false;
} }
} }
return $canEnter || $ph->getPermission($this, 'mod.canAlwaysEnter'); return true;
} }
/** /**
@ -70,26 +77,33 @@ public function canEnter(\wcf\data\user\User $user = null) {
public function canWrite(\wcf\data\user\User $user = null) { public function canWrite(\wcf\data\user\User $user = null) {
if ($user === null) $user = WCF::getUser(); if ($user === null) $user = WCF::getUser();
if (!$user->userID) return false; if (!$user->userID) return false;
$user = new \wcf\data\user\UserProfile($user);
$ph = new \chat\system\permission\PermissionHandler($user); if ($user->getPermission('admin.chat.canManageSuspensions')) return true;
$suspensions = Suspension::getSuspensionsForUser($user); if ($user->getPermission('mod.chat.canGmute')) return true;
$canWrite = $ph->getPermission($this, 'user.canWrite'); $ph = new \chat\system\permission\PermissionHandler($user->getDecoratedObject());
if ($ph->getPermission($this, 'mod.canAlwaysWrite')) return true;
if ($ph->getPermission($this, 'mod.canMute')) return true;
if (!$ph->getPermission($this, 'user.canWrite')) return false;
$suspensions = Suspension::getSuspensionsForUser($user->getDecoratedObject());
// room suspension // room suspension
if ($canWrite && isset($suspensions[$this->roomID][Suspension::TYPE_MUTE])) { if (isset($suspensions[$this->roomID][Suspension::TYPE_MUTE])) {
if ($suspensions[$this->roomID][Suspension::TYPE_MUTE]->isValid()) { if ($suspensions[$this->roomID][Suspension::TYPE_MUTE]->isValid()) {
$canWrite = false; return false;
} }
} }
// global suspension // global suspension
if ($canWrite && isset($suspensions[null][Suspension::TYPE_MUTE])) { if (isset($suspensions[null][Suspension::TYPE_MUTE])) {
if ($suspensions[null][Suspension::TYPE_MUTE]->isValid()) { if ($suspensions[null][Suspension::TYPE_MUTE]->isValid()) {
$canWrite = false; return false;
} }
} }
return $canWrite || $ph->getPermission($this, 'mod.canAlwaysWrite'); return true;
} }
/** /**

View File

@ -221,21 +221,22 @@ public function join() {
$messageAction->executeAction(); $messageAction->executeAction();
} }
$newestMessages = message\ViewableMessageList::getNewestMessages($room, CHAT_LASTMESSAGES); $newestMessages = message\ViewableMessageList::getNewestMessages($room, CHAT_LASTMESSAGES + CHAT_DISPLAY_JOIN_LEAVE);
try { // update last seen message
$lastSeen = end($newestMessages)->messageID; $sql = "SELECT
} MAX(messageID)
catch (\wcf\system\exception\SystemException $e) { FROM
$lastSeen = 0; chat".WCF_N."_message";
} $stmt = WCF::getDB()->prepareStatement($sql);
$stmt->execute();
$editor = new \wcf\data\user\UserEditor($this->parameters['user']); $editor = new \wcf\data\user\UserEditor($this->parameters['user']);
$editor->update(array( $editor->update(array(
'chatRoomID' => $room->roomID, 'chatRoomID' => $room->roomID,
'chatAway' => null, 'chatAway' => null,
'chatLastActivity' => TIME_NOW, 'chatLastActivity' => TIME_NOW,
'chatLastSeen' => $lastSeen 'chatLastSeen' => $stmt->fetchColumn() ?: 0
)); ));
// add activity points // add activity points

View File

@ -22,8 +22,8 @@ class Suspension extends \chat\data\CHATDatabaseObject {
*/ */
protected static $databaseTableIndexName = 'suspensionID'; protected static $databaseTableIndexName = 'suspensionID';
const TYPE_MUTE = 1; const TYPE_MUTE = 'mute';
const TYPE_BAN = 2; const TYPE_BAN = 'ban';
/** /**
* Returns whether the suspension still is valid. * Returns whether the suspension still is valid.
@ -34,6 +34,36 @@ public function isValid() {
return $this->expires > TIME_NOW; return $this->expires > TIME_NOW;
} }
/**
* Returns whether the given user may view this suspension.
*
* @param \wcf\data\user\User $user
* @return boolean
*/
public function isVisible($user = null) {
if ($user === null) $user = WCF::getUser();
$user = new \wcf\data\user\UserProfile($user);
$ph = new \chat\system\permission\PermissionHandler($user->getDecoratedObject());
if ($user->getPermission('admin.chat.canManageSuspensions')) return true;
if ($user->getPermission('mod.chat.canG'.$this->type)) return true;
if (!$this->roomID) return false;
if ($ph->getPermission($this->getRoom(), 'mod.can'.ucfirst($this->type))) return true;
return false;
}
/**
* Returns the room of this suspension.
*
* @return \chat\data\room\Room
*/
public function getRoom() {
if (!$this->roomID) return new \chat\data\room\Room(null, array('roomID' => null));
return \chat\data\room\RoomCache::getInstance()->getRoom($this->roomID);
}
/** /**
* Returns all the suspensions for the specified user (current user if no user was specified). * Returns all the suspensions for the specified user (current user if no user was specified).
* *
@ -53,15 +83,17 @@ public static function getSuspensionsForUser(\wcf\data\user\User $user = null) {
if ($suspensions === false) throw new \wcf\system\exception\SystemException(); if ($suspensions === false) throw new \wcf\system\exception\SystemException();
} }
catch (\wcf\system\exception\SystemException $e) { catch (\wcf\system\exception\SystemException $e) {
$condition = new \wcf\system\database\util\PreparedStatementConditionBuilder();
$condition->add('userID = ?', array($user->userID));
$condition->add('expires > ?', array(TIME_NOW));
$sql = "SELECT $sql = "SELECT
* *
FROM FROM
chat".WCF_N."_suspension chat".WCF_N."_suspension
WHERE ".$condition;
userID = ?
AND expires > ?";
$stmt = WCF::getDB()->prepareStatement($sql); $stmt = WCF::getDB()->prepareStatement($sql);
$stmt->execute(array($user->userID, TIME_NOW)); $stmt->execute($condition->getParameters());
$suspensions = array(); $suspensions = array();
while ($suspension = $stmt->fetchObject('\chat\data\suspension\Suspension')) { while ($suspension = $stmt->fetchObject('\chat\data\suspension\Suspension')) {
@ -76,7 +108,7 @@ public static function getSuspensionsForUser(\wcf\data\user\User $user = null) {
/** /**
* Returns the appropriate suspension for user, room and type. * Returns the appropriate suspension for user, room and type.
* Returns false if no suspension was found. * Returns false if no active suspension was found.
* *
* @param \wcf\data\user\User $user * @param \wcf\data\user\User $user
* @param \chat\data\room\Room $room * @param \chat\data\room\Room $room
@ -84,23 +116,21 @@ public static function getSuspensionsForUser(\wcf\data\user\User $user = null) {
* @return \chat\data\suspension\Suspension * @return \chat\data\suspension\Suspension
*/ */
public static function getSuspensionByUserRoomAndType(\wcf\data\user\User $user, \chat\data\room\Room $room, $type) { public static function getSuspensionByUserRoomAndType(\wcf\data\user\User $user, \chat\data\room\Room $room, $type) {
$condition = new \wcf\system\database\util\PreparedStatementConditionBuilder();
$condition->add('userID = ?', array($user->userID));
$condition->add('type = ?', array($type));
$condition->add('expires > ?', array(TIME_NOW));
if ($room->roomID) $condition->add('roomID = ?', array($room->roomID));
else $condition->add('roomID IS NULL');
$sql = "SELECT $sql = "SELECT
* *
FROM FROM
chat".WCF_N."_suspension chat".WCF_N."_suspension
WHERE ".$condition;
userID = ?
AND type = ?";
$parameter = array($user->userID, $type);
if ($room->roomID) {
$sql .= " AND roomID = ?";
$parameter[] = $room->roomID;
}
else $sql .= " AND roomID IS NULL";
$statement = WCF::getDB()->prepareStatement($sql); $statement = WCF::getDB()->prepareStatement($sql);
$statement->execute($parameter); $statement->execute($condition->getParameters());
$row = $statement->fetchArray(); $row = $statement->fetchArray();
if (!$row) return false; if (!$row) return false;

View File

@ -1,5 +1,6 @@
<?php <?php
namespace chat\data\suspension; namespace chat\data\suspension;
use \wcf\system\WCF;
/** /**
* Executes chat-suspension-related actions. * Executes chat-suspension-related actions.
@ -17,23 +18,28 @@ class SuspensionAction extends \wcf\data\AbstractDatabaseObjectAction {
protected $className = '\chat\data\suspension\SuspensionEditor'; protected $className = '\chat\data\suspension\SuspensionEditor';
/** /**
* Deletes expired suspensions. * Validates permissions and parameters
*
* @return integer Number of deleted suspensions
*/ */
public function prune() { public function validateRevoke() {
$sql = "SELECT WCF::getSession()->checkPermissions((array) 'admin.chat.canManageSuspensions');
".call_user_func(array($this->className, 'getDatabaseTableIndexName'))."
FROM
".call_user_func(array($this->className, 'getDatabaseTableName'))."
WHERE
expires < ?";
$stmt = \wcf\system\WCF::getDB()->prepareStatement($sql);
$stmt->execute(array(TIME_NOW));
$objectIDs = array();
while ($objectID = $stmt->fetchColumn()) $objectIDs[] = $objectID; $this->parameters['revoker'] = WCF::getUser()->userID;
}
return call_user_func(array($this->className, 'deleteAll'), $objectIDs); /**
* Revokes suspensions.
*/
public function revoke() {
if (!isset($this->parameters['revoker'])) {
$this->parameters['revoker'] = null;
}
$objectAction = new self($this->objectIDs, 'update', array(
'data' => array(
'expires' => TIME_NOW,
'revoker' => $this->parameters['revoker']
)
));
$objectAction->executeAction();
} }
} }

View File

@ -0,0 +1,18 @@
<?php
namespace chat\data\suspension;
/**
* Represents a list of chat suspensions.
*
* @author Maximilian Mader
* @copyright 2010-2013 Tim Düsterhus
* @license Creative Commons Attribution-NonCommercial-ShareAlike <http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode>
* @package be.bastelstu.chat
* @subpackage data.suspension
*/
class SuspensionList extends \wcf\data\DatabaseObjectList {
/**
* @see wcf\data\DatabaseObjectList::$className
*/
public $className = 'chat\data\suspension\Suspension';
}

View File

@ -84,11 +84,14 @@ class ChatPage extends \wcf\page\AbstractPage {
public function assignVariables() { public function assignVariables() {
parent::assignVariables(); parent::assignVariables();
$reflection = new \ReflectionClass('\chat\data\message\Message');
WCF::getTPL()->assign(array( WCF::getTPL()->assign(array(
'room' => $this->room, 'room' => $this->room,
'roomID' => $this->roomID, 'roomID' => $this->roomID,
'rooms' => $this->rooms, 'rooms' => $this->rooms,
'commands' => $this->commands, 'commands' => $this->commands,
'messageTypes' => $reflection->getConstants(),
'defaultSmilies' => $this->defaultSmilies, 'defaultSmilies' => $this->defaultSmilies,
'smileyCategories' => $this->smileyCategories, 'smileyCategories' => $this->smileyCategories,
'sidebarCollapsed' => \wcf\system\user\collapsible\content\UserCollapsibleContentHandler::getInstance()->isCollapsed('com.woltlab.wcf.collapsibleSidebar', 'be.bastelstu.chat.ChatPage'), 'sidebarCollapsed' => \wcf\system\user\collapsible\content\UserCollapsibleContentHandler::getInstance()->isCollapsed('com.woltlab.wcf.collapsibleSidebar', 'be.bastelstu.chat.ChatPage'),

View File

@ -106,10 +106,7 @@ public function show() {
parent::show(); parent::show();
@header('Content-type: application/json'); @header('Content-type: application/json');
// enable gzip compression \wcf\util\HeaderUtil::sendNoCacheHeaders();
if (HTTP_ENABLE_GZIP && HTTP_GZIP_LEVEL > 0 && HTTP_GZIP_LEVEL < 10 && !defined('HTTP_DISABLE_GZIP')) {
\wcf\util\HeaderUtil::compressOutput();
}
$json = array('users' => array(), 'messages' => array()); $json = array('users' => array(), 'messages' => array());

View File

@ -0,0 +1,126 @@
<?php
namespace chat\system\command;
use \chat\data\suspension;
use \chat\util\ChatUtil;
use \wcf\data\user\User;
use \wcf\system\WCF;
/**
* Default implementation for suspension commands
*
* @author Tim Düsterhus
* @copyright 2010-2013 Tim Düsterhus
* @license Creative Commons Attribution-NonCommercial-ShareAlike <http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode>
* @package be.bastelstu.chat
* @subpackage system.chat.command
*/
abstract class AbstractSuspensionCommand extends AbstractRestrictedCommand {
public $user = null;
public $expires = 0;
public $suspensionAction = null;
public $link = '';
public $room = null;
public $reason = '';
public function __construct(\chat\system\command\CommandHandler $commandHandler) {
parent::__construct($commandHandler);
try {
$parameters = explode(',', $commandHandler->getParameters(), 3);
list($username, $modifier) = $parameters;
if (isset($parameters[2])) {
$this->reason = \wcf\util\StringUtil::trim($parameters[2]);
}
$modifier = ChatUtil::timeModifier(\wcf\util\StringUtil::trim($modifier));
$expires = strtotime($modifier, TIME_NOW);
$this->expires = min(max(-0x80000000, $expires), 0x7FFFFFFF);
}
catch (\wcf\system\exception\SystemException $e) {
throw new \InvalidArgumentException();
}
$this->user = User::getUserByUsername($username);
if (!$this->user->userID) throw new \chat\system\command\UserNotFoundException($username);
$profile = \wcf\system\request\LinkHandler::getInstance()->getLink('User', array(
'object' => $this->user
));
$this->link = "[url='".$profile."']".$this->user->username.'[/url]';
$this->executeAction();
$this->didInit();
}
public function executeAction() {
if (static::IS_GLOBAL) $room = new \chat\data\room\Room(null, array('roomID' => null));
else $room = $this->room;
if ($suspension = suspension\Suspension::getSuspensionByUserRoomAndType($this->user, $room, static::SUSPENSION_TYPE)) {
if ($suspension->expires >= $this->expires) {
throw new \wcf\system\exception\UserInputException('text', WCF::getLanguage()->get('wcf.chat.suspension.exists'));
}
$action = new suspension\SuspensionAction(array($suspension), 'revoke', array(
'revoker' => WCF::getUser()->userID
));
$action->executeAction();
}
$this->suspensionAction = new suspension\SuspensionAction(array(), 'create', array(
'data' => array(
'userID' => $this->user->userID,
'roomID' => $room->roomID ?: null,
'type' => static::SUSPENSION_TYPE,
'expires' => $this->expires,
'time' => TIME_NOW,
'issuer' => WCF::getUser()->userID,
'reason' => $this->reason
)
));
$this->suspensionAction->executeAction();
}
/**
* @see \chat\system\command\IRestrictedChatCommand::checkPermission()
*/
public function checkPermission() {
parent::checkPermission();
$this->room = $this->commandHandler->getRoom();
if (WCF::getSession()->getPermission('admin.chat.canManageSuspensions')) return;
$ph = new \chat\system\permission\PermissionHandler();
if (static::IS_GLOBAL) {
WCF::getSession()->checkPermissions((array) 'mod.chat.canG'.static::SUSPENSION_TYPE);
}
else {
if (!WCF::getSession()->getPermission('mod.chat.canG'.static::SUSPENSION_TYPE)) {
if (!$ph->getPermission($this->room, 'mod.can'.ucfirst(static::SUSPENSION_TYPE))) {
throw new \wcf\system\exception\PermissionDeniedException();
}
}
}
}
/**
* @see \chat\system\command\ICommand::getType()
*/
public function getType() {
return \chat\data\message\Message::TYPE_MODERATE;
}
/**
* @see \chat\system\command\ICommand::getMessage()
*/
public function getMessage() {
return serialize(array(
'link' => $this->link,
'expires' => $this->expires,
'type' => (static::IS_GLOBAL ? 'g' : '').static::SUSPENSION_TYPE,
'reason' => $this->reason
));
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace chat\system\command;
use \chat\data\suspension;
use \chat\util\ChatUtil;
use \wcf\data\user\User;
use \wcf\system\WCF;
/**
* Default implementation for suspension commands
*
* @author Tim Düsterhus
* @copyright 2010-2013 Tim Düsterhus
* @license Creative Commons Attribution-NonCommercial-ShareAlike <http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode>
* @package be.bastelstu.chat
* @subpackage system.chat.command
*/
abstract class AbstractUnsuspensionCommand extends AbstractRestrictedCommand {
public $user = null;
public $suspensionAction = null;
public $link = '';
public $room = null;
public function __construct(\chat\system\command\CommandHandler $commandHandler) {
parent::__construct($commandHandler);
$username = rtrim($commandHandler->getParameters(), ',');
$this->user = User::getUserByUsername($username);
if (!$this->user->userID) throw new \chat\system\command\UserNotFoundException($username);
$profile = \wcf\system\request\LinkHandler::getInstance()->getLink('User', array(
'object' => $this->user
));
$this->link = "[url='".$profile."']".$this->user->username.'[/url]';
$this->executeAction();
$this->didInit();
}
public function executeAction() {
if (static::IS_GLOBAL) $room = new \chat\data\room\Room(null, array('roomID' => null));
else $room = $this->room;
if ($suspension = suspension\Suspension::getSuspensionByUserRoomAndType($this->user, $room, static::SUSPENSION_TYPE)) {
$action = new suspension\SuspensionAction(array($suspension), 'revoke', array(
'revoker' => WCF::getUser()->userID
));
$action->executeAction();
}
else {
throw new \wcf\system\exception\UserInputException('text', WCF::getLanguage()->get('wcf.chat.suspension.notExists'));
}
}
/**
* @see \chat\system\command\IRestrictedChatCommand::checkPermission()
*/
public function checkPermission() {
parent::checkPermission();
$this->room = $this->commandHandler->getRoom();
if (WCF::getSession()->getPermission('admin.chat.canManageSuspensions')) return;
$ph = new \chat\system\permission\PermissionHandler();
if (static::IS_GLOBAL) {
WCF::getSession()->checkPermission((array) 'mod.chat.canG'.static::SUSPENSION_TYPE);
}
else {
if (!WCF::getSession()->getPermission('mod.chat.canG'.static::SUSPENSION_TYPE)) {
if (!$ph->getPermission($this->room, 'mod.can'.ucfirst(static::SUSPENSION_TYPE))) {
throw new \wcf\system\exception\PermissionDeniedException();
}
}
}
}
/**
* @see \chat\system\command\ICommand::getType()
*/
public function getType() {
return \chat\data\message\Message::TYPE_MODERATE;
}
/**
* @see \chat\system\command\ICommand::getMessage()
*/
public function getMessage() {
return serialize(array(
'link' => $this->link,
'type' => 'un'.(static::IS_GLOBAL ? 'g' : '').static::SUSPENSION_TYPE,
));
}
}

View File

@ -36,11 +36,4 @@ public function getType() {
public function getMessage() { public function getMessage() {
return \wcf\system\bbcode\PreParser::getInstance()->parse($this->commandHandler->getParameters(), explode(',', WCF::getSession()->getPermission('user.chat.allowedBBCodes'))); return \wcf\system\bbcode\PreParser::getInstance()->parse($this->commandHandler->getParameters(), explode(',', WCF::getSession()->getPermission('user.chat.allowedBBCodes')));
} }
/**
* @see \chat\system\command\ICommand::getReceiver()
*/
public function getReceiver() {
return WCF::getUser()->userID;
}
} }

View File

@ -1,9 +1,5 @@
<?php <?php
namespace chat\system\command\commands; namespace chat\system\command\commands;
use \chat\data\suspension;
use \chat\util\ChatUtil;
use \wcf\data\user\User;
use \wcf\system\WCF;
/** /**
* Bans a user. * Bans a user.
@ -14,25 +10,7 @@
* @package be.bastelstu.chat * @package be.bastelstu.chat
* @subpackage system.chat.command.commands * @subpackage system.chat.command.commands
*/ */
class BanCommand extends MuteCommand { class BanCommand extends \chat\system\command\AbstractSuspensionCommand {
public function executeAction() { const IS_GLOBAL = false;
if ($suspension = suspension\Suspension::getSuspensionByUserRoomAndType($this->user, $this->room, suspension\Suspension::TYPE_BAN)) { const SUSPENSION_TYPE = \chat\data\suspension\Suspension::TYPE_BAN;
if ($suspension->expires > $this->expires) {
throw new \wcf\system\exception\UserInputException('text', WCF::getLanguage()->get('wcf.chat.suspension.exists'));
}
$action = new suspension\SuspensionAction(array($suspension), 'delete');
$action->executeAction();
}
$this->suspensionAction = new suspension\SuspensionAction(array(), 'create', array(
'data' => array(
'userID' => $this->user->userID,
'roomID' => WCF::getUser()->chatRoomID,
'type' => suspension\Suspension::TYPE_BAN,
'expires' => $this->expires
)
));
$this->suspensionAction->executeAction();
}
} }

View File

@ -1,9 +1,5 @@
<?php <?php
namespace chat\system\command\commands; namespace chat\system\command\commands;
use \chat\data\suspension;
use \chat\util\ChatUtil;
use \wcf\data\user\User;
use \wcf\system\WCF;
/** /**
* Globally bans a user. * Globally bans a user.
@ -14,27 +10,7 @@
* @package be.bastelstu.chat * @package be.bastelstu.chat
* @subpackage system.chat.command.commands * @subpackage system.chat.command.commands
*/ */
class GbanCommand extends MuteCommand { class GbanCommand extends \chat\system\command\AbstractSuspensionCommand {
public function executeAction() { const IS_GLOBAL = true;
$room = new \chat\data\room\Room(null, array('roomID' => null)); const SUSPENSION_TYPE = \chat\data\suspension\Suspension::TYPE_BAN;
if ($suspension = suspension\Suspension::getSuspensionByUserRoomAndType($this->user, $room, suspension\Suspension::TYPE_BAN)) {
if ($suspension->expires > $this->expires) {
throw new \wcf\system\exception\UserInputException('text', WCF::getLanguage()->get('wcf.chat.suspension.exists'));
}
$action = new suspension\SuspensionAction(array($suspension), 'delete');
$action->executeAction();
}
$this->suspensionAction = new suspension\SuspensionAction(array(), 'create', array(
'data' => array(
'userID' => $this->user->userID,
'roomID' => null,
'type' => suspension\Suspension::TYPE_BAN,
'expires' => $this->expires
)
));
$this->suspensionAction->executeAction();
}
} }

View File

@ -1,12 +1,8 @@
<?php <?php
namespace chat\system\command\commands; namespace chat\system\command\commands;
use \chat\data\suspension;
use \chat\util\ChatUtil;
use \wcf\data\user\User;
use \wcf\system\WCF;
/** /**
* Globally bans a user. * Globally mutes a user.
* *
* @author Tim Düsterhus * @author Tim Düsterhus
* @copyright 2010-2013 Tim Düsterhus * @copyright 2010-2013 Tim Düsterhus
@ -14,27 +10,7 @@
* @package be.bastelstu.chat * @package be.bastelstu.chat
* @subpackage system.chat.command.commands * @subpackage system.chat.command.commands
*/ */
class GmuteCommand extends MuteCommand { class GmuteCommand extends \chat\system\command\AbstractSuspensionCommand {
public function executeAction() { const IS_GLOBAL = true;
$room = new \chat\data\room\Room(null, array('roomID' => null)); const SUSPENSION_TYPE = \chat\data\suspension\Suspension::TYPE_MUTE;
if ($suspension = suspension\Suspension::getSuspensionByUserRoomAndType($this->user, $room, suspension\Suspension::TYPE_MUTE)) {
if ($suspension->expires > $this->expires) {
throw new \wcf\system\exception\UserInputException('text', WCF::getLanguage()->get('wcf.chat.suspension.exists'));
}
$action = new suspension\SuspensionAction(array($suspension), 'delete');
$action->executeAction();
}
$this->suspensionAction = new suspension\SuspensionAction(array(), 'create', array(
'data' => array(
'userID' => $this->user->userID,
'roomID' => null,
'type' => suspension\Suspension::TYPE_MUTE,
'expires' => $this->expires
)
));
$this->suspensionAction->executeAction();
}
} }

View File

@ -1,32 +0,0 @@
<?php
namespace chat\system\command\commands;
use \chat\data\suspension;
use \chat\util\ChatUtil;
use \wcf\data\user\User;
use \wcf\system\WCF;
/**
* Unbans a user globally.
*
* @author Tim Düsterhus
* @copyright 2010-2013 Tim Düsterhus
* @license Creative Commons Attribution-NonCommercial-ShareAlike <http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode>
* @package be.bastelstu.chat
* @subpackage system.chat.command.commands
*/
class GunbanCommand extends UnmuteCommand {
/**
* @see \chat\system\command\commands\UnmuteCommand::executeAction()
*/
public function executeAction() {
$room = new \chat\data\room\Room(null, array('roomID' => null));
if ($suspension = suspension\Suspension::getSuspensionByUserRoomAndType($this->user, $room, suspension\Suspension::TYPE_BAN)) {
$action = new suspension\SuspensionAction(array($suspension), 'delete');
$action->executeAction();
}
else {
throw new \wcf\system\exception\UserInputException('text', WCF::getLanguage()->get('wcf.chat.suspension.notExists'));
}
}
}

View File

@ -1,32 +0,0 @@
<?php
namespace chat\system\command\commands;
use \chat\data\suspension;
use \chat\util\ChatUtil;
use \wcf\data\user\User;
use \wcf\system\WCF;
/**
* Unmutes a user globally.
*
* @author Tim Düsterhus
* @copyright 2010-2013 Tim Düsterhus
* @license Creative Commons Attribution-NonCommercial-ShareAlike <http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode>
* @package be.bastelstu.chat
* @subpackage system.chat.command.commands
*/
class GunmuteCommand extends UnmuteCommand {
/**
* @see \chat\system\command\commands\UnmuteCommand::executeAction()
*/
public function executeAction() {
$room = new \chat\data\room\Room(null, array('roomID' => null));
if ($suspension = suspension\Suspension::getSuspensionByUserRoomAndType($this->user, $room, suspension\Suspension::TYPE_MUTE)) {
$action = new suspension\SuspensionAction(array($suspension), 'delete');
$action->executeAction();
}
else {
throw new \wcf\system\exception\UserInputException('text', WCF::getLanguage()->get('wcf.chat.suspension.notExists'));
}
}
}

View File

@ -45,11 +45,11 @@ public function __construct(\chat\system\command\CommandHandler $commandHandler)
} }
// Suspensions // Suspensions
// TODO: Permissions
$suspensions = \chat\data\suspension\Suspension::getSuspensionsForUser($this->user); $suspensions = \chat\data\suspension\Suspension::getSuspensionsForUser($this->user);
foreach ($suspensions as $roomSuspensions) { foreach ($suspensions as $roomSuspensions) {
foreach ($roomSuspensions as $typeSuspension) { foreach ($roomSuspensions as $typeSuspension) {
if (!$typeSuspension->isValid()) continue; if (!$typeSuspension->isValid()) continue;
if (!$typeSuspension->isVisible()) continue;
$dateTime = DateUtil::getDateTimeByTimestamp($typeSuspension->expires); $dateTime = DateUtil::getDateTimeByTimestamp($typeSuspension->expires);
$name = WCF::getLanguage()->getDynamicVariable('chat.general.information.suspension', array( $name = WCF::getLanguage()->getDynamicVariable('chat.general.information.suspension', array(

View File

@ -1,9 +1,5 @@
<?php <?php
namespace chat\system\command\commands; namespace chat\system\command\commands;
use \chat\data\suspension;
use \chat\util\ChatUtil;
use \wcf\data\user\User;
use \wcf\system\WCF;
/** /**
* Mutes a user. * Mutes a user.
@ -14,86 +10,7 @@
* @package be.bastelstu.chat * @package be.bastelstu.chat
* @subpackage system.chat.command.commands * @subpackage system.chat.command.commands
*/ */
class MuteCommand extends \chat\system\command\AbstractRestrictedCommand { class MuteCommand extends \chat\system\command\AbstractSuspensionCommand {
public $user = null; const IS_GLOBAL = false;
public $expires = 0; const SUSPENSION_TYPE = \chat\data\suspension\Suspension::TYPE_MUTE;
public $suspensionAction = null;
public $link = '';
public $room = null;
public function __construct(\chat\system\command\CommandHandler $commandHandler) {
parent::__construct($commandHandler);
try {
list($username, $modifier) = explode(',', $commandHandler->getParameters(), 2);
$modifier = ChatUtil::timeModifier(\wcf\util\StringUtil::trim($modifier));
$expires = strtotime($modifier, TIME_NOW);
$this->expires = min(max(-0x80000000, $expires), 0x7FFFFFFF);
}
catch (\wcf\system\exception\SystemException $e) {
throw new \InvalidArgumentException();
}
$this->user = User::getUserByUsername($username);
if (!$this->user->userID) throw new \chat\system\command\UserNotFoundException($username);
$profile = \wcf\system\request\LinkHandler::getInstance()->getLink('User', array(
'object' => $this->user
));
$this->link = "[url='".$profile."']".$this->user->username.'[/url]';
$this->executeAction();
$this->didInit();
}
public function executeAction() {
if ($suspension = suspension\Suspension::getSuspensionByUserRoomAndType($this->user, $this->room, suspension\Suspension::TYPE_MUTE)) {
if ($suspension->expires > $this->expires) {
throw new \wcf\system\exception\UserInputException('text', WCF::getLanguage()->get('wcf.chat.suspension.exists'));
}
$action = new suspension\SuspensionAction(array($suspension), 'delete');
$action->executeAction();
}
$this->suspensionAction = new suspension\SuspensionAction(array(), 'create', array(
'data' => array(
'userID' => $this->user->userID,
'roomID' => WCF::getUser()->chatRoomID,
'type' => suspension\Suspension::TYPE_MUTE,
'expires' => $this->expires
)
));
$this->suspensionAction->executeAction();
}
/**
* @see \chat\system\command\IRestrictedChatCommand::checkPermission()
*/
public function checkPermission() {
parent::checkPermission();
$this->room = $this->commandHandler->getRoom();
$ph = new \chat\system\permission\PermissionHandler();
if (!$ph->getPermission($this->room, 'mod.can'.str_replace(array('chat\system\command\commands\\', 'Command'), '', get_class($this)))) throw new \wcf\system\exception\PermissionDeniedException();
}
/**
* @see \chat\system\command\ICommand::getType()
*/
public function getType() {
return \chat\data\message\Message::TYPE_MODERATE;
}
/**
* @see \chat\system\command\ICommand::getMessage()
*/
public function getMessage() {
return serialize(array(
'link' => $this->link,
'expires' => $this->expires,
'type' => str_replace(array('chat\system\command\commands\\', 'command'), '', strtolower(get_class($this)))
));
}
} }

View File

@ -1,9 +1,5 @@
<?php <?php
namespace chat\system\command\commands; namespace chat\system\command\commands;
use \chat\data\suspension;
use \chat\util\ChatUtil;
use \wcf\data\user\User;
use \wcf\system\WCF;
/** /**
* Bans a user. * Bans a user.
@ -14,17 +10,7 @@
* @package be.bastelstu.chat * @package be.bastelstu.chat
* @subpackage system.chat.command.commands * @subpackage system.chat.command.commands
*/ */
class UnbanCommand extends UnmuteCommand { class UnbanCommand extends \chat\system\command\AbstractUnsuspensionCommand {
/** const IS_GLOBAL = false;
* @see \chat\system\command\commands\UnmuteCommand::executeAction() const SUSPENSION_TYPE = \chat\data\suspension\Suspension::TYPE_BAN;
*/
public function executeAction() {
if ($suspension = suspension\Suspension::getSuspensionByUserRoomAndType($this->user, $this->room, suspension\Suspension::TYPE_BAN)) {
$action = new suspension\SuspensionAction(array($suspension), 'delete');
$action->executeAction();
}
else {
throw new \wcf\system\exception\UserInputException('text', WCF::getLanguage()->get('wcf.chat.suspension.notExists'));
}
}
} }

View File

@ -0,0 +1,16 @@
<?php
namespace chat\system\command\commands;
/**
* Unbans a user globally.
*
* @author Tim Düsterhus
* @copyright 2010-2013 Tim Düsterhus
* @license Creative Commons Attribution-NonCommercial-ShareAlike <http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode>
* @package be.bastelstu.chat
* @subpackage system.chat.command.commands
*/
class UngbanCommand extends \chat\system\command\AbstractUnsuspensionCommand {
const IS_GLOBAL = true;
const SUSPENSION_TYPE = \chat\data\suspension\Suspension::TYPE_BAN;
}

View File

@ -0,0 +1,16 @@
<?php
namespace chat\system\command\commands;
/**
* Unmutes a user globally.
*
* @author Tim Düsterhus
* @copyright 2010-2013 Tim Düsterhus
* @license Creative Commons Attribution-NonCommercial-ShareAlike <http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode>
* @package be.bastelstu.chat
* @subpackage system.chat.command.commands
*/
class UngmuteCommand extends \chat\system\command\AbstractUnsuspensionCommand {
const IS_GLOBAL = true;
const SUSPENSION_TYPE = \chat\data\suspension\Suspension::TYPE_MUTE;
}

View File

@ -1,8 +1,5 @@
<?php <?php
namespace chat\system\command\commands; namespace chat\system\command\commands;
use \chat\data\suspension;
use \wcf\data\user\User;
use \wcf\system\WCF;
/** /**
* Unmutes a user. * Unmutes a user.
@ -13,67 +10,7 @@
* @package be.bastelstu.chat * @package be.bastelstu.chat
* @subpackage system.chat.command.commands * @subpackage system.chat.command.commands
*/ */
class UnmuteCommand extends \chat\system\command\AbstractRestrictedCommand { class UnmuteCommand extends \chat\system\command\AbstractUnsuspensionCommand {
public $user = null; const IS_GLOBAL = false;
public $suspensionAction = null; const SUSPENSION_TYPE = \chat\data\suspension\Suspension::TYPE_MUTE;
public $link = '';
public $room = null;
public function __construct(\chat\system\command\CommandHandler $commandHandler) {
parent::__construct($commandHandler);
$username = rtrim($commandHandler->getParameters(), ',');
$this->user = User::getUserByUsername($username);
if (!$this->user->userID) throw new \chat\system\command\UserNotFoundException($username);
$profile = \wcf\system\request\LinkHandler::getInstance()->getLink('User', array(
'object' => $this->user
));
$this->link = "[url='".$profile."']".$this->user->username.'[/url]';
$this->executeAction();
$this->didInit();
}
/**
* Removes the suspension.
*/
public function executeAction() {
if ($suspension = suspension\Suspension::getSuspensionByUserRoomAndType($this->user, $this->room, suspension\Suspension::TYPE_MUTE)) {
$action = new suspension\SuspensionAction(array($suspension), 'delete');
$action->executeAction();
}
else {
throw new \wcf\system\exception\UserInputException('text', WCF::getLanguage()->get('wcf.chat.suspension.notExists'));
}
}
/**
* @see \chat\system\command\IRestrictedChatCommand::checkPermission()
*/
public function checkPermission() {
parent::checkPermission();
$this->room = $this->commandHandler->getRoom();
$ph = new \chat\system\permission\PermissionHandler();
if (!$ph->getPermission($this->room, 'mod.can'.ucfirst(str_replace(array('chat\system\command\commands\\Un', 'Command'), '', get_class($this))))) throw new \wcf\system\exception\PermissionDeniedException();
}
/**
* @see \chat\system\command\ICommand::getType()
*/
public function getType() {
return \chat\data\message\Message::TYPE_MODERATE;
}
/**
* @see \chat\system\command\ICommand::getMessage()
*/
public function getMessage() {
return serialize(array(
'link' => $this->link,
'type' => str_replace(array('chat\system\command\commands\\', 'command'), '', strtolower(get_class($this)))
));
}
} }

View File

@ -20,8 +20,6 @@ public function execute(\wcf\data\cronjob\Cronjob $cronjob) {
$messageAction->executeAction(); $messageAction->executeAction();
$roomAction = new data\room\RoomAction(array(), 'prune'); $roomAction = new data\room\RoomAction(array(), 'prune');
$roomAction->executeAction(); $roomAction->executeAction();
$suspensionAction = new data\suspension\SuspensionAction(array(), 'prune');
$suspensionAction->executeAction();
// kill dead users // kill dead users
$roomAction = new data\room\RoomAction(array(), 'removeDeadUsers'); $roomAction = new data\room\RoomAction(array(), 'removeDeadUsers');

View File

@ -16,6 +16,18 @@
} }
} }
.__bubbleArrow {
border-color: transparent @wcfContainerBorderColor;
left: -6px;
top: 5px;
border-width: 6px 6px 6px 0;
border-style: solid;
content: "";
display: block;
position: absolute;
width: 0;
}
#tplChat { #tplChat {
#main > div { #main > div {
overflow: hidden; overflow: hidden;
@ -28,17 +40,120 @@
.transition(padding-top, .2s); .transition(padding-top, .2s);
.transition(padding-bottom, .2s); .transition(padding-bottom, .2s);
&.empty { &.empty, &.hidden {
height: 0px; height: 0px;
overflow: hidden; overflow: hidden;
border: 0px; border: 0px;
padding: 0px; padding: 0px;
margin: 0px; margin: 0px;
~ #timsChatMessageContainer {
margin-top: 0;
}
}
.jsTopicCloser {
cursor: pointer;
float: right;
}
}
#privateChannelsMenu {
.transition(opacity, .2s);
.marginTop;
z-index: -1;
position: absolute;
opacity: 0;
&.shown {
opacity: 1;
z-index: 130;
~ .timsChatMessageContainer {
margin-left: 35px;
border-top-left-radius: 0;
}
}
> ul {
text-align: right;
> li:first-child {
> .userAvatar.framed {
img, > canvas, > .icon {
border-radius: @wcfContainerBorderRadius 0 0 0;
}
}
}
> li:last-child {
> .userAvatar.framed {
img, > canvas, > .icon {
border-radius: 0 0 0 @wcfContainerBorderRadius;
}
}
}
> li {
margin-bottom: -1px;
background-color: @wcfContainerBackgroundColor;
> .userAvatar, .userAvatar > .icon {
cursor: pointer;
}
> .userAvatar {
&.large {
display: none;
}
&.small {
display: block;
}
&.framed {
> .icon {
background-color: @wcfContentBackgroundColor;
border: 1px solid @wcfContainerBorderColor;
padding: 1px;
}
}
}
&.active {
> .userAvatar {
&.large {
display: block;
}
&.small {
display: none;
}
&.framed {
> img, > canvas, > .icon {
border-right-color: @wcfContentBackgroundColor;
border-radius: @wcfContainerBorderRadius 0 0 @wcfContainerBorderRadius;
}
}
}
}
&.notify {
> .userAvatar {
> * {
// TODO
opacity: .4;
}
}
}
}
} }
} }
.timsChatMessageContainer { .timsChatMessageContainer {
height: 200px; height: 320px;
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
display: none; display: none;
@ -55,9 +170,9 @@
color: @wcfSelectedColor; color: @wcfSelectedColor;
} }
> { > .innerMessageContainer {
.markContainer { .markContainer {
display: table-cell; display: block;
} }
} }
} }
@ -77,47 +192,138 @@
opacity: .5; opacity: .5;
} }
&::before {
.icon;
.icon16;
padding: @wcfGapTiny;
}
&:nth-child(even) { &:nth-child(even) {
> .innerMessageContainer.bubble .innerMessage, .innerMessageContainer.right.bubble .innerMessage {
background-color: @wcfContainerAccentBackgroundColor; background-color: @wcfContainerAccentBackgroundColor;
&:after {
border-color: transparent @wcfContainerAccentBackgroundColor;
}
}
} }
> { .messageIcon {
time, .usernameContainer, .text, .markContainer { float: left;
display: table-cell; padding: 8px 0 0 4px;
vertical-align: top; margin-right: -16px;
padding: @wcfGapTiny 0;
} }
.markContainer { > .innerMessageContainer {
display: none; padding: 5px 20px 5px 5px;
padding: 0; position: relative;
.userAvatar {
float: left;
margin-left: 16px;
} }
.innerMessage {
margin-left: 46px;
padding: 2px 5px 5px;
time { time {
&::before { float: right;
content: "["; }
> .text {
img {
max-width: 480px;
max-height: 320px;
} }
&::after {
content: "]";
} }
} }
.usernameContainer { &.bubble {
text-align: right; .userAvatar {
min-width: 100px; margin-left: 0;
padding-right: @wcfGapSmall;
white-space: nowrap; .icon {
padding: 2px;
}
}
.innerMessage {
border-width: 1px;
border-style: solid;
border-color: @wcfContainerBorderColor;
border-radius: @wcfContainerBorderRadius;
background-color: @wcfContainerBackgroundColor;
position: relative;
.username {
font-weight: bold; font-weight: bold;
} }
.text { > ul.text {
width: 100%; li {
.clearfix;
border-style: solid;
border-width: 0 0 1px 0;
border-color: @wcfContainerBorderColor;
padding: 3px 0 4px;
&:last-child {
border-style: none;
padding: 3px 0 0 0;
}
}
}
&:before {
.__bubbleArrow;
}
&:after {
.__bubbleArrow;
border-color: transparent @wcfContainerBackgroundColor;
left: -5px;
top: 6px;
border-width: 5px 5px 5px 0;
}
}
&.right {
.userAvatar {
float: right;
}
.innerMessage {
margin-right: 46px;
margin-left: 0px;
&:before {
.__bubbleArrow;
left: auto;
right: -6px;
border-width: 6px 0 6px 6px;
}
&:after {
.__bubbleArrow;
border-color: transparent @wcfContainerBackgroundColor;
left: auto;
right: -5px;
top: 6px;
border-width: 5px 0 5px 5px;
}
time {
float: left;
margin-right: @wcfGapTiny;
}
.username {
float: right;
}
}
}
}
> .markContainer {
display: none;
position: absolute;
right: 0px;
top: 6px;
} }
} }
} }

View File

@ -46,10 +46,14 @@ CREATE TABLE chat1_suspension (
suspensionID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, suspensionID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
userID INT(10) NOT NULL, userID INT(10) NOT NULL,
roomID INT(10) DEFAULT NULL, roomID INT(10) DEFAULT NULL,
type TINYINT(3) NOT NULL, type VARCHAR(15) NOT NULL,
expires INT(10) NOT NULL, expires INT(10) NOT NULL,
time INT(10) NOT NULL,
issuer INT(10) DEFAULT NULL,
reason VARCHAR(255) NOT NULL DEFAULT '',
revoker INT(10) DEFAULT NULL,
UNIQUE KEY suspension (userID, roomID, type), KEY suspension (userID, roomID, type),
KEY (roomID), KEY (roomID),
KEY (type), KEY (type),
KEY (expires) KEY (expires)
@ -70,10 +74,12 @@ ALTER TABLE chat1_room ADD FOREIGN KEY (owner) REFERENCES wcf1_user (userID) ON
ALTER TABLE chat1_suspension ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE; ALTER TABLE chat1_suspension ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;
ALTER TABLE chat1_suspension ADD FOREIGN KEY (roomID) REFERENCES chat1_room (roomID) ON DELETE CASCADE; ALTER TABLE chat1_suspension ADD FOREIGN KEY (roomID) REFERENCES chat1_room (roomID) ON DELETE CASCADE;
ALTER TABLE chat1_suspension ADD FOREIGN KEY (issuer) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
ALTER TABLE chat1_suspension ADD FOREIGN KEY (revoker) REFERENCES wcf1_user (userID) ON DELETE SET NULL;
ALTER TABLE wcf1_user ADD FOREIGN KEY (chatRoomID) REFERENCES chat1_room (roomID) ON DELETE SET NULL; ALTER TABLE wcf1_user ADD FOREIGN KEY (chatRoomID) REFERENCES chat1_room (roomID) ON DELETE SET NULL;
INSERT INTO chat1_room (title, topic, showOrder) VALUES ('chat.room.title1', 'chat.room.topic1', 1); INSERT INTO chat1_room (title, topic, showOrder) VALUES ('chat.room.title1', 'chat.room.topic1', 1);
INSERT INTO chat1_room (title, topic, showOrder) VALUES ('Testroom 2', 'Topic of Testroom 2', 2); INSERT INTO chat1_room (title, topic, showOrder) VALUES ('Testroom 2', 'Topic of Testroom 2', 2);
INSERT INTO chat1_room (title, topic, showOrder) VALUES ('Testroom with a very long', 'The topic of this room is rather loing as well!', 3); INSERT INTO chat1_room (title, topic, showOrder) VALUES ('Testroom with a very long name', 'The topic of this room is rather loing as well!', 3);
INSERT INTO chat1_room (title, topic, showOrder) VALUES ('Room w/o topic', '', 4); INSERT INTO chat1_room (title, topic, showOrder) VALUES ('Room w/o topic', '', 4);

View File

@ -11,10 +11,32 @@
<item name="chat.acp.room.delete.sure"><![CDATA[Wollen Sie den Raum „{$chatRoom}“ wirklich löschen?]]></item> <item name="chat.acp.room.delete.sure"><![CDATA[Wollen Sie den Raum „{$chatRoom}“ wirklich löschen?]]></item>
</category> </category>
<category name="chat.acp.suspension">
<item name="chat.acp.suspension.list"><![CDATA[Sanktionen]]></item>
<item name="chat.acp.suspension.type"><![CDATA[Art der Sanktion]]></item>
<item name="chat.acp.suspension.issuer"><![CDATA[Aussteller]]></item>
<item name="chat.acp.suspension.displayRevoked"><![CDATA[Abgelaufene Sanktionen anzeigen]]></item>
<item name="chat.acp.suspension.reason"><![CDATA[Grund]]></item>
<item name="chat.acp.suspension.revoked"><![CDATA[Zurückziehen]]></item>
<item name="chat.acp.suspension.revokedBy"><![CDATA[Zurückgezogen von {$suspension->revokerUsername}]]></item>
</category>
<category name="chat.acp.menu"> <category name="chat.acp.menu">
<item name="chat.acp.menu.link"><![CDATA[Chat]]></item> <item name="chat.acp.menu.link"><![CDATA[Chat]]></item>
<item name="chat.acp.menu.link.room.list"><![CDATA[Chaträume auflisten]]></item> <item name="chat.acp.menu.link.room.list"><![CDATA[Chaträume auflisten]]></item>
<item name="chat.acp.menu.link.room.add"><![CDATA[Chatraum hinzufügen]]></item> <item name="chat.acp.menu.link.room.add"><![CDATA[Chatraum hinzufügen]]></item>
<item name="chat.acp.menu.link.suspension.list"><![CDATA[Sanktionen auflisten]]></item>
</category>
<category name="wcf.acl.option">
<item name="wcf.acl.option.category.be.bastelstu.chat.room.user"><![CDATA[Benutzerrechte]]></item>
<item name="wcf.acl.option.category.be.bastelstu.chat.room.mod"><![CDATA[Moderative Rechte]]></item>
<item name="wcf.acl.option.be.bastelstu.chat.room.user.canEnter"><![CDATA[Kann Raum betreten]]></item>
<item name="wcf.acl.option.be.bastelstu.chat.room.user.canWrite"><![CDATA[Kann schreiben]]></item>
<item name="wcf.acl.option.be.bastelstu.chat.room.mod.canAlwaysEnter"><![CDATA[Kann Raum immer betreten]]></item>
<item name="wcf.acl.option.be.bastelstu.chat.room.mod.canAlwaysWrite"><![CDATA[Kann immer schreiben]]></item>
<item name="wcf.acl.option.be.bastelstu.chat.room.mod.canMute"><![CDATA[Kann knebeln]]></item>
<item name="wcf.acl.option.be.bastelstu.chat.room.mod.canBan"><![CDATA[Kann bannen]]></item>
</category> </category>
<category name="wcf.acp.group"> <category name="wcf.acp.group">
@ -24,12 +46,24 @@
<item name="wcf.acp.group.option.user.chat.canTempRoom"><![CDATA[Kann temporäre Räume erstellen]]></item> <item name="wcf.acp.group.option.user.chat.canTempRoom"><![CDATA[Kann temporäre Räume erstellen]]></item>
<item name="wcf.acp.group.option.category.mod.chat"><![CDATA[Chat]]></item> <item name="wcf.acp.group.option.category.mod.chat"><![CDATA[Chat]]></item>
<item name="wcf.acp.group.option.mod.chat.canAlwaysEnter"><![CDATA[Kann jeden Raum betreten]]></item> <item name="wcf.acp.group.option.mod.chat.canAlwaysEnter"><![CDATA[Kann Räume immer betreten]]></item>
<item name="wcf.acp.group.option.mod.chat.canAlwaysEnter.description"><![CDATA[Setzt Banns außer Kraft.]]></item> <item name="wcf.acp.group.option.mod.chat.canAlwaysEnter.description"><![CDATA[Setzt Banns außer Kraft.]]></item>
<item name="wcf.acp.group.option.mod.chat.canAlwaysWrite"><![CDATA[Kann in jedem Raum schreiben]]></item> <item name="wcf.acp.group.option.mod.chat.canAlwaysWrite"><![CDATA[Kann immer schreiben]]></item>
<item name="wcf.acp.group.option.mod.chat.canAlwaysWrite.description"><![CDATA[Setzt Knebel außer Kraft.]]></item> <item name="wcf.acp.group.option.mod.chat.canAlwaysWrite.description"><![CDATA[Setzt Knebel außer Kraft.]]></item>
<item name="wcf.acp.group.option.mod.chat.canRestore"><![CDATA[Kann User zurücksetzen]]></item> <item name="wcf.acp.group.option.mod.chat.canRestore"><![CDATA[Kann User zurücksetzen]]></item>
<item name="wcf.acp.group.option.mod.chat.canRestore.description"><![CDATA[Setzt Nutzerdaten (Farbe) zurück.]]></item> <item name="wcf.acp.group.option.mod.chat.canRestore.description"><![CDATA[Setzt Nutzerdaten (Farbe) zurück.]]></item>
<item name="wcf.acp.group.option.mod.chat.canBan"><![CDATA[Kann bannen]]></item>
<item name="wcf.acp.group.option.mod.chat.canBan.description"><![CDATA[Impliziert „Kann Chat immer betreten“]]></item>
<item name="wcf.acp.group.option.mod.chat.canMute"><![CDATA[Kann knebeln]]></item>
<item name="wcf.acp.group.option.mod.chat.canMute.description"><![CDATA[Impliziert „Kann immer schreiben“]]></item>
<item name="wcf.acp.group.option.mod.chat.canGban"><![CDATA[Kann global bannen]]></item>
<item name="wcf.acp.group.option.mod.chat.canGban.description"><![CDATA[Impliziert „Kann bannen“]]></item>
<item name="wcf.acp.group.option.mod.chat.canGmute"><![CDATA[Kann global knebeln]]></item>
<item name="wcf.acp.group.option.mod.chat.canGmute.description"><![CDATA[Impliziert „Kann knebeln“]]></item>
<item name="wcf.acp.group.option.category.admin.chat"><![CDATA[Chat]]></item>
<item name="wcf.acp.group.option.admin.chat.canManageSuspensions"><![CDATA[Kann Sanktionen verwalten]]></item>
<item name="wcf.acp.group.option.admin.chat.canManageSuspensions.description"><![CDATA[Impliziert „Kann bannen“, „Kann knebeln“, „Kann global bannen“, „Kann global knebeln“ und gegenbenenfalls weitere Rechte.]]></item>
</category> </category>
<category name="wcf.acp.option"> <category name="wcf.acp.option">
@ -59,7 +93,7 @@
</category> </category>
<category name="chat.error"> <category name="chat.error">
<item name="chat.error.notFound"><![CDATA[Der Befehl wurde nicht gefunden.]]></item> <item name="chat.error.notFound"><![CDATA[Der Befehl „{$exception->getCommand()}“ wurde nicht gefunden.]]></item>
<item name="chat.error.userNotFound"><![CDATA[Der Benutzer „{$exception->getUsername()}“ wurde nicht gefunden.]]></item> <item name="chat.error.userNotFound"><![CDATA[Der Benutzer „{$exception->getUsername()}“ wurde nicht gefunden.]]></item>
<item name="chat.error.permissionDenied"><![CDATA[Sie dürfen diesen Befehl nicht verwenden.]]></item> <item name="chat.error.permissionDenied"><![CDATA[Sie dürfen diesen Befehl nicht verwenden.]]></item>
<item name="chat.error.duplicateTab"><![CDATA[Der Chat wurde in einem weiteren Tab geöffnet.]]></item> <item name="chat.error.duplicateTab"><![CDATA[Der Chat wurde in einem weiteren Tab geöffnet.]]></item>
@ -68,6 +102,9 @@
</category> </category>
<category name="chat.general"> <category name="chat.general">
<item name="chat.general.expires"><![CDATA[Ablaufzeitpunkt]]></item>
<item name="chat.general.time"><![CDATA[Datum]]></item>
<item name="chat.general.title"><![CDATA[{lang}chat.header.menu.chat{/lang}]]></item> <item name="chat.general.title"><![CDATA[{lang}chat.header.menu.chat{/lang}]]></item>
<item name="chat.general.protocol"><![CDATA[Protokoll]]></item> <item name="chat.general.protocol"><![CDATA[Protokoll]]></item>
@ -100,6 +137,10 @@
<item name="chat.general.information"><![CDATA[Information]]></item> <item name="chat.general.information"><![CDATA[Information]]></item>
<item name="chat.general.information.chatUpdate"><![CDATA[Der Chat wurde aktualisiert. Bitte laden Sie die Seite neu, da es sonst zu Fehlern kommen kann.]]></item> <item name="chat.general.information.chatUpdate"><![CDATA[Der Chat wurde aktualisiert. Bitte laden Sie die Seite neu, da es sonst zu Fehlern kommen kann.]]></item>
<item name="chat.general.information.suspension"><![CDATA[{lang}chat.suspension.{$suspension->type}{/lang} ({if $room}{$room}{else}{lang}chat.room.global{/lang}{/if})]]></item> <item name="chat.general.information.suspension"><![CDATA[{lang}chat.suspension.{$suspension->type}{/lang} ({if $room}{$room}{else}{lang}chat.room.global{/lang}{/if})]]></item>
<item name="chat.general.privateChannelTopic"><![CDATA[{literal}Sie befinden sich in einem privaten Kanal mit „{$username}“, um diesen zu schließen, klicken Sie einfach auf das Kreuz rechts.{/literal}]]></item>
<item name="chat.general.closePrivateChannel"><![CDATA[Privaten Kanal schließen]]></item>
<item name="chat.general.closeTopic"><![CDATA[Thema ausblenden]]></item>
</category> </category>
<category name="chat.header"> <category name="chat.header">
@ -117,21 +158,21 @@
<item name="chat.message.4"><![CDATA[ist jetzt wieder da.]]></item> <item name="chat.message.4"><![CDATA[ist jetzt wieder da.]]></item>
<!-- 5 = TYPE_MODERATE --> <!-- 5 = TYPE_MODERATE -->
<item name="chat.message.5.restore"><![CDATA[hat {@$link} zurückgesetzt.]]></item> <item name="chat.message.5.restore"><![CDATA[hat {@$link} zurückgesetzt.]]></item>
<item name="chat.message.5.mute"><![CDATA[hat {@$link} bis {@$expires|plainTime} geknebelt.]]></item> <item name="chat.message.5.mute"><![CDATA[hat {@$link} bis {@$expires|plainTime} geknebelt{if !$reason|empty}: {$reason}{else}.{/if}]]></item>
<item name="chat.message.5.ban"><![CDATA[hat {@$link} bis {@$expires|plainTime} gebannt.]]></item> <item name="chat.message.5.ban"><![CDATA[hat {@$link} bis {@$expires|plainTime} gebannt{if !$reason|empty}: {$reason}{else}.{/if}]]></item>
<item name="chat.message.5.gmute"><![CDATA[hat {@$link} bis {@$expires|plainTime} global geknebelt.]]></item> <item name="chat.message.5.gmute"><![CDATA[hat {@$link} bis {@$expires|plainTime} global geknebelt{if !$reason|empty}: {$reason}{else}.{/if}]]></item>
<item name="chat.message.5.gban"><![CDATA[hat {@$link} bis {@$expires|plainTime} global gebannt.]]></item> <item name="chat.message.5.gban"><![CDATA[hat {@$link} bis {@$expires|plainTime} global gebannt{if !$reason|empty}: {$reason}{else}.{/if}]]></item>
<item name="chat.message.5.unmute"><![CDATA[hat {@$link} entknebelt.]]></item> <item name="chat.message.5.unmute"><![CDATA[hat {@$link} entknebelt.]]></item>
<item name="chat.message.5.unban"><![CDATA[hat {@$link} entbannt.]]></item> <item name="chat.message.5.unban"><![CDATA[hat {@$link} entbannt.]]></item>
<item name="chat.message.5.gunmute"><![CDATA[hat {@$link} global entknebelt.]]></item> <item name="chat.message.5.ungmute"><![CDATA[hat {@$link} global entknebelt.]]></item>
<item name="chat.message.5.gunban"><![CDATA[hat {@$link} global entbannt.]]></item> <item name="chat.message.5.ungban"><![CDATA[hat {@$link} global entbannt.]]></item>
<item name="chat.message.color.success"><![CDATA[Die Farbe wurde erfolgreich geändert.]]></item> <item name="chat.message.color.success"><![CDATA[Die Farbe wurde erfolgreich geändert.]]></item>
</category> </category>
<category name="chat.suspension"> <category name="chat.suspension">
<item name="chat.suspension.1"><![CDATA[Knebel]]></item> <item name="chat.suspension.mute"><![CDATA[Knebel]]></item>
<item name="chat.suspension.2"><![CDATA[Bann]]></item> <item name="chat.suspension.ban"><![CDATA[Bann]]></item>
</category> </category>
<!-- I18N Values --> <!-- I18N Values -->

View File

@ -49,7 +49,9 @@
<optiontype>textarea</optiontype> <optiontype>textarea</optiontype>
<defaultvalue>afk:away <defaultvalue>afk:away
col:color col:color
msg:whisper</defaultvalue> msg:whisper
gunban:ungban
gunmute:ungmute</defaultvalue>
</option> </option>
<!-- general chat options end --> <!-- general chat options end -->

View File

@ -5,7 +5,7 @@
<packagedescription><![CDATA[Chat for WoltLab Community Framework™.]]></packagedescription> <packagedescription><![CDATA[Chat for WoltLab Community Framework™.]]></packagedescription>
<packagedescription language="de"><![CDATA[Chat für WoltLab Community Framework™.]]></packagedescription> <packagedescription language="de"><![CDATA[Chat für WoltLab Community Framework™.]]></packagedescription>
<isapplication>1</isapplication> <isapplication>1</isapplication>
<version>3.0.0 Alpha 58</version><!-- Codename: Codenames are overrated --> <version>3.0.0 Alpha 80</version><!-- Codename: Codenames are overrated -->
<date>2011-11-26</date> <date>2011-11-26</date>
<license><![CDATA[Creative Commons Attribution-NonCommercial-ShareAlike <http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode>]]></license> <license><![CDATA[Creative Commons Attribution-NonCommercial-ShareAlike <http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode>]]></license>
</packageinformation> </packageinformation>

View File

@ -5,7 +5,7 @@
{include file='headInclude' sandbox=false} {include file='headInclude' sandbox=false}
{include file='javascriptInclude' application='chat'} {include file='javascriptInclude' application='chat'}
<script type="text/javascript"> <script>
//<![CDATA[ //<![CDATA[
(function ($, window) { (function ($, window) {
$(function(){ $(function(){
@ -15,27 +15,33 @@
'chat.general.ban': '{lang}chat.general.ban{/lang}', 'chat.general.ban': '{lang}chat.general.ban{/lang}',
'chat.general.profile': '{lang}chat.general.profile{/lang}', 'chat.general.profile': '{lang}chat.general.profile{/lang}',
'chat.general.notify.title': '{lang}chat.general.notify.title{/lang}', 'chat.general.notify.title': '{lang}chat.general.notify.title{/lang}',
'chat.general.privateChannelTopic': '{lang}chat.general.privateChannelTopic{/lang}',
'chat.general.closePrivateChannel': '{lang}chat.general.closePrivateChannel{/lang}',
'chat.general.closeTopic': '{lang}chat.general.closeTopic{/lang}',
'chat.error.onMessageLoad': '{lang}chat.error.onMessageLoad{/lang}', 'chat.error.onMessageLoad': '{lang}chat.error.onMessageLoad{/lang}',
'chat.error.duplicateTab': '{lang}chat.error.duplicateTab{/lang}', 'chat.error.duplicateTab': '{lang}chat.error.duplicateTab{/lang}',
'chat.error.join': '{lang}chat.error.join{/lang}', 'chat.error.join': '{lang}chat.error.join{/lang}',
'chat.error.reload': '{lang}chat.error.reload{/lang}' 'chat.error.reload': '{lang}chat.error.reload{/lang}'
}); });
{event name='beforeInit'}
// Boot the chat // Boot the chat
{if MODULE_SMILEY}WCF.TabMenu.init();{/if} {if MODULE_SMILEY}WCF.TabMenu.init();{/if}
new WCF.Message.Smilies(); new WCF.Message.Smilies();
{capture assign='messageTemplate'}{include application='chat' file='message'}{/capture} {capture assign='messageTemplate'}{include application='chat' file='message'}{/capture}
{capture assign='userTemplate'}{include application='chat' file='userListUser'}{/capture} {capture assign='userTemplate'}{include application='chat' file='userListUser'}{/capture}
be.bastelstu.Chat.init( var config = {
{$roomID},
{
reloadTime: {@CHAT_RELOADTIME}, reloadTime: {@CHAT_RELOADTIME},
messageURL: '{link application="chat" controller="NewMessages"}{/link}', messageURL: '{link application="chat" controller="NewMessages"}{/link}',
installedCommands: [ {implode from=$commands item='command'}'{$command|encodeJS}'{/implode} ] installedCommands: [ {implode from=$commands item='command'}'{$command|encodeJS}'{/implode} ],
}, messageTypes: { {implode from=$messageTypes key='name' item='messageType'}'{$name|substr:5|encodeJS}': '{$messageType|encodeJS}'{/implode} }
};
{event name='beforeInit'}
be.bastelstu.Chat.init(
{$roomID},
config,
new WCF.Template('{literal}{if $newMessageCount}({#$newMessageCount}) {/if}{$title} - {/literal}{"chat.general.title"|language|encodeJS} - {PAGE_TITLE|language|encodeJS}'), new WCF.Template('{literal}{if $newMessageCount}({#$newMessageCount}) {/if}{$title} - {/literal}{"chat.general.title"|language|encodeJS} - {PAGE_TITLE|language|encodeJS}'),
new WCF.Template('{@$messageTemplate|encodeJS}'), new WCF.Template('{@$messageTemplate|encodeJS}'),
new WCF.Template('{@$userTemplate|encodeJS}') new WCF.Template('{@$userTemplate|encodeJS}')
@ -54,36 +60,31 @@
})(jQuery, this); })(jQuery, this);
//]]> //]]>
</script> </script>
<style type="text/css">
/*<![CDATA[*/
.timsChatMessage::before {
content: "";
}
{assign var='type' value='\chat\data\message\Message::TYPE_'}
.timsChatMessage{$type|concat:'JOIN'|constant}::before {
content: "\f090";
}
.timsChatMessage{$type|concat:'LEAVE'|constant}::before {
content: "\f08b";
}
.timsChatMessage{$type|concat:'INFORMATION'|constant}::before {
content: "\f05a";
}
/*]]>*/
</style>
</head> </head>
<body id="tpl{$templateName|ucfirst}"> <body id="tpl{$templateName|ucfirst}">
{capture assign='sidebar'}{include application='chat' file='sidebar'}{/capture} {capture assign='sidebar'}{include application='chat' file='sidebar'}{/capture}
{include file='header' sandbox=false sidebarOrientation='right'} {include file='header' sandbox=false sidebarOrientation='right'}
<div id="timsChatTopic" class="container{if $room->topic|language === ''} empty{/if}">{$room->topic|language}</div> <div id="timsChatTopic" class="container{if $room->topic|language === ''} empty{/if}">
<span class="icon icon16 icon-remove jsTopicCloser jsTooltip" title="{lang}chat.general.closeTopic{/lang}"></span>
<span class="topic">{$room->topic|language}</span>
</div>
<div id="timsChatMessageContainer" class="timsChatMessageContainer marginTop container active"> <div id="privateChannelsMenu">
<ul>
<li id="privateChannel0" class="privateChannel active" data-private-channel-id="0">
<span class="userAvatar framed small">
<span class="icon icon16 icon-comment-alt jsTooltip" title="{lang}chat.general.room{/lang}"></span>
</span>
<span class="userAvatar framed large">
<span class="icon icon32 icon-comment-alt jsTooltip" title="{lang}chat.general.room{/lang}"></span>
</span>
</li>
</ul>
</div>
<div id="timsChatMessageContainer0" class="timsChatMessageContainer marginTop container active" data-user-id="0">
<p class="error noJsOnly" style="display: none;">{lang}chat.general.noJs{/lang}</p> <p class="error noJsOnly" style="display: none;">{lang}chat.general.noJs{/lang}</p>
<ul> <ul>
</ul> </ul>
@ -93,7 +94,7 @@
<fieldset> <fieldset>
<dl class="wide" id="timsChatInputContainer"> <dl class="wide" id="timsChatInputContainer">
<dd> <dd>
<input id="timsChatInput" accesskey="w" type="text" class="inputText long" name="text" autocomplete="off" maxlength="{@CHAT_MAX_LENGTH}" disabled="disabled" required="required" placeholder="{lang}chat.general.submit.default{/lang}" /> <input id="timsChatInput" accesskey="w" type="text" class="inputText long" name="text" autocomplete="off" maxlength="{@CHAT_MAX_LENGTH}" disabled="disabled" placeholder="{lang}chat.general.submit.default{/lang}" />
<small class="innerError" style="display: none;">Lorem ipsum dolor sit amet.</small> <small class="innerError" style="display: none;">Lorem ipsum dolor sit amet.</small>
</dd> </dd>
</dl> </dl>
@ -105,7 +106,7 @@
{include file='messageFormSmilies' wysiwygSelector=''} {include file='messageFormSmilies' wysiwygSelector=''}
{/if} {/if}
<nav id="timsChatOptions" class="marginTop buttonGroupNavigation"> <nav id="timsChatOptions" class="marginTop jsMobileNavigation buttonGroupNavigation">
<span class="invisible">{lang}chat.general.controls{/lang}</span> <span class="invisible">{lang}chat.general.controls{/lang}</span>
<ul class="smallButtons buttonGroup"> <ul class="smallButtons buttonGroup">
<li><a id="timsChatAutoscroll" accesskey="d" class="button active timsChatToggle jsTooltip" title="{lang}chat.general.scroll{/lang}" data-status="1"><span class="icon icon16 icon-arrow-down"></span><span class="invisible">{lang}chat.general.scroll{/lang}</span></a></li>{* <li><a id="timsChatAutoscroll" accesskey="d" class="button active timsChatToggle jsTooltip" title="{lang}chat.general.scroll{/lang}" data-status="1"><span class="icon icon16 icon-arrow-down"></span><span class="invisible">{lang}chat.general.scroll{/lang}</span></a></li>{*

View File

@ -35,7 +35,7 @@
{@$roomList} {@$roomList}
</ul> </ul>
</div> </div>
<script type="text/javascript"> <script>
//<![CDATA[ //<![CDATA[
(function($, window, undefined) { (function($, window, undefined) {
proxy = new WCF.Action.Proxy({ proxy = new WCF.Action.Proxy({

View File

@ -1,3 +1,3 @@
<script type="text/javascript" src="{$__wcf->getPath('chat')}js/be.bastelstu.Chat{if !ENABLE_DEBUG_MODE}.min{/if}.js?version={PACKAGE_VERSION|rawurlencode}"></script> <script src="{$__wcf->getPath('chat')}js/be.bastelstu.Chat{if !ENABLE_DEBUG_MODE}.min{/if}.js?version={PACKAGE_VERSION|rawurlencode}"></script>
<!--script type="text/javascript" src="{$__wcf->getPath('chat')}js/be.bastelstu.Chat.Log{if !ENABLE_DEBUG_MODE}.min{/if}.js?version={PACKAGE_VERSION|rawurlencode}"></script--> <!--script src="{$__wcf->getPath('chat')}js/be.bastelstu.Chat.Log{if !ENABLE_DEBUG_MODE}.min{/if}.js?version={PACKAGE_VERSION|rawurlencode}"></script-->
{event name='javascript'} {event name='javascript'}

View File

@ -4,7 +4,7 @@
</ul> </ul>
</div> </div>
</div> </div>
<script type="text/javascript"> <script>
//<![CDATA[ //<![CDATA[
var log = new be.bastelstu.Chat.Log(chat); var log = new be.bastelstu.Chat.Log(chat);
log.handleMessages([ log.handleMessages([

View File

@ -1,24 +1,54 @@
{literal} {literal}
<time>{@$formattedTime}</time> <div class="messageIcon">
<span class="usernameContainer"> {if $message.type == $messageTypes.LEAVE || $message.type == $messageTypes.JOIN}
<span class="username">{* <span class="icon icon16 icon-{if $message.type == $messageTypes.LEAVE}signout{else}signin{/if}"></span>
*}{if $type != 7}{* {/if}
*}{@$formattedUsername}{* </div>
*}{else} <div class="innerMessageContainer{if $message.type == $messageTypes.NORMAL || $message.type == $messageTypes.WHISPER || $message.type == $messageTypes.INFORMATION} bubble{/if}{if $message.type == $messageTypes.WHISPER && $message.sender != $__wcf.User.userID} right{/if}">
{if $receiver == WCF.User.userID} <div class="userAvatar framed">
{@$formattedUsername} {if $message.type != $messageTypes.INFORMATION}
{if $message.type == $messageTypes.NORMAL || $message.type == $messageTypes.WHISPER}
{@$message.avatar[32]}
{else}
{@$message.avatar[16]}
{/if}
{else}
<span class="icon icon32 icon-info-sign"></span>
{/if}
</div>
<div class="innerMessage">
<span class="username">
{if ($message.type == $messageTypes.WHISPER && $message.sender == WCF.User.userID) || $message.type != $messageTypes.WHISPER}
{@$message.formattedUsername}
{else}
{$message.additionalData.receiverUsername}
{/if}
{if $message.type == $messageTypes.WHISPER}
<span class="icon icon16 icon-double-angle-{if $message.sender == WCF.User.userID}right{else}left{/if} jsTooltip" title="{/literal}{lang}chat.ui.whispers{/lang}{literal}" onclick="be.bastelstu.Chat.insertText('/whisper {if $message.receiver == WCF.User.userID}{$message.username.replace("\\", "\\\\").replace("'", "\\'")}{else}{$message.additionalData.receiverUsername.replace("\\", "\\\\").replace("'", "\\'")}{/if}, ', { append: false });"></span>
{if ($message.type == $messageTypes.WHISPER && $message.sender == WCF.User.userID) || $message.type != $messageTypes.WHISPER}
{$message.additionalData.receiverUsername}
{else}
{@$message.formattedUsername}
{/if} {/if}
<span class="icon icon16 icon-double-angle-right jsTooltip" title="{/literal}{lang}chat.ui.whispers{/lang}{literal}" onclick="be.bastelstu.Chat.insertText('/whisper {if $receiver == WCF.User.userID}{$username.replace("\\", "\\\\").replace("'", "\\'")}{else}{$additionalData.receiverUsername.replace("\\", "\\\\").replace("'", "\\'")}{/if}, ', { append: false });"></span>
{if $receiver != WCF.User.userID}
{$additionalData.receiverUsername}{/if}{*
*}{/if}{*
*}</span>{*
*}{if $receiver != WCF.User.userID}{*
*}<span class="separator">{$separator}</span>
{/if} {/if}
</span> </span>
<span class="text">{@$formattedMessage}</span>
<time>{@$message.formattedTime}</time>
{if $message.type == $messageTypes.NORMAL || $message.type == $messageTypes.WHISPER}
<ul class="text">
<li>
{if $message.isFollowUp} <time>{@$message.formattedTime}</time>{/if}
{@$message.formattedMessage}
</li>
</ul>
{else}
<span class="text">{@$message.formattedMessage}</span>
{/if}
</div>
<span class="markContainer"> <span class="markContainer">
<input type="checkbox" value="{@$messageID}" /> <input type="checkbox" value="{@$message.messageID}" />
</span> </span>
</div>
{/literal} {/literal}

View File

@ -104,6 +104,12 @@
<defaultvalue>0</defaultvalue> <defaultvalue>0</defaultvalue>
<admindefaultvalue>1</admindefaultvalue> <admindefaultvalue>1</admindefaultvalue>
</option> </option>
<option name="admin.chat.canManageSuspensions">
<categoryname>admin.chat</categoryname>
<optiontype>boolean</optiontype>
<defaultvalue>0</defaultvalue>
<admindefaultvalue>1</admindefaultvalue>
</option>
</options> </options>
</import> </import>
</data> </data>