Initial import

This commit is contained in:
Tim Düsterhus 2018-08-17 00:30:59 +02:00
commit 317ee29461
273 changed files with 20383 additions and 0 deletions

17
.babelrc Normal file
View File

@ -0,0 +1,17 @@
{ "presets": [ [ "env"
, { "targets": { "browsers": [ "last 2 chrome versions"
, "last 2 chromeandroid versions"
, "firefox esr"
, "not firefox 52"
, "last 2 firefox versions"
, "edge >= 15"
, "safari >= 11"
, "ios >= 11"
]
}
, "debug": true
, "include": [ ]
}
]
]
}

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
node_modules
be.bastelstu.chat.tar.gz
be.bastelstu.chat.tar
files.tar
files_wcf.tar
acptemplates.tar
templates.tar
Bastelstu.be.Chat.js
Bastelstu.be.Chat.babel.js
files_wcf/js/Bastelstu.be.Chat.min.js

104
LICENSE Normal file
View File

@ -0,0 +1,104 @@
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
“Business Source License” is a trademark of MariaDB Corporation Ab.
Parameters
Licensor: Tim Düsterhus
Licensed Work: Tims Chat 4.0
The Licensed Work is (c) 2010-2018 Tim Düsterhus
Additional Use Grant: You may use the Licensed Work when your application
uses the Licensed Work for a purpose that does neither
directly or indirectly generate revenue.
Change Date: 2022-08-16
Change License: Version 2 or later of the GNU General Public License as
published by the Free Software Foundation.
For information about alternative licensing arrangements for the Software,
please email: tim <at the domain> bastelstu.be
Notice
The Business Source License (this document, or the “License”) is not an Open
Source license. However, the Licensed Work will eventually be made available
under an Open Source License, as stated in this License.
For more information on the use of the Business Source License for MariaDB
products, please visit the MariaDB Business Source License FAQ at
https://mariadb.com/bsl-faq-mariadb.
For more information on the use of the Business Source License generally,
please visit the Adopting and Developing Business Source License FAQ at
https://mariadb.com/bsl-faq-adopting.
-----------------------------------------------------------------------------
Business Source License 1.1
Terms
The Licensor hereby grants you the right to copy, modify, create derivative
works, redistribute, and make non-production use of the Licensed Work. The
Licensor may make an Additional Use Grant, above, permitting limited
production use.
Effective on the Change Date, or the fourth anniversary of the first publicly
available distribution of a specific version of the Licensed Work under this
License, whichever comes first, the Licensor hereby grants you rights under
the terms of the Change License, and the rights granted in the paragraph
above terminate.
If your use of the Licensed Work does not comply with the requirements
currently in effect as described in this License, you must purchase a
commercial license from the Licensor, its affiliated entities, or authorized
resellers, or you must refrain from using the Licensed Work.
All copies of the original and modified Licensed Work, and derivative works
of the Licensed Work, are subject to this License. This License applies
separately for each version of the Licensed Work and the Change Date may vary
for each version of the Licensed Work released by Licensor.
You must conspicuously display this License on each original or modified copy
of the Licensed Work. If you receive the Licensed Work in original or
modified form from a third party, the terms and conditions set forth in this
License apply to your use of that work.
Any use of the Licensed Work in violation of this License will automatically
terminate your rights under this License for the current and all other
versions of the Licensed Work.
This License does not grant you any right in any trademark or logo of
Licensor or its affiliates (provided that you may use a trademark or logo of
Licensor as expressly required by this License).
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
TITLE.
MariaDB hereby grants you permission to use this Licenses text to license
your works, and to refer to it using the trademark “Business Source License”,
as long as you comply with the Covenants of Licensor below.
Covenants of Licensor
In consideration of the right to use this Licenses text and the “Business
Source License” name and trademark, Licensor covenants to MariaDB, and to all
other recipients of the licensed work to be provided by Licensor:
1. To specify as the Change License the GPL Version 2.0 or any later version,
or a license that is compatible with GPL Version 2.0 or a later version,
where “compatible” means that software provided under the Change License can
be included in a program with software provided under GPL Version 2.0 or a
later version. Licensor may specify additional Change Licenses without
limitation.
2. To either: (a) specify an additional grant of rights to use that does not
impose any additional restriction on the right granted in this License, as
the Additional Use Grant; or (b) insert the text “None”.
3. To specify a Change Date.
4. Not to modify this License in any other way.

43
Makefile Normal file
View File

@ -0,0 +1,43 @@
FILES = $(shell find files -type f)
WCF_FILES = $(shell find files_wcf -type f)
JS_MODULE_FILES = $(shell find files_wcf/js/Bastelstu.be -type f)
all: be.bastelstu.chat.tar be.bastelstu.chat.tar.gz
be.bastelstu.chat.tar.gz: be.bastelstu.chat.tar
gzip -9 < $< > $@
be.bastelstu.chat.tar: files.tar files_wcf.tar acptemplates.tar templates.tar *.xml LICENSE sql/*.sql language/*.xml
tar cvf be.bastelstu.chat.tar --numeric-owner --exclude-vcs -- $^
files.tar: $(FILES)
files_wcf.tar: $(WCF_FILES) files_wcf/js/Bastelstu.be.Chat.min.js
acptemplates.tar: acptemplates/*.tpl
templates.tar: templates/*.tpl
%.tar:
tar cvf $@ --numeric-owner --exclude-vcs -C $* -- $(^:$*/%=%)
files_wcf/js/Bastelstu.be.Chat.min.js: Bastelstu.be.Chat.babel.js
yarn run terser --comments '/Copyright|stackoverflow/' -m -c pure_funcs=[console.debug] --verbose --timings -o $@ $^
Bastelstu.be.Chat.babel.js: Bastelstu.be.Chat.js .babelrc
yarn run babel $< --out-file $@
Bastelstu.be.Chat.js: $(JS_MODULE_FILES)
yarn run r.js -o require.build.js
clean:
-rm -f files.tar
-rm -f files_wcf.tar
-rm -f templates.tar
-rm -f acptemplates.tar
-rm -f Bastelstu.be.Chat.js
-rm -f Bastelstu.be.Chat.babel.js
-rm -f files_wcf/js/Bastelstu.be.Chat.min.js
distclean: clean
-rm -f be.bastelstu.chat.tar
-rm -f be.bastelstu.chat.tar.gz
.PHONY: distclean clean

48
aclOption.xml Normal file
View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/maelstrom/aclOption.xsd">
<import>
<categories>
<category name="user">
<objecttype>be.bastelstu.chat.room</objecttype>
</category>
<category name="mod">
<objecttype>be.bastelstu.chat.room</objecttype>
</category>
</categories>
<options>
<option name="user.canSee">
<objecttype>be.bastelstu.chat.room</objecttype>
<categoryname>user</categoryname>
</option>
<option name="user.canSeeLog">
<objecttype>be.bastelstu.chat.room</objecttype>
<categoryname>user</categoryname>
</option>
<option name="user.canWrite">
<objecttype>be.bastelstu.chat.room</objecttype>
<categoryname>user</categoryname>
</option>
<option name="mod.canIgnoreUserLimit">
<objecttype>be.bastelstu.chat.room</objecttype>
<categoryname>mod</categoryname>
</option>
<option name="mod.canMute">
<objecttype>be.bastelstu.chat.room</objecttype>
<categoryname>mod</categoryname>
</option>
<option name="mod.canIgnoreMute">
<objecttype>be.bastelstu.chat.room</objecttype>
<categoryname>mod</categoryname>
</option>
<option name="mod.canBan">
<objecttype>be.bastelstu.chat.room</objecttype>
<categoryname>mod</categoryname>
</option>
<option name="mod.canIgnoreBan">
<objecttype>be.bastelstu.chat.room</objecttype>
<categoryname>mod</categoryname>
</option>
</options>
</import>
</data>

42
acpMenu.xml Normal file
View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/maelstrom/acpMenu.xsd">
<import>
<acpmenuitem name="chat.acp.menu.link.chat">
<parent>wcf.acp.menu.link.application</parent>
</acpmenuitem>
<acpmenuitem name="chat.acp.menu.link.room.list">
<controller>chat\acp\page\RoomListPage</controller>
<parent>chat.acp.menu.link.chat</parent>
<permissions>admin.chat.canManageRoom</permissions>
<showorder>1</showorder>
</acpmenuitem>
<acpmenuitem name="chat.acp.menu.link.room.add">
<controller>chat\acp\form\RoomAddForm</controller>
<parent>chat.acp.menu.link.room.list</parent>
<permissions>admin.chat.canManageRoom</permissions>
<icon>fa-plus</icon>
</acpmenuitem>
<acpmenuitem name="chat.acp.menu.link.command.trigger.list">
<controller>chat\acp\page\CommandTriggerListPage</controller>
<parent>chat.acp.menu.link.chat</parent>
<permissions>admin.chat.canManageTriggers</permissions>
</acpmenuitem>
<acpmenuitem name="chat.acp.menu.link.command.trigger.add">
<controller>chat\acp\form\CommandTriggerAddForm</controller>
<parent>chat.acp.menu.link.command.trigger.list</parent>
<permissions>admin.chat.canManageTriggers</permissions>
<icon>fa-plus</icon>
</acpmenuitem>
<acpmenuitem name="chat.acp.menu.link.suspension.list">
<controller>chat\acp\page\SuspensionListPage</controller>
<parent>chat.acp.menu.link.chat</parent>
<permissions>admin.chat.canManageSuspensions</permissions>
</acpmenuitem>
</import>
</data>

View File

@ -0,0 +1,5 @@
<dl>
<dt>{lang}chat.acp.index.system.software.chatVersion{/lang}</dt>
<dd>{$__chat->getPackage()->packageVersion}</dd>
</dl>

View File

@ -0,0 +1,72 @@
{include file='header' pageTitle='chat.acp.command.trigger.'|concat:$action}
<header class="contentHeader">
<div class="contentHeaderTitle">
<h1 class="contentTitle">{lang}chat.acp.command.trigger.{$action}{/lang}</h1>
</div>
<nav class="contentHeaderNavigation">
<ul>
<li><a href="{link application='chat' controller='CommandTriggerList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}chat.acp.command.trigger.list{/lang}</span></a></li>
{event name='contentHeaderNavigation'}
</ul>
</nav>
</header>
{include file='formError'}
{if $success|isset}
<p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
{/if}
<form method="post" action="{if $action == 'add'}{link application='chat' controller='CommandTriggerAdd'}{/link}{else}{link application='chat' controller='CommandTriggerEdit' id=$triggerID}{/link}{/if}">
<div class="section">
<div class="section">
<dl{if $errorField == 'commandTrigger'} class="formError"{/if}>
<dt><label for="commandTrigger">{lang}chat.acp.command.trigger{/lang}</label></dt>
<dd>
<input type="text" id="commandTrigger" name="commandTrigger" value="{$commandTrigger}" autofocus class="medium">
{if $errorField == 'commandTrigger'}
<small class="innerError">
{if $errorType == 'empty'}
{lang}wcf.global.form.error.empty{/lang}
{else}
{lang}chat.acp.command.trigger.commandTrigger.error.{@$errorType}{/lang}
{/if}
</small>
{/if}
</dd>
</dl>
<dl{if $errorField == 'className'} class="formError"{/if}>
<dt><label for="className">{lang}chat.acp.command.className{/lang}</label></dt>
<dd>
<select id="className" name="className">
{foreach from=$availableCommands item=$command}
<option value="{$command->className}"{if $command->className === $className} selected{/if}>{$command->className}</option>
{/foreach}
</select>
{if $errorField == 'className'}
<small class="innerError">
{if $errorType == 'empty'}
{lang}wcf.global.form.error.empty{/lang}
{else}
{lang}chat.acp.command.trigger.className.error.{@$errorType}{/lang}
{/if}
</small>
{/if}
</dd>
</dl>
</div>
</div>
<div class="formSubmit">
<input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
{@SECURITY_TOKEN_INPUT_TAG}
</div>
</form>
{include file='footer'}

View File

@ -0,0 +1,86 @@
{include file='header' pageTitle='chat.acp.command.trigger.list'}
<script data-relocate="true">
$(function() {
new WCF.Action.Delete('chat\\data\\command\\CommandTriggerAction', '.jsTriggerRow');
});
</script>
<header class="contentHeader">
<div class="contentHeaderTitle">
<h1 class="contentTitle">{lang}chat.acp.command.trigger.list{/lang}</h1>
</div>
<nav class="contentHeaderNavigation">
<ul>
<li><a href="{link controller='CommandTriggerAdd' application='chat'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}chat.acp.command.trigger.add{/lang}</span></a></li>
{event name='contentHeaderNavigation'}
</ul>
</nav>
</header>
{hascontent}
<div class="paginationTop">
{content}{pages print=true assign=pagesLinks controller="CommandTriggerList" application="chat" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}{/content}
</div>
{/hascontent}
{hascontent}
<div class="section tabularBox">
<table class="table">
<thead>
<tr>
<th class="columnID columnTriggerID{if $sortField == 'triggerID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='CommandTriggerList' application='chat'}pageNo={@$pageNo}&sortField=triggerID&sortOrder={if $sortField == 'triggerID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
<th class="columnTitle columnTrigger{if $sortField == 'commandTrigger'} active {@$sortOrder}{/if}"><a href="{link controller='CommandTriggerList' application='chat'}pageNo={@$pageNo}&sortField=commandTrigger&sortOrder={if $sortField == 'commandTrigger' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}chat.acp.command.trigger{/lang}</a></th>
<th class="columnText columnClassName{if $sortField == 'className'} active {@$sortOrder}{/if}"><a href="{link controller='CommandTriggerList' application='chat'}pageNo={@$pageNo}&sortField=className&sortOrder={if $sortField == 'className' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}chat.acp.command.className{/lang}</a></th>
{event name='columnHeads'}
</tr>
</thead>
<tbody>
{content}
{foreach from=$objects item=trigger}
<tr class="jsTriggerRow">
<td class="columnIcon">
<a href="{link controller='CommandTriggerEdit' object=$trigger application='chat'}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
<span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$trigger->triggerID}" data-confirm-message-html="{lang __encode=true}chat.acp.command.trigger.delete.sure{/lang}"></span>
{event name='rowButtons'}
</td>
<td class="columnID">{@$trigger->triggerID}</td>
<td class="columnTitle columnTrigger"><a href="{link controller='CommandTriggerEdit' object=$trigger application='chat'}{/link}">/{$trigger->commandTrigger}</a></td>
<td class="columnText columnClassName">{$trigger->className}</td>
{event name='columns'}
</tr>
{/foreach}
{/content}
</tbody>
</table>
</div>
{hascontentelse}
<p class="info">{lang}wcf.global.noItems{/lang}</p>
{/hascontent}
<footer class="contentFooter">
{hascontent}
<div class="paginationBottom">
{content}{@$pagesLinks}{/content}
</div>
{/hascontent}
<nav class="contentFooterNavigation">
<ul>
<li><a href="{link controller='CommandTriggerAdd' application='chat'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}chat.acp.command.trigger.add{/lang}</span></a></li>
{event name='contentFooterNavigation'}
</ul>
</nav>
</footer>
{include file='footer'}

113
acptemplates/roomAdd.tpl Normal file
View File

@ -0,0 +1,113 @@
{include file='header' pageTitle='chat.acp.room.'|concat:$action}
{include file='aclPermissions'}
{include file='multipleLanguageInputJavascript' elementIdentifier='title' forceSelection=false}
{include file='multipleLanguageInputJavascript' elementIdentifier='topic' forceSelection=false}
{if $roomID|isset}
{include file='aclPermissionJavaScript' containerID='aclContainer' objectTypeID=$aclObjectTypeID objectID=$roomID}
{else}
{include file='aclPermissionJavaScript' containerID='aclContainer' objectTypeID=$aclObjectTypeID}
{/if}
<header class="contentHeader">
<div class="contentHeaderTitle">
<h1 class="contentTitle">{lang}chat.acp.room.{$action}{/lang}</h1>
{if $action == 'edit'}<p class="contentHeaderDescription">{$room->getTitle()}</p>{/if}
</div>
<nav class="contentHeaderNavigation">
<ul>
<li><a href="{link application='chat' controller='RoomList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}chat.acp.room.list{/lang}</span></a></li>
{event name='contentHeaderNavigation'}
</ul>
</nav>
</header>
{include file='formError'}
{if $success|isset}
<p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
{/if}
<form method="post" action="{if $action == 'add'}{link application='chat' controller='RoomAdd'}{/link}{else}{link application='chat' controller='RoomEdit' id=$roomID}{/link}{/if}">
<div class="section">
<div class="section">
<dl{if $errorField == 'title'} class="formError"{/if}>
<dt><label for="title">{lang}wcf.global.title{/lang}</label></dt>
<dd>
<input type="text" id="title" name="title" value="{$i18nPlainValues['title']}" autofocus class="medium">
{if $errorField == 'title'}
<small class="innerError">
{if $errorType == 'empty'}
{lang}wcf.global.form.error.empty{/lang}
{elseif $errorType == 'multilingual'}
{lang}wcf.global.form.error.multilingual{/lang}
{else}
{lang}chat.acp.room.title.error.{@$errorType}{/lang}
{/if}
</small>
{/if}
</dd>
</dl>
<dl{if $errorField == 'topic'} class="formError"{/if}>
<dt><label for="topic">{lang}chat.acp.room.topic{/lang}</label></dt>
<dd>
<input type="text" id="topic" name="topic" value="{$i18nPlainValues['topic']}" class="medium">
{if $errorField == 'topic'}
<small class="innerError">
{if $errorType == 'empty'}
{lang}wcf.global.form.error.empty{/lang}
{else}
{lang}chat.acp.room.topic.error.{@$errorType}{/lang}
{/if}
</small>
{/if}
</dd>
</dl>
<dl{if $errorField == 'topicUseHtml'} class="formError"{/if}>
<dt></dt>
<dd>
<label><input type="checkbox" name="topicUseHtml" value="1"{if $topicUseHtml} checked{/if}> {lang}chat.acp.room.topicUseHtml{/lang}</label>
</dd>
</dl>
<dl{if $errorField == 'userLimit'} class="formError"{/if}>
<dt><label for="userLimit">{lang}chat.acp.room.userLimit{/lang}</label></dt>
<dd>
<input type="number" id="userLimit" name="userLimit" value="{$userLimit}" min="0" class="medium">
{if $errorField == 'userLimit'}
<small class="innerError">
{if $errorType == 'empty'}
{lang}wcf.global.form.error.empty{/lang}
{else}
{lang}chat.acp.room.userLimit.error.{@$errorType}{/lang}
{/if}
</small>
{/if}
</dd>
</dl>
</div>
<div class="section">
<dl id="aclContainer">
<dt>{lang}wcf.acl.permissions{/lang}</dt>
<dd></dd>
</dl>
</div>
</div>
<div class="formSubmit">
<input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
{@SECURITY_TOKEN_INPUT_TAG}
</div>
</form>
{include file='footer'}

81
acptemplates/roomList.tpl Normal file
View File

@ -0,0 +1,81 @@
{include file='header' pageTitle='chat.acp.room.list'}
<script data-relocate="true">
$(function() {
require([ 'WoltLabSuite/Core/Ui/Sortable/List' ], function (UiSortableList) {
new UiSortableList({ containerId: 'roomNodeList'
, className: 'chat\\data\\room\\RoomAction'
})
})
new WCF.Action.Delete('chat\\data\\room\\RoomAction', '#roomNodeList')
})
</script>
<header class="contentHeader">
<div class="contentHeaderTitle">
<h1 class="contentTitle">{lang}chat.acp.room.list{/lang}</h1>
</div>
<nav class="contentHeaderNavigation">
<ul>
<li><a href="{link controller='RoomAdd' application='chat'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}chat.acp.room.add{/lang}</span></a></li>
{event name='contentHeaderNavigation'}
</ul>
</nav>
</header>
{hascontent}
<div class="paginationTop">
{content}{pages print=true assign=pagesLinks controller="RoomList" application="chat" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}{/content}
</div>
{/hascontent}
{hascontent}
<div id="roomNodeList" class="section sortableListContainer">
<ol id="roomContainer0" class="sortableList" data-object-id="0">
{content}
{foreach from=$objects item=room}
<li class="sortableNode sortableNoNesting" data-object-id="{@$room->roomID}">
<span class="sortableNodeLabel">
<a href="{link controller='RoomEdit' application='chat' object=$room}{/link}">{$room}</a>
<span class="statusDisplay sortableButtonContainer">
<span class="icon icon16 fa-arrows sortableNodeHandle"></span>
<a href="{link controller='RoomEdit' application='chat' object=$room}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
<span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$room->roomID}" data-confirm-message-html="{lang __encode=true}chat.acp.room.delete.sure{/lang}"></span>
{event name='itemButtons'}
</span>
</span>
</li>
{/foreach}
{/content}
</ol>
</div>
<div class="formSubmit">
<button class="button buttonPrimary" data-type="submit">{lang}wcf.global.button.saveSorting{/lang}</button>
</div>
{hascontentelse}
<p class="info">{lang}wcf.global.noItems{/lang}</p>
{/hascontent}
<footer class="contentFooter">
{hascontent}
<div class="paginationBottom">
{content}{@$pagesLinks}{/content}
</div>
{/hascontent}
<nav class="contentFooterNavigation">
<ul>
<li><a href="{link controller='RoomAdd' application='chat'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}chat.acp.room.add{/lang}</span></a></li>
{event name='contentFooterNavigation'}
</ul>
</nav>
</footer>
{include file='footer'}

View File

@ -0,0 +1,192 @@
{include file='header' pageTitle='chat.acp.suspension.list'}
<script>
require([ 'Bastelstu.be/PromiseWrap/Ajax', 'Bastelstu.be/PromiseWrap/Ui/Confirmation', 'WoltLabSuite/Core/Dom/Traverse' ], function (Ajax, Confirmation, Traverse) {
elBySelAll('.jsRevokeButton:not(.disabled)', document, function (button) {
const row = Traverse.parentByClass(button, 'jsSuspensionRow')
if (row == null) {
throw new Error('Unreachable')
}
const objectID = row.dataset.objectId
const listener = function (event) {
Confirmation.show({
message: button.dataset.confirmMessageHtml,
messageIsHtml: true
}).then(function () {
const payload = { data: { className: 'chat\\data\\suspension\\SuspensionAction'
, actionName: 'revoke'
, objectIDs: [ objectID ]
}
}
return Ajax.apiOnce(payload)
}).then(function () {
button.classList.remove('pointer')
button.classList.add('disabled')
button.removeEventListener('click', listener)
})
}
button.addEventListener('click', listener)
})
})
</script>
<header class="contentHeader">
<div class="contentHeaderTitle">
<h1 class="contentTitle">{lang}chat.acp.suspension.list{/lang}</h1>
</div>
{hascontent}
<nav class="contentHeaderNavigation">
<ul>
{content}{event name='contentHeaderNavigation'}{/content}
</ul>
</nav>
{/hascontent}
</header>
<form method="post" action="{link controller='SuspensionList' application='chat'}{/link}">
<section class="section">
<h2 class="sectionTitle">{lang}wcf.global.filter{/lang}</h2>
<div class="row rowColGap formGrid">
<dl class="col-xs-12 col-md-4">
<dt></dt>
<dd>
<select name="roomID" id="roomID">
<option value=""{if $roomID === null} selected{/if}>{lang}chat.acp.suspension.room.all{/lang}</option>
<option value="0"{if $roomID === 0} selected{/if}>{lang}chat.acp.suspension.room.global{/lang}</option>
{htmlOptions options=$availableRooms selected=$roomID}
</select>
</dd>
</dl>
<dl class="col-xs-12 col-md-4">
<dt></dt>
<dd>
<select name="objectTypeID" id="objectTypeID">
<option value="">{lang}chat.acp.suspension.objectType.allTypes{/lang}</option>
{foreach from=$availableObjectTypes item=availableObjectType}
<option value="{$availableObjectType->objectTypeID}"{if $availableObjectType->objectTypeID == $objectTypeID} selected{/if}>{lang}chat.acp.suspension.type.{$availableObjectType->objectType}{/lang}</option>
{/foreach}
</select>
</dd>
</dl>
<dl class="col-xs-12 col-md-4">
<dt></dt>
<dd>
<input type="text" id="searchUsername" name="searchUsername" value="{$searchUsername}" placeholder="{lang}chat.acp.suspension.username{/lang}" class="long">
</dd>
</dl>
<dl class="col-xs-12 col-md-4">
<dt></dt>
<dd>
<input type="text" id="searchJudge" name="searchJudge" value="{$searchJudge}" placeholder="{lang}chat.acp.suspension.judge{/lang}" class="long">
</dd>
</dl>
<dl class="col-xs-12 col-md-4">
<dt></dt>
<dd>
<label><input name="showExpired" value="1" type="checkbox"{if $showExpired !== false} checked{/if}>{lang}chat.acp.suspension.showExpired{/lang}</label>
</dd>
</dl>
{event name='filterFields'}
</div>
<div class="formSubmit">
<input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
{@SECURITY_TOKEN_INPUT_TAG}
</div>
</section>
</form>
{capture assign=additionalParameters}{*
*}{if $userID !== null}&userID={$userID}{/if}{*
*}{if $judgeID !== null}&judgeID={$judgeID}{/if}{*
*}{if $roomID !== null}&roomID={$roomID}{/if}{*
*}{if $objectTypeID !== null}&objectTypeID={$objectTypeID}{/if}{*
*}{if $showExpired !== null}&showExpired={$showExpired}{/if}{*
*}{/capture}
{hascontent}
<div class="paginationTop">
{content}{pages print=true assign=pagesLinks controller="SuspensionList" application="chat" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder$additionalParameters"}{/content}
</div>
{/hascontent}
{hascontent}
<div class="section tabularBox">
<table class="table">
<thead>
<tr>
<th class="columnID columnSuspensionID{if $sortField == 'suspensionID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='SuspensionList' application='chat'}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="columnTitle columnObjectType">{lang}chat.acp.suspension.type{/lang}</th>
<th class="columnText columnUsername">{lang}chat.acp.suspension.username{/lang}</th>
<th class="columnText columnJudge">{lang}chat.acp.suspension.judge{/lang}</th>
<th class="columnText columnRoom">{lang}chat.acp.suspension.room{/lang}</th>
<th class="columnText columnTime{if $sortField == 'time'} active {@$sortOrder}{/if}"><a href="{link controller='SuspensionList' application='chat'}pageNo={@$pageNo}&sortField=time&sortOrder={if $sortField == 'time' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$additionalParameters}{/link}">{lang}chat.acp.suspension.time{/lang}</a></th>
<th class="columnText columnExpires{if $sortField == 'expiresSort'} active {@$sortOrder}{/if}"><a href="{link controller='SuspensionList' application='chat'}pageNo={@$pageNo}&sortField=expiresSort&sortOrder={if $sortField == 'expiresSort' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$additionalParameters}{/link}">{lang}chat.acp.suspension.expires{/lang}</a></th>
{event name='columnHeads'}
</tr>
</thead>
<tbody>
{content}
{foreach from=$objects item=suspension}
<tr class="jsSuspensionRow" data-object-id="{$suspension->suspensionID}">
<td class="columnIcon">
<span class="icon icon16 fa-undo{if !$suspension->isActive()} disabled{else} pointer{/if} jsRevokeButton" title="{lang}chat.acp.suspension.revoke{/lang}" data-confirm-message-html="{lang}chat.acp.suspension.revoke.sure{/lang}"></span>
{event name='rowButtons'}
</td>
<td class="columnID">{@$suspension->suspensionID}</td>
<td class="columnTitle columnObjectType"><a href="{link controller="SuspensionList" application="chat"}objectTypeID={$suspension->objectTypeID}{/link}">{lang}chat.acp.suspension.type.{$suspension->getSuspensionType()->objectType}{/lang}</a></td>
<td class="columnText columnUsername"><a href="{link controller="SuspensionList" application="chat"}userID={$suspension->userID}{/link}">{$suspension->getUser()->username}</a></td>
<td class="columnText columnJudge"><a href="{link controller="SuspensionList" application="chat"}judgeID={$suspension->judgeID}{/link}">{$suspension->judge}</a></td>
<td class="columnText columnRoom"><a href="{link controller="SuspensionList" application="chat"}roomID={$suspension->roomID}{/link}">{if $suspension->getRoom() !== null}{$suspension->getRoom()}{else}-{/if}</a></td>
<td class="columnText columnTime">{@$suspension->time|time}</td>
<td class="columnText columnExpires">
{assign var='isActive' value=$suspension->isActive()}
{if $isActive}<strong>{/if}
{if $suspension->expires !== null}{@$suspension->expires|time}{else}{lang}chat.acp.suspension.expires.forever{/lang}{/if}
{if $isActive}</strong>{/if}
{if $suspension->revoked !== null}
<br>{lang}chat.acp.suspension.revoked{/lang}
{/if}
</td>
{event name='columns'}
</tr>
{/foreach}
{/content}
</tbody>
</table>
</div>
{hascontentelse}
<p class="info">{lang}wcf.global.noItems{/lang}</p>
{/hascontent}
<footer class="contentFooter">
{hascontent}
<div class="paginationBottom">
{content}{@$pagesLinks}{/content}
</div>
{/hascontent}
{hascontent}
<nav class="contentFooterNavigation">
<ul>
{content}{event name='contentFooterNavigation'}{/content}
</ul>
</nav>
{/hascontent}
</footer>
{include file='footer'}

42
box.xml Normal file
View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/vortex/box.xsd">
<import>
<box identifier="be.bastelstu.chat.roomListDashboard">
<name language="de">Chaträume (Inhaltsbereich)</name>
<name language="en">Chat Rooms (Content)</name>
<boxType>system</boxType>
<objectType>be.bastelstu.chat.roomList</objectType>
<position>contentTop</position>
<showHeader>1</showHeader>
<visibleEverywhere>0</visibleEverywhere>
<visibilityExceptions>
<page>com.woltlab.wcf.Dashboard</page>
</visibilityExceptions>
<content language="de">
<title>Chaträume</title>
</content>
<content language="en">
<title>Chat Rooms</title>
</content>
</box>
<box identifier="be.bastelstu.chat.roomListSidebar">
<name language="de">Chaträume (Seitenleiste)</name>
<name language="en">Chat Rooms (Sidebar)</name>
<boxType>system</boxType>
<objectType>be.bastelstu.chat.roomList</objectType>
<position>sidebarRight</position>
<showHeader>1</showHeader>
<visibleEverywhere>0</visibleEverywhere>
<visibilityExceptions>
<page>be.bastelstu.chat.Room</page>
</visibilityExceptions>
<content language="de">
<title>Chaträume</title>
</content>
<content language="en">
<title>Chat Rooms</title>
</content>
</box>
</import>
</data>

103
chatCommand.xml Normal file
View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<import>
<command name="away">
<classname>chat\system\command\AwayCommand</classname>
<triggers>
<trigger>away</trigger>
</triggers>
</command>
<command name="back">
<classname>chat\system\command\BackCommand</classname>
</command>
<command name="ban">
<classname>chat\system\command\BanCommand</classname>
<triggers>
<trigger>ban</trigger>
</triggers>
</command>
<command name="broadcast">
<classname>chat\system\command\BroadcastCommand</classname>
<triggers>
<trigger>broadcast</trigger>
</triggers>
</command>
<command name="color">
<classname>chat\system\command\ColorCommand</classname>
<triggers>
<trigger>color</trigger>
</triggers>
</command>
<command name="info">
<classname>chat\system\command\InfoCommand</classname>
<triggers>
<trigger>info</trigger>
</triggers>
</command>
<command name="me">
<classname>chat\system\command\MeCommand</classname>
<triggers>
<trigger>me</trigger>
</triggers>
</command>
<command name="mute">
<classname>chat\system\command\MuteCommand</classname>
<triggers>
<trigger>mute</trigger>
</triggers>
</command>
<command name="plain">
<classname>chat\system\command\PlainCommand</classname>
</command>
<command name="team">
<classname>chat\system\command\TeamCommand</classname>
<triggers>
<trigger>team</trigger>
</triggers>
</command>
<command name="temproom">
<classname>chat\system\command\TemproomCommand</classname>
<triggers>
<trigger>temproom</trigger>
</triggers>
</command>
<command name="unban">
<classname>chat\system\command\UnbanCommand</classname>
<triggers>
<trigger>unban</trigger>
</triggers>
</command>
<command name="unmute">
<classname>chat\system\command\UnmuteCommand</classname>
<triggers>
<trigger>unmute</trigger>
</triggers>
</command>
<command name="where">
<classname>chat\system\command\WhereCommand</classname>
<triggers>
<trigger>where</trigger>
</triggers>
</command>
<command name="whisper">
<classname>chat\system\command\WhisperCommand</classname>
<triggers>
<trigger>whisper</trigger>
</triggers>
</command>
</import>
</data>

166
eventListener.xml Normal file
View File

@ -0,0 +1,166 @@
<?xml version="1.0" encoding="UTF-8"?>
<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/maelstrom/eventListener.xsd">
<import>
<!-- Hourly Cleanup -->
<eventlistener name="hourlyCleanUpUser">
<eventclassname>wcf\system\cronjob\HourlyCleanUpCronjob</eventclassname>
<eventname>execute</eventname>
<listenerclassname>chat\system\event\listener\HourlyCleanUpCronjobExecuteChatCleanUpListener</listenerclassname>
<environment>user</environment>
</eventlistener>
<eventlistener name="hourlyCleanUpAdmin">
<eventclassname>wcf\system\cronjob\HourlyCleanUpCronjob</eventclassname>
<eventname>execute</eventname>
<listenerclassname>chat\system\event\listener\HourlyCleanUpCronjobExecuteChatCleanUpListener</listenerclassname>
<environment>admin</environment>
</eventlistener>
<!-- Temprooms -->
<eventlistener name="temproomHourlyCleanUpUser">
<eventclassname>wcf\system\cronjob\HourlyCleanUpCronjob</eventclassname>
<eventname>execute</eventname>
<listenerclassname>chat\system\event\listener\HourlyCleanUpCronjobExecuteTemproomListener</listenerclassname>
<environment>user</environment>
</eventlistener>
<eventlistener name="temproomHourlyCleanUpAdmin">
<eventclassname>wcf\system\cronjob\HourlyCleanUpCronjob</eventclassname>
<eventname>execute</eventname>
<listenerclassname>chat\system\event\listener\HourlyCleanUpCronjobExecuteTemproomListener</listenerclassname>
<environment>admin</environment>
</eventlistener>
<eventlistener name="temproomCanSee">
<eventclassname>chat\data\room\Room</eventclassname>
<eventname>canSee</eventname>
<listenerclassname>chat\system\event\listener\RoomCanSeeTemproomListener</listenerclassname>
<environment>user</environment>
</eventlistener>
<eventlistener name="temproomRoomList">
<eventclassname>chat\acp\page\RoomListPage</eventclassname>
<eventname>calculateNumberOfPages</eventname>
<listenerclassname>chat\system\event\listener\RoomListPageTemproomListener</listenerclassname>
<environment>admin</environment>
</eventlistener>
<eventlistener name="temproomRoomEdit">
<eventclassname>chat\acp\form\RoomEditForm</eventclassname>
<eventname>readParameters</eventname>
<listenerclassname>chat\system\event\listener\RoomEditFormTemproomListener</listenerclassname>
<environment>admin</environment>
</eventlistener>
<eventlistener name="temproomSuspensionList">
<eventclassname>chat\acp\page\SuspensionListPage</eventclassname>
<eventname>readData</eventname>
<listenerclassname>chat\system\event\listener\SuspensionListPageTemproomListener</listenerclassname>
<environment>admin</environment>
</eventlistener>
<!-- User Limit -->
<eventlistener name="userLimitCanJoin">
<eventclassname>chat\data\room\Room</eventclassname>
<eventname>canJoin</eventname>
<listenerclassname>chat\system\event\listener\RoomCanJoinUserLimitListener</listenerclassname>
<environment>user</environment>
</eventlistener>
<!-- Suspensions -->
<eventlistener name="suspensionCanJoin">
<eventclassname>chat\data\room\Room</eventclassname>
<eventname>canJoin</eventname>
<listenerclassname>chat\system\event\listener\RoomCanJoinBanListener</listenerclassname>
<environment>user</environment>
</eventlistener>
<eventlistener name="suspensionCanWritePublicly">
<eventclassname>chat\data\room\Room</eventclassname>
<eventname>canWritePublicly</eventname>
<listenerclassname>chat\system\event\listener\RoomCanWritePubliclyMuteListener</listenerclassname>
<environment>user</environment>
</eventlistener>
<eventlistener name="suspensionInfoCommand">
<eventclassname>chat\system\command\InfoCommand</eventclassname>
<eventname>execute</eventname>
<listenerclassname>chat\system\event\listener\InfoCommandSuspensionsListener</listenerclassname>
<environment>user</environment>
</eventlistener>
<eventlistener name="moderatorPermissions">
<eventclassname>chat\data\room\RoomAction</eventclassname>
<eventname>getUsers</eventname>
<listenerclassname>chat\system\event\listener\RoomActionGetUsersModeratorListener</listenerclassname>
<environment>user</environment>
</eventlistener>
</import>
<delete>
<!-- Hourly Cleanup -->
<eventlistener>
<eventclassname>wcf\system\cronjob\HourlyCleanUpCronjob</eventclassname>
<eventname>execute</eventname>
<listenerclassname>chat\system\event\listener\HourlyCleanUpCronjobExecuteChatCleanUpListener</listenerclassname>
<environment>user</environment>
</eventlistener>
<eventlistener>
<eventclassname>wcf\system\cronjob\HourlyCleanUpCronjob</eventclassname>
<eventname>execute</eventname>
<listenerclassname>chat\system\event\listener\HourlyCleanUpCronjobExecuteChatCleanUpListener</listenerclassname>
<environment>admin</environment>
</eventlistener>
<!-- Temprooms -->
<eventlistener>
<eventclassname>wcf\system\cronjob\HourlyCleanUpCronjob</eventclassname>
<eventname>execute</eventname>
<listenerclassname>chat\system\event\listener\HourlyCleanUpCronjobExecuteTemproomListener</listenerclassname>
<environment>user</environment>
</eventlistener>
<eventlistener>
<eventclassname>wcf\system\cronjob\HourlyCleanUpCronjob</eventclassname>
<eventname>execute</eventname>
<listenerclassname>chat\system\event\listener\HourlyCleanUpCronjobExecuteTemproomListener</listenerclassname>
<environment>admin</environment>
</eventlistener>
<eventlistener>
<eventclassname>chat\data\room\Room</eventclassname>
<eventname>canSee</eventname>
<listenerclassname>chat\system\event\listener\RoomCanSeeTemproomListener</listenerclassname>
<environment>user</environment>
</eventlistener>
<eventlistener>
<eventclassname>chat\acp\page\RoomListPage</eventclassname>
<eventname>calculateNumberOfPages</eventname>
<listenerclassname>chat\system\event\listener\RoomListPageTemproomListener</listenerclassname>
<environment>admin</environment>
</eventlistener>
<eventlistener>
<eventclassname>chat\acp\form\RoomEditForm</eventclassname>
<eventname>readParameters</eventname>
<listenerclassname>chat\system\event\listener\RoomEditFormTemproomListener</listenerclassname>
<environment>admin</environment>
</eventlistener>
<!-- User Limit -->
<eventlistener>
<eventclassname>chat\data\room\Room</eventclassname>
<eventname>canJoin</eventname>
<listenerclassname>chat\system\event\listener\RoomCanJoinUserLimitListener</listenerclassname>
<environment>user</environment>
</eventlistener>
<!-- Suspensions -->
<eventlistener>
<eventclassname>chat\data\room\Room</eventclassname>
<eventname>canJoin</eventname>
<listenerclassname>chat\system\event\listener\RoomCanJoinBanListener</listenerclassname>
<environment>user</environment>
</eventlistener>
<eventlistener>
<eventclassname>chat\data\room\Room</eventclassname>
<eventname>canWritePublicly</eventname>
<listenerclassname>chat\system\event\listener\RoomCanWritePubliclyMuteListener</listenerclassname>
<environment>user</environment>
</eventlistener>
<eventlistener>
<eventclassname>chat\system\command\InfoCommand</eventclassname>
<eventname>execute</eventname>
<listenerclassname>chat\system\event\listener\InfoCommandSuspensionsListener</listenerclassname>
<environment>user</environment>
</eventlistener>
</delete>
</data>

View File

@ -0,0 +1,21 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
use \wcf\system\box\BoxHandler;
BoxHandler::getInstance()->createBoxCondition( 'be.bastelstu.chat.roomListDashboard'
, 'be.bastelstu.chat.box.roomList.condition'
, 'be.bastelstu.chat.roomFilled'
, [ 'chatRoomIsFilled' => 1 ]
);

View File

@ -0,0 +1,35 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
use \chat\data\message\MessageAction;
$objectTypeID = \wcf\data\object\type\ObjectTypeCache::getInstance()->getObjectTypeIDByName('be.bastelstu.chat.messageType', 'be.bastelstu.chat.messageType.chatUpdate');
if ($objectTypeID) {
(new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => null
, 'userID' => null
, 'username' => ''
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ ])
]
]
)
)->executeAction();
}
$CHATCore = file_get_contents(__DIR__.'/../lib/system/CHATCore.class.php');
if (strpos($CHATCore, 'chat.phar.php') === false) {
@unlink(__DIR__.'/../chat.phar.php');
}

18
files/acp/global.php Normal file
View File

@ -0,0 +1,18 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
define('RELATIVE_CHAT_DIR', '../');
require_once(RELATIVE_CHAT_DIR.'/config.inc.php');
require_once(RELATIVE_WCF_DIR.'acp/global.php');

16
files/acp/index.php Normal file
View File

@ -0,0 +1,16 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
require('./global.php');
\wcf\system\request\RequestHandler::getInstance()->handle('chat', true);

16
files/global.php Normal file
View File

@ -0,0 +1,16 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
require_once(dirname(__FILE__).'/config.inc.php');
require_once(RELATIVE_WCF_DIR.'global.php');

16
files/index.php Normal file
View File

@ -0,0 +1,16 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
require('./global.php');
\wcf\system\request\RequestHandler::getInstance()->handle('chat');

View File

@ -0,0 +1,159 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\acp\form;
use \chat\data\command\CommandCache;
use \chat\data\command\CommandTrigger;
use \chat\data\command\CommandTriggerAction;
use \chat\data\command\CommandTriggerEditor;
use \wcf\system\exception\UserInputException;
use \wcf\system\WCF;
/**
* Shows the command trigger add form.
*/
class CommandTriggerAddForm extends \wcf\form\AbstractForm {
/**
* @inheritDoc
*/
public $activeMenuItem = 'chat.acp.menu.link.command.trigger.add';
/**
* @inheritDoc
*/
public $neededPermissions = [ 'admin.chat.canManageTriggers' ];
/**
* The new trigger for the specified command
* @var string
*/
public $commandTrigger = '';
/**
* List of currently known commands
* @var array
*/
public $commands = [ ];
/**
* The selected command.
*
* @param Command
*/
public $command = null;
/**
* The fully qualified name of the command
* @var string
*/
public $className = '';
/**
* @inheritDoc
*/
public function readData() {
$commandList = new \chat\data\command\CommandList();
$commandList->sqlOrderBy = 'command.className';
$commandList->readObjects();
$this->commands = $commandList->getObjects();
parent::readData();
}
/**
* @inheritDoc
*/
public function readFormParameters() {
parent::readFormParameters();
if (isset($_POST['commandTrigger'])) $this->commandTrigger = \wcf\util\StringUtil::trim($_POST['commandTrigger']);
if (isset($_POST['className'])) $this->className = \wcf\util\StringUtil::trim($_POST['className']);
}
/**
* @inheritDoc
*/
public function validate() {
parent::validate();
if (empty($this->commandTrigger)) {
throw new UserInputException('commandTrigger', 'empty');
}
// Triggers must not contain whitespace
if (preg_match('~\s~', $this->commandTrigger)) {
throw new UserInputException('commandTrigger', 'invalid');
}
// Check for duplicates
$trigger = CommandTrigger::getTriggerByName($this->commandTrigger);
if ((!isset($this->trigger) && $trigger->triggerID) || (isset($this->trigger) && $trigger->triggerID != $this->trigger->triggerID)) {
throw new UserInputException('commandTrigger', 'duplicate');
}
if (empty($this->className)) {
throw new UserInputException('className', 'empty');
}
// Check if the command is registered
foreach ($this->commands as $command) {
if ($command->className === $this->className) {
$this->command = $command;
break;
}
}
if (!$this->command) {
throw new UserInputException('className', 'notFound');
}
}
/**
* @inheritDoc
*/
public function save() {
parent::save();
$fields = [ 'commandTrigger' => $this->commandTrigger
, 'commandID' => $this->command->commandID
];
// create room
$this->objectAction = new \chat\data\command\CommandTriggerAction([ ], 'create', [ 'data' => array_merge($this->additionalFields, $fields) ]);
$this->objectAction->executeAction();
$this->saved();
// reset values
$this->commandTrigger = $this->className = '';
// show success message
WCF::getTPL()->assign('success', true);
}
/**
* @inheritDoc
*/
public function assignVariables() {
parent::assignVariables();
WCF::getTPL()->assign([ 'action' => 'add'
, 'commandTrigger' => $this->commandTrigger
, 'className' => $this->className
, 'availableCommands' => $this->commands
]);
}
}

View File

@ -0,0 +1,112 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\acp\form;
use \chat\data\command\CommandTrigger;
use \chat\data\command\CommandTriggerAction;
use \chat\data\command\CommandTriggerEditor;
use \wcf\system\exception\IllegalLinkException;
use \wcf\system\exception\UserInputException;
use \wcf\system\WCF;
/**
* Shows the command trigger edit form.
*/
class CommandTriggerEditForm extends CommandTriggerAddForm {
/**
* @inheritDoc
*/
public $activeMenuItem = 'chat.acp.menu.link.command.trigger.list';
/**
* The requested command trigger ID.
*
* @param int
*/
public $triggerID = 0;
/**
* The requested command trigger.
*
* @param CommandTrigger
*/
public $trigger = null;
/**
* @inheritDoc
*/
public function readParameters() {
if (isset($_REQUEST['id'])) $this->triggerID = intval($_REQUEST['id']);
$this->trigger = new CommandTrigger($this->triggerID);
if (!$this->trigger) {
throw new IllegalLinkException();
}
parent::readParameters();
}
/**
* @inheritDoc
*/
public function readData() {
parent::readData();
if (empty($_POST)) {
$commandList = new \chat\data\command\CommandList();
$commandList->getConditionBuilder()->add('command.commandID = ?', [ $this->trigger->commandID ]);
$commandList->readObjects();
$commands = $commandList->getObjects();
if (!count($commands)) {
throw new IllegalLinkException();
}
$this->commandTrigger = $this->trigger->commandTrigger;
$this->className = $commands[$this->trigger->commandID]->className;
}
}
/**
* @inheritDoc
*/
public function save() {
\wcf\form\AbstractForm::save();
$fields = [ 'commandTrigger' => $this->commandTrigger
, 'commandID' => $this->command->commandID
];
// update trigger
$this->objectAction = new CommandTriggerAction([ $this->trigger ], 'update', [ 'data' => array_merge($this->additionalFields, $fields) ]);
$this->objectAction->executeAction();
$this->saved();
// show success message
WCF::getTPL()->assign('success', true);
}
/**
* @inheritDoc
*/
public function assignVariables() {
parent::assignVariables();
WCF::getTPL()->assign([ 'action' => 'edit'
, 'triggerID' => $this->trigger->triggerID
]);
}
}

View File

@ -0,0 +1,215 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\acp\form;
use \chat\data\room\Room;
use \chat\data\room\RoomAction;
use \chat\data\room\RoomEditor;
use \wcf\system\acl\ACLHandler;
use \wcf\system\exception\UserInputException;
use \wcf\system\language\I18nHandler;
use \wcf\system\WCF;
/**
* Shows the room add form.
*/
class RoomAddForm extends \wcf\form\AbstractForm {
/**
* @inheritDoc
*/
public $activeMenuItem = 'chat.acp.menu.link.room.add';
/**
* @inheritDoc
*/
public $neededPermissions = [ 'admin.chat.canManageRoom' ];
/**
* Object type ID of the ACL object type for rooms.
* @var int
*/
public $aclObjectTypeID = 0;
/**
* Chat room title.
* @var string
*/
public $title = '';
/**
* Chat room topic.
* @var string
*/
public $topic = '';
/**
* Whether HTML should be interpreted in the room's topic.
* @var boolean
*/
public $topicUseHtml = false;
/**
* Chat room user limit.
* @var int
*/
public $userLimit = 0;
/**
* @inheritDoc
*/
public function readParameters() {
parent::readParameters();
I18nHandler::getInstance()->register('title');
I18nHandler::getInstance()->register('topic');
$this->aclObjectTypeID = ACLHandler::getInstance()->getObjectTypeID('be.bastelstu.chat.room');
}
/**
* @inheritDoc
*/
public function readFormParameters() {
parent::readFormParameters();
// read i18n values
I18nHandler::getInstance()->readValues();
// handle i18n plain input
if (I18nHandler::getInstance()->isPlainValue('title')) $this->title = I18nHandler::getInstance()->getValue('title');
if (I18nHandler::getInstance()->isPlainValue('topic')) $this->topic = I18nHandler::getInstance()->getValue('topic');
if (isset($_POST['userLimit'])) $this->userLimit = intval($_POST['userLimit']);
if (isset($_POST['topicUseHtml'])) $this->topicUseHtml = true;
}
/**
* @inheritDoc
*/
public function validate() {
parent::validate();
// validate title
if (!I18nHandler::getInstance()->validateValue('title')) {
if (I18nHandler::getInstance()->isPlainValue('title')) {
throw new UserInputException('title');
}
else {
throw new UserInputException('title', 'multilingual');
}
}
// validate topic
if (!I18nHandler::getInstance()->validateValue('topic', false, true)) {
throw new UserInputException('topic');
}
if (mb_strlen($this->topic) > 10000) {
throw new UserInputException('topic', 'tooLong');
}
if ($this->userLimit < 0) {
throw new UserInputException('userLimit', 'negative');
}
}
/**
* @inheritDoc
*/
public function save() {
parent::save();
$fields = [ 'title' => $this->title
, 'topic' => $this->topic
, 'topicUseHtml' => (int) $this->topicUseHtml
, 'userLimit' => $this->userLimit
, 'position' => 0 // TODO
];
// create room
$this->objectAction = new \chat\data\room\RoomAction([], 'create', [ 'data' => array_merge($this->additionalFields, $fields) ]);
$returnValues = $this->objectAction->executeAction();
// save i18n values
$this->saveI18nValue($returnValues['returnValues'], [ 'title', 'topic' ]);
// save ACL
ACLHandler::getInstance()->save($returnValues['returnValues']->roomID, $this->aclObjectTypeID);
$this->saved();
// reset values
$this->title = $this->topic = '';
$this->userLimit = 0;
$this->topicUseHtml = false;
I18nHandler::getInstance()->reset();
ACLHandler::getInstance()->disableAssignVariables();
// show success message
WCF::getTPL()->assign('success', true);
}
/**
* Saves i18n values.
*
* @param Room $room
* @param string[] $columns
*/
public function saveI18nValue(Room $room, $columns) {
$data = [ ];
foreach ($columns as $columnName) {
$languageItem = 'chat.room.room'.$room->roomID.'.'.$columnName;
if (I18nHandler::getInstance()->isPlainValue($columnName)) {
if ($room->$columnName === $languageItem) {
I18nHandler::getInstance()->remove($languageItem);
}
}
else {
$packageID = \wcf\data\package\PackageCache::getInstance()->getPackageID('be.bastelstu.chat');
I18nHandler::getInstance()->save( $columnName
, $languageItem
, 'chat.room'
, $packageID
);
$data[$columnName] = $languageItem;
}
}
if (!empty($data)) {
(new RoomEditor($room))->update($data);
}
}
/**
* @inheritDoc
*/
public function assignVariables() {
parent::assignVariables();
ACLHandler::getInstance()->assignVariables($this->aclObjectTypeID);
I18nHandler::getInstance()->assignVariables();
WCF::getTPL()->assign([ 'action' => 'add'
, 'aclObjectTypeID' => $this->aclObjectTypeID
, 'userLimit' => $this->userLimit
, 'topicUseHtml' => $this->topicUseHtml
]);
}
}

View File

@ -0,0 +1,117 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\acp\form;
use \chat\data\room\Room;
use \chat\data\room\RoomAction;
use \wcf\system\acl\ACLHandler;
use \wcf\system\language\I18nHandler;
use \wcf\system\WCF;
/**
* Shows the room edit form.
*/
class RoomEditForm extends RoomAddForm {
/**
* @inheritDoc
*/
public $activeMenuItem = 'chat.acp.menu.link.room.list';
/**
* The requested chat room ID.
*
* @param int
*/
public $roomID = 0;
/**
* The requested chat room.
*
* @param Room
*/
public $room = null;
/**
* @inheritDoc
*/
public function readParameters() {
if (isset($_REQUEST['id'])) $this->roomID = intval($_REQUEST['id']);
$this->room = new Room($this->roomID);
if (!$this->room) {
throw new IllegalLinkException();
}
parent::readParameters();
}
/**
* @inheritDoc
*/
public function readData() {
parent::readData();
if (empty($_POST)) {
$packageID = \wcf\data\package\PackageCache::getInstance()->getPackageID('be.bastelstu.chat');
I18nHandler::getInstance()->setOptions('title', $packageID, $this->room->title, 'chat.room.room\d+.title');
I18nHandler::getInstance()->setOptions('topic', $packageID, $this->room->topic, 'chat.room.room\d+.topic');
$this->userLimit = $this->room->userLimit;
$this->topicUseHtml = $this->room->topicUseHtml;
}
}
/**
* @inheritDoc
*/
public function save() {
\wcf\form\AbstractForm::save();
$fields = [ 'title' => $this->title
, 'topic' => $this->topic
, 'topicUseHtml' => (int) $this->topicUseHtml
, 'userLimit' => $this->userLimit
, 'position' => 0 // TODO
];
// update room
$this->objectAction = new RoomAction([ $this->room ], 'update', [ 'data' => array_merge($this->additionalFields, $fields) ]);
$returnValues = $this->objectAction->executeAction();
// save i18n values
$this->saveI18nValue($this->room, [ 'title', 'topic' ]);
// save ACL
ACLHandler::getInstance()->save($this->room->roomID, $this->aclObjectTypeID);
$this->saved();
// show success message
WCF::getTPL()->assign('success', true);
}
/**
* @inheritDoc
*/
public function assignVariables() {
parent::assignVariables();
I18nHandler::getInstance()->assignVariables(!empty($_POST));
WCF::getTPL()->assign([ 'action' => 'edit'
, 'roomID' => $this->room->roomID
, 'room' => $this->room
]);
}
}

View File

@ -0,0 +1,55 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\acp\page;
/**
* Shows the command trigger list.
*/
class CommandTriggerListPage extends \wcf\page\SortablePage {
/**
* @inheritDoc
*/
public $activeMenuItem = 'chat.acp.menu.link.command.trigger.list';
/**
* @inheritDoc
*/
public $neededPermissions = [ 'admin.chat.canManageTriggers' ];
/**
* @inheritDoc
*/
public $objectListClassName = \chat\data\command\CommandTriggerList::class;
/**
* @inheritDoc
*/
public $validSortFields = [ 'triggerID', 'commandTrigger', 'className' ];
/**
* @inheritDoc
*/
public $defaultSortField = 'commandTrigger';
/**
* @inheritDoc
*/
protected function initObjectList() {
parent::initObjectList();
$this->objectList->sqlSelects = 'command.className';
$this->objectList->sqlJoins = 'LEFT JOIN chat'.WCF_N.'_command command ON (command.commandID = command_trigger.commandID)';
}
}

View File

@ -0,0 +1,45 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\acp\page;
/**
* Shows the room list.
*/
class RoomListPage extends \wcf\page\SortablePage {
/**
* @inheritDoc
*/
public $activeMenuItem = 'chat.acp.menu.link.room.list';
/**
* @inheritDoc
*/
public $neededPermissions = [ 'admin.chat.canManageRoom' ];
/**
* @inheritDoc
*/
public $objectListClassName = \chat\data\room\RoomList::class;
/**
* @inheritDoc
*/
public $validSortFields = [ 'roomID', 'title' ];
/**
* @inheritDoc
*/
public $defaultSortField = 'position';
}

View File

@ -0,0 +1,225 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\acp\page;
use \chat\data\room\Room;
use \chat\data\suspension\Suspension;
use \wcf\data\user\User;
use \wcf\system\WCF;
use \wcf\util\StringUtil;
/**
* Shows the suspension list.
*/
class SuspensionListPage extends \wcf\page\SortablePage {
/**
* @inheritDoc
*/
public $activeMenuItem = 'chat.acp.menu.link.suspension.list';
/**
* @inheritDoc
*/
public $neededPermissions = [ 'admin.chat.canManageSuspensions' ];
/**
* @inheritDoc
*/
public $objectListClassName = \chat\data\suspension\SuspensionList::class;
/**
* @inheritDoc
*/
public $validSortFields = [ 'suspensionID', 'time', 'expires', 'revoked' ];
/**
* @inheritDoc
*/
public $defaultSortField = 'expiresSort';
/**
* @inheritDoc
*/
public $defaultSortOrder = 'DESC';
/**
* userID filter
* @var int
*/
public $userID = null;
/**
* roomID filter
* @var int
*/
public $roomID = null;
/**
* objectTypeID filter
* @var int
*/
public $objectTypeID = null;
/**
* judgeID filter
* @var int
*/
public $judgeID = null;
/**
* Whether to show expired entries
* @var boolean
*/
public $showExpired = true;
/**
* username filter
* @var string
*/
public $searchUsername = null;
/**
* judge's username filter
* @var string
*/
public $searchJudge = null;
/**
* Array of available suspension object types
* @var array
*/
public $availableObjectTypes = [ ];
/**
* Array of available chat rooms
* @var array
*/
public $availableRooms = [ ];
/**
* @inheritDoc
*/
public function readParameters() {
parent::readParameters();
if (isset($_REQUEST['roomID']) && $_REQUEST['roomID'] !== '') $this->roomID = intval($_REQUEST['roomID']);
if (isset($_REQUEST['userID']) && $_REQUEST['userID'] !== '') $this->userID = intval($_REQUEST['userID']);
if (isset($_REQUEST['judgeID']) && $_REQUEST['judgeID'] !== '') $this->judgeID = intval($_REQUEST['judgeID']);
if (isset($_REQUEST['objectTypeID']) && $_REQUEST['objectTypeID'] !== '') $this->objectTypeID = intval($_REQUEST['objectTypeID']);
// Checkboxes need special handling
if (!empty($_POST) && !isset($_POST['showExpired'])) $this->showExpired = false;
if (isset($_POST['searchUsername'])) {
$this->searchUsername = StringUtil::trim($_POST['searchUsername']);
if (!empty($this->searchUsername)) {
$this->userID = User::getUserByUsername($this->searchUsername)->userID;
}
}
else if ($this->userID !== null) {
$this->searchUsername = (new User($this->userID))->username;
}
if (isset($_POST['searchJudge'])) {
$this->searchJudge = StringUtil::trim($_POST['searchJudge']);
if (!empty($this->searchJudge)) {
$this->judgeID = User::getUserByUsername($this->searchJudge)->userID;
}
}
else if ($this->judgeID !== null) {
$this->searchJudge = (new User($this->judgeID))->username;
}
}
/**
* @inheritDoc
*/
public function readData() {
$this->availableObjectTypes = \wcf\data\object\type\ObjectTypeCache::getInstance()->getObjectTypes('be.bastelstu.chat.suspension');
$roomList = new \chat\data\room\RoomList();
$roomList->sqlOrderBy = "room.position";
$roomList->readObjects();
$this->availableRooms = $roomList->getObjects();
parent::readData();
\wcf\system\cache\runtime\UserRuntimeCache::getInstance()->cacheObjectIDs(array_map(function (Suspension $s) {
return $s->userID;
}, $this->objectList->getObjects()));
}
/**
* @inheritDoc
*/
protected function initObjectList() {
parent::initObjectList();
$this->objectList->sqlSelects .= 'COALESCE(suspension.revoked, suspension.expires, 2147483647) AS expiresSort';
if (!empty($this->availableRooms)) {
$this->objectList->getConditionBuilder()->add('(roomID IN (?) OR roomID IS NULL)', [ array_map(function (Room $room) {
return $room->roomID;
}, $this->availableRooms) ]);
}
else {
$this->objectList->getConditionBuilder()->add('1 = 0');
}
if ($this->userID !== null) {
$this->objectList->getConditionBuilder()->add('userID = ?', [ $this->userID ]);
}
if ($this->roomID !== null) {
if ($this->roomID === 0) {
$this->objectList->getConditionBuilder()->add('roomID IS NULL');
}
else {
$this->objectList->getConditionBuilder()->add('roomID = ?', [ $this->roomID ]);
}
}
if ($this->objectTypeID !== null) {
$this->objectList->getConditionBuilder()->add('objectTypeID = ?', [ $this->objectTypeID ]);
}
if ($this->judgeID !== null) {
$this->objectList->getConditionBuilder()->add('judgeID = ?', [ $this->judgeID ]);
}
if ($this->showExpired === false) {
$this->objectList->getConditionBuilder()->add('expires >= ?', [ TIME_NOW ]);
}
}
/**
* @inheritDoc
*/
public function assignVariables() {
parent::assignVariables();
WCF::getTPL()->assign([ 'userID' => $this->userID
, 'roomID' => $this->roomID
, 'objectTypeID' => $this->objectTypeID
, 'judgeID' => $this->judgeID
, 'availableRooms' => $this->availableRooms
, 'availableObjectTypes' => $this->availableObjectTypes
, 'searchUsername' => $this->searchUsername
, 'searchJudge' => $this->searchJudge
, 'showExpired' => $this->showExpired
]);
}
}

View File

@ -0,0 +1,51 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\command;
use \wcf\system\WCF;
/**
* Represents a chat command.
*/
class Command extends \wcf\data\ProcessibleDatabaseObject {
/**
* @inheritDoc
*/
protected static $processorInterface = \chat\system\command\ICommand::class;
/**
* Returns whether this command has at least one trigger assigned.
*
* The default PlainCommand implicitely has one.
*/
public function hasTriggers() {
static $chatPackageID = null;
if ($chatPackageID === null) {
$chatPackageID = \wcf\data\package\PackageCache::getInstance()->getPackageID('be.bastelstu.chat');
}
if ($this->packageID === $chatPackageID && $this->identifier === 'plain') {
return true;
}
$sql = "SELECT COUNT(*)
FROM chat".WCF_N."_command_trigger
WHERE commandID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ $this->commandID ]);
return $statement->fetchSingleColumn() > 0;
}
}

View File

@ -0,0 +1,110 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\command;
/**
* Manages the command cache.
*/
class CommandCache extends \wcf\system\SingletonFactory {
/**
* list of cached commands
* @var Command[]
*/
protected $commands = [ ];
/**
* list of cached commands by package
* @var Command[][]
*/
protected $packages = [ ];
/**
* list of cached triggers
* @var int[]
*/
protected $triggers = [ ];
/**
* @inheritDoc
*/
protected function init() {
$data = \chat\system\cache\builder\CommandCacheBuilder::getInstance()->getData();
$this->commands = $data['commands'];
$this->packages = $data['packages'];
$this->triggers = $data['triggers'];
}
/**
* Returns a specific command.
*
* @param integer $commandID
* @return Command
*/
public function getCommand($commandID) {
if (isset($this->commands[$commandID])) {
return $this->commands[$commandID];
}
return null;
}
/**
* Returns a specific command defined by a trigger.
*
* @param string $trigger
* @return Command
*/
public function getCommandByTrigger($trigger) {
if (isset($this->triggers[$trigger])) {
return $this->commands[$this->triggers[$trigger]];
}
return null;
}
/**
* Returns the command defined by the given package and identifier.
*
* @param \wcf\data\package\Package $package
* @param string $identifier
* @return Command
*/
public function getCommandByPackageAndIdentifier(\wcf\data\package\Package $package, $identifier) {
if (isset($this->packages[$package->packageID][$identifier])) {
return $this->packages[$package->packageID][$identifier];
}
return null;
}
/**
* Returns all commands.
*
* @return Command[]
*/
public function getCommands() {
return $this->commands;
}
/**
* Returns all triggers.
*
* @return int[]
*/
public function getTriggers() {
return $this->triggers;
}
}

View File

@ -0,0 +1,25 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\command;
/**
* Represents a chat command editor.
*/
class CommandEditor extends \wcf\data\DatabaseObjectEditor {
/**
* @inheritDoc
*/
protected static $baseClass = Command::class;
}

View File

@ -0,0 +1,21 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\command;
/**
* Represents a list of chat commands.
*/
class CommandList extends \wcf\data\DatabaseObjectList {
}

View File

@ -0,0 +1,54 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\command;
use \wcf\system\WCF;
/**
* Represents a chat command trugger.
*/
class CommandTrigger extends \wcf\data\DatabaseObject implements \wcf\system\request\IRouteController {
/**
* @inheritDoc
*/
public function getTitle() {
return $this->commandTrigger;
}
/**
* @inheritDoc
*/
public function getObjectID() {
return $this->triggerID;
}
/**
* Returns the trigger specified by its commandTrigger value
*
* @param string $name
* @return CommandTrigger
*/
public static function getTriggerByName($name) {
$sql = "SELECT *
FROM chat".WCF_N."_command_trigger
WHERE commandTrigger = ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ $name ]);
$row = $statement->fetchArray();
if (!$row) $row = [];
return new self(null, $row);
}
}

View File

@ -0,0 +1,30 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\command;
/**
* Executes command trigger-related actions.
*/
class CommandTriggerAction extends \wcf\data\AbstractDatabaseObjectAction {
/**
* @inheritDoc
*/
protected $permissionsDelete = [ 'admin.chat.canManageTriggers' ];
/**
* @inheritDoc
*/
protected $permissionsUpdate = [ 'admin.chat.canManageTriggers' ];
}

View File

@ -0,0 +1,32 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\command;
/**
* Represents a command trigger editor.
*/
class CommandTriggerEditor extends \wcf\data\DatabaseObjectEditor implements \wcf\data\IEditableCachedObject {
/**
* @inheritDoc
*/
protected static $baseClass = CommandTrigger::class;
/**
* @inheritDoc
*/
public static function resetCache() {
\chat\system\cache\builder\CommandCacheBuilder::getInstance()->reset();
}
}

View File

@ -0,0 +1,20 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\command;
/**
* Represents a list command triggers.
*/
class CommandTriggerList extends \wcf\data\DatabaseObjectList { }

View File

@ -0,0 +1,59 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\message;
/**
* Represents a chat message.
*/
class Message extends \wcf\data\DatabaseObject {
/**
* @inheritDoc
*/
protected function handleData($data) {
parent::handleData($data);
$this->data['payload'] = @unserialize($this->data['payload']);
if (!is_array($this->data['payload'])) {
$this->data['payload'] = [ ];
}
}
/**
* Returns whether this message already is inside the log.
*
* @return boolean
*/
public function isInLog() {
return $this->time < (TIME_NOW - CHAT_ARCHIVE_AFTER);
}
/**
* Returns the message type object of this message.
*
* @return \wcf\data\object\type\ObjectType
*/
public function getMessageType() {
return \wcf\data\object\type\ObjectTypeCache::getInstance()->getObjectType($this->objectTypeID);
}
/**
* Returns the chat room that contains this message.
*
* @return \chat\data\room\Room
*/
public function getRoom() {
return \chat\data\room\RoomCache::getInstance()->getRoom($this->roomID);
}
}

View File

@ -0,0 +1,307 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\message;
use \chat\data\command\CommandCache;
use \chat\data\room\RoomCache;
use \wcf\data\object\type\ObjectTypeCache;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\exception\UserInputException;
use \wcf\system\user\activity\point\UserActivityPointHandler;
use \wcf\system\WCF;
/**
* Executes chat user-related actions.
*/
class MessageAction extends \wcf\data\AbstractDatabaseObjectAction {
/**
* @inheritDoc
*/
public function create() {
$message = parent::create();
if (isset($this->parameters['updateTimestamp']) && $this->parameters['updateTimestamp']) {
$sql = "UPDATE chat".WCF_N."_room_to_user SET lastPush = ? WHERE roomID = ? AND userID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ TIME_NOW, $message->roomID, $message->userID ]);
}
if (isset($this->parameters['grantPoints']) && $this->parameters['grantPoints']) {
UserActivityPointHandler::getInstance()->fireEvent('be.bastelstu.chat.activityPointEvent.message', $message->messageID, $message->userID);
}
$pushHandler = \wcf\system\push\PushHandler::getInstance();
if ($pushHandler->isEnabled() && in_array('target:channels', $pushHandler->getFeatureFlags())) {
$fastSelect = $message->getMessageType()->getProcessor()->supportsFastSelect();
if ($fastSelect) {
$target = [ 'channels' => [ 'be.bastelstu.chat.room-'.$message->roomID ] ];
}
else {
$target = [ 'channels' => [ 'be.bastelstu.chat' ] ];
}
$pushHandler->sendMessage([ 'message' => 'be.bastelstu.chat.message'
, 'target' => $target
]);
}
return $message;
}
/**
* Validates parameters and permissions.
*/
public function validateTrash() {
// read objects
if (empty($this->objects)) {
$this->readObjects();
if (empty($this->objects)) {
throw new UserInputException('objectIDs');
}
}
foreach ($this->getObjects() as $message) {
if ($message->isDeleted) continue;
$messageType = $message->getMessageType()->getProcessor();
if (!($messageType instanceof \chat\system\message\type\IDeletableMessageType) || !$messageType->canDelete($message->getDecoratedObject())) {
throw new PermissionDeniedException();
}
}
}
/**
* Marks this message as deleted and creates a tombstone message.
*
* Note: Contrary to other applications there is no way to undelete a message.
*/
public function trash() {
if (empty($this->objects)) {
$this->readObjects();
}
$data = [ 'isDeleted' => 1
];
$objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('be.bastelstu.chat.messageType', 'be.bastelstu.chat.messageType.tombstone');
if (!$objectTypeID) {
throw new \LogicException('Missing object type');
}
WCF::getDB()->beginTransaction();
$objectAction = new static($this->getObjects(), 'update', [ 'data' => $data ]);
$objectAction->executeAction();
foreach ($this->getObjects() as $message) {
if ($message->isDeleted) continue;
(new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $message->roomID
, 'userID' => null
, 'username' => ''
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ 'messageID' => $message->messageID ])
]
]
)
)->executeAction();
}
WCF::getDB()->commitTransaction();
}
/**
* Prunes chat messages older than chat_log_archivetime days.
*/
public function prune() {
// Check whether pruning is disabled.
if (!CHAT_LOG_ARCHIVETIME) return;
$sql = "SELECT messageID
FROM chat".WCF_N."_message
WHERE time < ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ TIME_NOW - CHAT_LOG_ARCHIVETIME * 86400 ]);
$messageIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
return call_user_func([$this->className, 'deleteAll'], $messageIDs);
}
/**
* Validates parameters and permissions.
*/
public function validatePull() {
$this->readString('sessionID', true);
if ($this->parameters['sessionID']) {
$this->parameters['sessionID'] = pack('H*', str_replace('-', '', $this->parameters['sessionID']));
}
$this->readInteger('roomID');
$this->readBoolean('inLog', true);
$room = RoomCache::getInstance()->getRoom($this->parameters['roomID']);
if ($room === null) throw new UserInputException('roomID');
if (!$room->canSee($user = null, $reason)) throw $reason;
$user = new \chat\data\user\User(WCF::getUser());
if (!$this->parameters['inLog'] && !$user->isInRoom($room)) throw new PermissionDeniedException();
if ($this->parameters['inLog'] && !$room->canSeeLog(null, $reason)) throw $reason;
$this->readInteger('from', true);
$this->readInteger('to', true);
// One may not pass both 'from' and 'to'
if ($this->parameters['from'] && $this->parameters['to']) {
throw new UserInputException();
}
}
/**
* Pulls messages for the given room.
*/
public function pull() {
$room = RoomCache::getInstance()->getRoom($this->parameters['roomID']);
if ($room === null) throw new UserInputException('roomID');
if (($sessionID = $this->parameters['sessionID'])) {
if (strlen($sessionID) !== 16) throw new UserInputException('sessionID');
(new \chat\data\user\UserAction([], 'clearDeadSessions'))->executeAction();
WCF::getDB()->beginTransaction();
// update timestamp
$sql = "UPDATE chat".WCF_N."_room_to_user
SET lastPull = ?
WHERE roomID = ?
AND userID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ TIME_NOW
, $room->roomID
, WCF::getUser()->userID
]);
$sql = "UPDATE chat".WCF_N."_session
SET lastRequest = ?
WHERE roomID = ?
AND userID = ?
AND sessionID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ TIME_NOW
, $room->roomID
, WCF::getUser()->userID
, $sessionID
]);
WCF::getDB()->commitTransaction();
}
// Determine message types supporting fast select
$objectTypes = \wcf\data\object\type\ObjectTypeCache::getInstance()->getObjectTypes('be.bastelstu.chat.messageType');
$fastSelect = array_map(function ($item) {
return $item->objectTypeID;
}, array_filter($objectTypes, function ($item) {
return $item->getProcessor()->supportsFastSelect();
}));
// Build fast select filter
$condition = new \wcf\system\database\util\PreparedStatementConditionBuilder();
$condition->add('((roomID = ? AND objectTypeID IN (?)) OR objectTypeID NOT IN (?))', [ $room->roomID, $fastSelect, $fastSelect ]);
$sortOrder = 'DESC';
// Add offset
if ($this->parameters['from']) {
$condition->add('messageID >= ?', [ $this->parameters['from'] ]);
$sortOrder = 'ASC';
}
if ($this->parameters['to']) {
$condition->add('messageID <= ?', [ $this->parameters['to'] ]);
}
$sql = "SELECT messageID
FROM chat".WCF_N."_message
".$condition."
ORDER BY messageID ".$sortOrder;
$statement = WCF::getDB()->prepareStatement($sql, 20);
$statement->execute($condition->getParameters());
$messageIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
$objectList = new MessageList();
$objectList->setObjectIDs($messageIDs);
$objectList->readObjects();
$objects = $objectList->getObjects();
$canSeeLog = $room->canSeeLog();
$messages = array_map(function (Message $item) use ($room) {
return new ViewableMessage($item, $room);
}, array_filter($objects, function (Message $message) use ($canSeeLog, $room) {
if ($this->parameters['inLog'] || $message->isInLog()) {
return $canSeeLog && $message->getMessageType()->getProcessor()->canSeeInLog($message, $room);
}
else {
return $message->getMessageType()->getProcessor()->canSee($message, $room);
}
}));
$embeddedObjectMessageIDs = array_map(function ($message) {
return $message->messageID;
}, array_filter($messages, function ($message) {
return $message->hasEmbeddedObjects;
}));
if (!empty($embeddedObjectMessageIDs)) {
// load embedded objects
\wcf\system\message\embedded\object\MessageEmbeddedObjectManager::getInstance()->loadObjects('be.bastelstu.chat.message', $embeddedObjectMessageIDs);
}
return [ 'messages' => $messages
, 'from' => $this->parameters['from'] ?: (!empty($objects) ? reset($objects)->messageID : $this->parameters['to'] + 1)
, 'to' => $this->parameters['to'] ?: (!empty($objects) ? end($objects)->messageID : $this->parameters['from'] - 1)
];
}
/**
* Validates parameters and permissions.
*/
public function validatePush() {
$this->readInteger('roomID');
$room = RoomCache::getInstance()->getRoom($this->parameters['roomID']);
if ($room === null) throw new UserInputException('roomID');
if (!$room->canSee($user = null, $reason)) throw $reason;
$user = new \chat\data\user\User(WCF::getUser());
if (!$user->isInRoom($room)) throw new PermissionDeniedException();
$this->readInteger('commandID');
$command = CommandCache::getInstance()->getCommand($this->parameters['commandID']);
if ($command === null) throw new UserInputException('commandID');
if (!$command->hasTriggers()) {
if (!$command->getProcessor()->allowWithoutTrigger()) {
throw new UserInputException('commandID');
}
}
$this->readJSON('parameters', true);
}
/**
* Pushes a new message into the given room.
*/
public function push() {
$room = RoomCache::getInstance()->getRoom($this->parameters['roomID']);
if ($room === null) throw new UserInputException('roomID');
$command = CommandCache::getInstance()->getCommand($this->parameters['commandID']);
if ($command === null) throw new UserInputException('commandID');
$processor = $command->getProcessor();
$processor->validate($this->parameters['parameters'], $room);
$processor->execute($this->parameters['parameters'], $room);
}
}

View File

@ -0,0 +1,25 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\message;
/**
* Represents a chat message editor.
*/
class MessageEditor extends \wcf\data\DatabaseObjectEditor {
/**
* @inheritDoc
*/
protected static $baseClass = Message::class;
}

View File

@ -0,0 +1,21 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\message;
/**
* Represents a list of chat messages.
*/
class MessageList extends \wcf\data\DatabaseObjectList {
}

View File

@ -0,0 +1,63 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\message;
use \wcf\system\request\LinkHandler;
use \wcf\system\WCF;
/**
* Represents a chat message.
*/
class ViewableMessage extends \wcf\data\DatabaseObjectDecorator implements \JsonSerializable {
protected static $baseClass = Message::class;
protected $room = null;
public function __construct(Message $message, \chat\data\room\Room $room) {
parent::__construct($message);
$this->room = $room;
}
/**
* @inheritDoc
*/
public function jsonSerialize() {
$link = LinkHandler::getInstance()->getLink('Log', [ 'application' => 'chat'
, 'messageid' => $this->messageID
, 'object' => $this->room
]);
if ($this->isDeleted) {
$payload = false;
$objectType = 'be.bastelstu.chat.messageType.tombstone';
}
else {
$payload = $this->getMessageType()->getProcessor()->getPayload($this->getDecoratedObject());
$objectType = $this->getMessageType()->objectType;
}
return [ 'messageID' => $this->messageID
, 'userID' => $this->userID
, 'username' => $this->username
, 'time' => $this->time
, 'payload' => $payload
, 'objectType' => $objectType
, 'link' => $link
, 'isIgnored' => WCF::getUserProfileHandler()->isIgnoredUser($this->userID)
, 'isDeleted' => (bool) $this->isDeleted
];
}
}

View File

@ -0,0 +1,262 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\room;
use \chat\system\cache\runtime\UserRuntimeCache;
use \chat\system\permission\PermissionHandler;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\request\LinkHandler;
use \wcf\system\WCF;
use \wcf\util\StringUtil;
/**
* Represents a chat room.
*/
final class Room extends \wcf\data\DatabaseObject implements \wcf\system\request\IRouteController
, \wcf\data\ITitledLinkObject
, \JsonSerializable {
/**
* User to Room mapping.
*
* @param int[]
*/
private static $userToRoom = null;
/**
* @see Room::getTitle()
*/
public function __toString() {
return $this->getTitle();
}
/**
* Returns whether the given user can see at least
* one chat room. If no user is given the current user
* should be assumed
*
* @param \wcf\data\user\UserProfile $user
* @return boolean
*/
public static function canSeeAny(\wcf\data\user\UserProfile $user = null) {
$rooms = RoomCache::getInstance()->getRooms();
foreach ($rooms as $room) {
if ($room->canSee($user)) return true;
}
return false;
}
/**
* Returns whether the given user can see this room.
* If no user is given the current user should be assumed.
*
* @param \wcf\data\user\UserProfile $user
* @return boolean
*/
public function canSee(\wcf\data\user\UserProfile $user = null, \Exception &$reason = null) {
static $cache = [ ];
if ($user === null) $user = new \wcf\data\user\UserProfile(WCF::getUser());
if (!isset($cache[$this->roomID])) $cache[$this->roomID] = [];
if (array_key_exists($user->userID, $cache[$this->roomID])) {
return ($reason = $cache[$this->roomID][$user->userID]) === null;
}
if (!$user->userID) {
$reason = new PermissionDeniedException();
return ($cache[$this->roomID][$user->userID] = $reason) === null;
}
$result = null;
if (!PermissionHandler::get($user)->getPermission($this, 'user.canSee')) {
$result = new PermissionDeniedException();
}
$parameters = [ 'user' => $user
, 'result' => $result
];
\wcf\system\event\EventHandler::getInstance()->fireAction($this, 'canSee', $parameters);
$reason = $parameters['result'];
if (!($reason === null || $reason instanceof \Exception || $reason instanceof \Throwable)) {
throw new \DomainException('Result of canSee must be a \Throwable or null.');
}
return ($cache[$this->roomID][$user->userID] = $reason) === null;
}
/**
* Returns whether the given user can see the log of this room.
* If no user is given the current user should be assumed.
*
* @param \wcf\data\user\UserProfile $user
* @return boolean
*/
public function canSeeLog(\wcf\data\user\UserProfile $user = null, \Exception &$reason = null) {
static $cache = [ ];
if ($user === null) $user = new \wcf\data\user\UserProfile(WCF::getUser());
if (!isset($cache[$this->roomID])) $cache[$this->roomID] = [];
if (array_key_exists($user->userID, $cache[$this->roomID])) {
return ($reason = $cache[$this->roomID][$user->userID]) === null;
}
$result = null;
if (!PermissionHandler::get($user)->getPermission($this, 'user.canSeeLog')) {
$result = new PermissionDeniedException();
}
$parameters = [ 'user' => $user
, 'result' => $result
];
\wcf\system\event\EventHandler::getInstance()->fireAction($this, 'canSeeLog', $parameters);
$reason = $parameters['result'];
if (!($reason === null || $reason instanceof \Exception || $reason instanceof \Throwable)) {
throw new \DomainException('Result of canSeeLog must be a \Throwable or null.');
}
return ($cache[$this->roomID][$user->userID] = $reason) === null;
}
/**
* Returns whether the given user can join this room.
* If no user is given the current user should be assumed.
*
* @param \wcf\data\user\UserProfile $user
* @return boolean
*/
public function canJoin(\wcf\data\user\UserProfile $user = null, \Exception &$reason = null) {
static $cache = [ ];
if ($user === null) $user = new \wcf\data\user\UserProfile(WCF::getUser());
if (!isset($cache[$this->roomID])) $cache[$this->roomID] = [];
if (array_key_exists($user->userID, $cache[$this->roomID])) {
return ($reason = $cache[$this->roomID][$user->userID]) === null;
}
$parameters = [ 'user' => $user
, 'result' => null
];
\wcf\system\event\EventHandler::getInstance()->fireAction($this, 'canJoin', $parameters);
$reason = $parameters['result'];
if (!($reason === null || $reason instanceof \Exception || $reason instanceof \Throwable)) {
throw new \DomainException('Result of canJoin must be a \Throwable or null.');
}
return ($cache[$this->roomID][$user->userID] = $reason) === null;
}
/**
* Returns whether the given user can write public messages in this room.
* If no user is given the current user should be assumed.
*
* @param \wcf\data\user\UserProfile $user
* @return boolean
*/
public function canWritePublicly(\wcf\data\user\UserProfile $user = null, \Exception &$reason = null) {
static $cache = [ ];
if ($user === null) $user = new \wcf\data\user\UserProfile(WCF::getUser());
if (!isset($cache[$this->roomID])) $cache[$this->roomID] = [];
if (array_key_exists($user->userID, $cache[$this->roomID])) {
return ($reason = $cache[$this->roomID][$user->userID]) === null;
}
$result = null;
if (!PermissionHandler::get($user)->getPermission($this, 'user.canWrite')) {
$result = new PermissionDeniedException();
}
$parameters = [ 'user' => $user
, 'result' => $result
];
\wcf\system\event\EventHandler::getInstance()->fireAction($this, 'canWritePublicly', $parameters);
$reason = $parameters['result'];
if (!($reason === null || $reason instanceof \Exception || $reason instanceof \Throwable)) {
throw new \DomainException('Result of canWritePublicly must be a \Throwable or null.');
}
return ($cache[$this->roomID][$user->userID] = $reason) === null;
}
/**
* @inheritDoc
*/
public function getTitle() {
return WCF::getLanguage()->get($this->title);
}
/**
* @inheritDoc
*/
public function getTopic() {
$topic = StringUtil::trim(WCF::getLanguage()->get($this->topic));
if (!$this->topicUseHtml) {
$topic = StringUtil::encodeHTML($topic);
}
return $topic;
}
/**
* Returns an array of users in this room.
*/
public function getUsers() {
if (self::$userToRoom === null) {
$sql = "SELECT r2u.userID, r2u.roomID
FROM chat".WCF_N."_room_to_user r2u
INNER JOIN wcf".WCF_N."_user u
ON r2u.userID = u.userID
WHERE r2u.active = ?
ORDER BY u.username ASC";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ 1 ]);
self::$userToRoom = $statement->fetchMap('roomID', 'userID', false);
if (!empty(self::$userToRoom)) {
UserRuntimeCache::getInstance()->cacheObjectIDs(array_merge(...self::$userToRoom));
}
}
if (!isset(self::$userToRoom[$this->roomID])) return [ ];
return UserRuntimeCache::getInstance()->getObjects(self::$userToRoom[$this->roomID]);
}
/**
* @inheritDoc
*/
public function getLink() {
return LinkHandler::getInstance()->getLink('Room', [ 'application' => 'chat'
, 'object' => $this
, 'forceFrontend' => true
]
);
}
/**
* @inheritDoc
*/
public function jsonSerialize() {
return [ 'title' => $this->getTitle()
, 'topic' => $this->getTopic()
, 'link' => $this->getLink()
];
}
}

View File

@ -0,0 +1,376 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\room;
use \chat\data\user\User as ChatUser;
use \chat\data\message\MessageAction;
use \wcf\data\object\type\ObjectTypeCache;
use \wcf\system\cache\runtime\UserProfileRuntimeCache;
use \wcf\system\database\util\PreparedStatementConditionBuilder;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\exception\UserInputException;
use \wcf\system\user\activity\point\UserActivityPointHandler;
use \wcf\system\WCF;
/**
* Executes chat room-related actions.
*/
class RoomAction extends \wcf\data\AbstractDatabaseObjectAction implements \wcf\data\ISortableAction {
/**
* @inheritDoc
*/
protected $permissionsDelete = [ 'admin.chat.canManageRoom' ];
/**
* @inheritDoc
*/
protected $permissionsUpdate = [ 'admin.chat.canManageRoom' ];
/**
* Validates parameters and permissions.
*/
public function validateJoin() {
unset($this->parameters['user']);
$this->readString('sessionID');
$this->parameters['sessionID'] = pack('H*', str_replace('-', '', $this->parameters['sessionID']));
$this->readInteger('roomID');
$room = RoomCache::getInstance()->getRoom($this->parameters['roomID']);
if ($room === null) throw new UserInputException('roomID');
if (!$room->canSee($user = null, $reason)) throw $reason;
if (!$room->canJoin($user = null, $reason)) throw $reason;
}
/**
* Makes the given user join the current chat room.
*/
public function join() {
$objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('be.bastelstu.chat.messageType', 'be.bastelstu.chat.messageType.join');
if (!$objectTypeID) throw new \LogicException('Missing object type');
// User cannot be set during an AJAX request, but may be set by Tims Chat itself.
if (!isset($this->parameters['user'])) $this->parameters['user'] = WCF::getUser();
$user = new ChatUser($this->parameters['user']);
// Check parameters
$room = RoomCache::getInstance()->getRoom($this->parameters['roomID']);
if ($room === null) throw new UserInputException('roomID');
$sessionID = $this->parameters['sessionID'];
if (strlen($sessionID) !== 16) throw new UserInputException('sessionID');
try {
// Create room_to_user mapping.
$sql = "INSERT INTO chat".WCF_N."_room_to_user (active, roomID, userID) VALUES (?, ?, ?)";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ 0, $room->roomID, $user->userID ]);
}
catch (\wcf\system\database\exception\DatabaseException $e) {
// Ignore if there already is a mapping.
if ((string) $e->getCode() !== '23000') throw $e;
}
try {
$sql = "INSERT INTO chat".WCF_N."_session (roomID, userID, sessionID, lastRequest) VALUES (?, ?, ?, ?)";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ $room->roomID, $user->userID, $sessionID, TIME_NOW ]);
}
catch (\wcf\system\database\exception\DatabaseException $e) {
if ((string) $e->getCode() !== '23000') throw $e;
throw new UserInputException('sessionID');
}
$markAsBack = function () use ($user, $room) {
$userProfile = new \wcf\data\user\UserProfile($user->getDecoratedObject());
$package = \wcf\data\package\PackageCache::getInstance()->getPackageByIdentifier('be.bastelstu.chat');
$command = \chat\data\command\CommandCache::getInstance()->getCommandByPackageAndIdentifier($package, 'back');
$processor = $command->getProcessor();
$processor->execute([ ], $room, $userProfile);
};
if ($user->chatAway !== null) {
$markAsBack();
}
// Attempt to mark the user as active in the room.
$sql = "UPDATE chat".WCF_N."_room_to_user SET active = ? WHERE roomID = ? AND userID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ 1, $room->roomID, $user->userID ]);
if ($statement->getAffectedRows() === 0) {
// The User already is inside the room: Nothing to do here.
return;
}
// Update lastPull. This must not be merged into the above query, because of the 'getAffectedRows' check.
$sql = "UPDATE chat".WCF_N."_room_to_user SET lastPull = ? WHERE roomID = ? AND userID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ TIME_NOW, $room->roomID, $user->userID ]);
(new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ ])
]
]
)
)->executeAction();
UserActivityPointHandler::getInstance()->fireEvent('be.bastelstu.chat.activityPointEvent.join', 0, $user->userID);
$pushHandler = \wcf\system\push\PushHandler::getInstance();
$pushHandler->sendMessage([ 'message' => 'be.bastelstu.chat.join'
, 'target' => 'registered'
]);
}
/**
* Validates parameters and permissions.
*/
public function validateLeave() {
unset($this->parameters['user']);
$this->readString('sessionID');
$this->parameters['sessionID'] = pack('H*', str_replace('-', '', $this->parameters['sessionID']));
$this->readInteger('roomID');
$room = RoomCache::getInstance()->getRoom($this->parameters['roomID']);
if ($room === null) throw new UserInputException('roomID');
// Do not check permissions: If the user is not inside the room nothing happens, if he is it
// may lead to a faster eviction of the user.
}
/**
* Makes the given user leave the current chat room.
*/
public function leave() {
$objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('be.bastelstu.chat.messageType', 'be.bastelstu.chat.messageType.leave');
if ($objectTypeID) {
// User cannot be set during an AJAX request, but may be set by Tims Chat itself.
if (!isset($this->parameters['user'])) $this->parameters['user'] = WCF::getUser();
$user = new ChatUser($this->parameters['user']);
$room = RoomCache::getInstance()->getRoom($this->parameters['roomID']);
if ($room === null) throw new UserInputException('roomID');
$sessionID = null;
if (isset($this->parameters['sessionID'])) {
$sessionID = $this->parameters['sessionID'];
if (strlen($sessionID) !== 16) throw new UserInputException('sessionID');
}
// Delete session.
$condition = new \wcf\system\database\util\PreparedStatementConditionBuilder();
$condition->add('roomID = ?', [ $room->roomID ]);
$condition->add('userID = ?', [ $user->userID ]);
if ($sessionID !== null) {
$condition->add('sessionID = ?', [ $sessionID ]);
}
$sql = "DELETE FROM chat".WCF_N."_session
".$condition;
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute($condition->getParameters());
if ($statement->getAffectedRows() === 0) {
throw new UserInputException('sessionID');
}
try {
$commited = false;
WCF::getDB()->beginTransaction();
// Check whether we deleted the last session.
$sql = "SELECT COUNT(*)
FROM chat".WCF_N."_session
WHERE roomID = ?
AND userID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ $room->roomID, $user->userID ]);
// We did not: Nothing to do here.
if ($statement->fetchColumn()) return;
// Mark the user as inactive.
$sql = "UPDATE chat".WCF_N."_room_to_user SET active = ? WHERE roomID = ? AND userID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ 0, $room->roomID, $user->userID ]);
if ($statement->getAffectedRows() === 0) throw new \LogicException('Unreachable');
WCF::getDB()->commitTransaction();
$commited = true;
}
finally {
if (!$commited) WCF::getDB()->rollBackTransaction();
}
(new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ ])
]
]
)
)->executeAction();
$pushHandler = \wcf\system\push\PushHandler::getInstance();
$pushHandler->sendMessage([ 'message' => 'be.bastelstu.chat.leave'
, 'target' => 'registered'
]);
}
else {
throw new \LogicException('Missing object type');
}
}
/**
* Validates parameters and permissions.
*/
public function validateGetUsers() {
if (empty($this->getObjects())) {
$this->readObjects();
}
if (count($this->getObjects()) !== 1) {
throw new UserInputException('objectIDs');
}
$room = $this->getObjects()[0];
$user = new ChatUser(WCF::getUser());
if (!$user->isInRoom($room->getDecoratedObject())) throw new PermissionDeniedException();
}
/**
* Returns the userIDs of the users in this room.
*/
public function getUsers() {
if (empty($this->getObjects())) {
$this->readObjects();
}
if (count($this->getObjects()) !== 1) {
throw new UserInputException('objectIDs');
}
$room = $this->getObjects()[0];
$users = (new \chat\data\user\UserAction([ ], 'getUsersByID', [
'userIDs' => array_keys($room->getUsers())
]))->executeAction()['returnValues'];
$users = array_map(function (array $user) use ($room) {
$userProfile = UserProfileRuntimeCache::getInstance()->getObject($user['userID']);
if (!isset($user['permissions'])) $user['permissions'] = [];
$user['permissions']['canWritePublicly'] = $room->canWritePublicly($userProfile);
return $user;
}, $users);
\wcf\system\event\EventHandler::getInstance()->fireAction($this, 'getUsers', $users);
return $users;
}
/**
* @inheritDoc
*/
public function validateUpdatePosition() {
// validate permissions
if (is_array($this->permissionsUpdate) && !empty($this->permissionsUpdate)) {
WCF::getSession()->checkPermissions($this->permissionsUpdate);
}
else {
throw new PermissionDeniedException();
}
$this->readIntegerArray('structure', false, 'data');
$roomList = new RoomList();
$roomList->readObjects();
foreach ($this->parameters['data']['structure'][0] as $roomID) {
$room = $roomList->search($roomID);
if ($room === null) throw new UserInputException('structure');
}
}
/**
* @inheritDoc
*/
public function updatePosition() {
$roomList = new RoomList();
$roomList->readObjects();
$i = 0;
WCF::getDB()->beginTransaction();
foreach ($this->parameters['data']['structure'][0] as $roomID) {
$room = $roomList->search($roomID);
if ($room === null) continue;
$editor = new RoomEditor($room);
$editor->update([ 'position' => $i++ ]);
}
WCF::getDB()->commitTransaction();
}
/**
* Validates permissions.
*/
public function validateGetBoxRoomList() {
if (!\chat\data\room\Room::canSeeAny()) throw new \wcf\system\exception\PermissionDeniedException();
$this->readBoolean('isSidebar', true);
$this->readBoolean('skipEmptyRooms', true);
$this->readInteger('activeRoomID', true);
unset($this->parameters['boxController']);
$this->readInteger('boxID', true);
if ($this->parameters['boxID']) {
$box = new \wcf\data\box\Box($this->parameters['boxID']);
if ($box->boxID) {
$this->parameters['boxController'] = $box->getController();
if ($this->parameters['boxController'] instanceof \chat\system\box\RoomListBoxController) {
// all checks passed, end validation; otherwise throw the exception below
return;
}
}
throw new UserInputException('boxID');
}
}
/**
* Returns dashboard roomlist.
*/
public function getBoxRoomList() {
if (isset($this->parameters['boxController'])) {
$this->parameters['boxController']->setActiveRoomID($this->parameters['activeRoomID']);
return [ 'template' => $this->parameters['boxController']->getContent() ];
}
// Fetch all rooms, the templates have filtering in place
$rooms = RoomCache::getInstance()->getRooms();
$template = 'boxRoomList'.($this->parameters['isSidebar'] ? 'Sidebar' : '');
\wcf\system\WCF::getTPL()->assign([ 'boxRoomList' => $rooms
, 'skipEmptyRooms' => $this->parameters['skipEmptyRooms']
, 'activeRoomID' => $this->parameters['activeRoomID']
]);
return [ 'template' => \wcf\system\WCF::getTPL()->fetch($template, 'chat') ];
}
}

View File

@ -0,0 +1,66 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\room;
use \wcf\system\WCF;
/**
* Manages the room cache.
*/
class RoomCache extends \wcf\system\SingletonFactory {
/**
* List of cached rooms.
*
* @var Room[]
*/
protected $rooms = [ ];
/**
* Cached user counts for the rooms.
*
* @var int[]
*/
protected $userCount = [ ];
/**
* @inheritDoc
*/
protected function init() {
$this->rooms = \chat\system\cache\builder\RoomCacheBuilder::getInstance()->getData();
}
/**
* Returns a specific room.
*
* @param integer $roomID
* @return Room
*/
public function getRoom($roomID) {
if (isset($this->rooms[$roomID])) {
return $this->rooms[$roomID];
}
return null;
}
/**
* Returns all rooms.
*
* @return Room[]
*/
public function getRooms() {
return $this->rooms;
}
}

View File

@ -0,0 +1,33 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\room;
/**
* Represents a chat room editor.
*/
class RoomEditor extends \wcf\data\DatabaseObjectEditor implements \wcf\data\IEditableCachedObject {
/**
* @inheritDoc
*/
protected static $baseClass = Room::class;
/**
* @inheritDoc
*/
public static function resetCache() {
\chat\system\cache\builder\RoomCacheBuilder::getInstance()->reset();
\chat\system\permission\PermissionHandler::resetCache();
}
}

View File

@ -0,0 +1,25 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\room;
/**
* Represents a list of chat rooms.
*/
class RoomList extends \wcf\data\DatabaseObjectList {
/**
* @inheritDoc
*/
public $sqlOrderBy = 'position';
}

View File

@ -0,0 +1,111 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\data\suspension;
use \chat\data\room\Room;
use \wcf\data\user\User;
use \wcf\system\WCF;
/**
* Represents a chat suspension.
*/
class Suspension extends \wcf\data\DatabaseObject implements \JsonSerializable {
/**
* Returns the active suspensions for the given (objectTypeID, Room, User)
* triple.
*
* @param int $objectTypeID
* @param \wcf\data\user\User $user
* @param \chat\data\room\Room $room
* @return \chat\data\suspension\Suspension[]
*/
public static function getActiveSuspensionsByTriple($objectTypeID, User $user, Room $room) {
$suspensionList = new SuspensionList();
$suspensionList->getConditionBuilder()->add('(expires IS NULL OR expires > ?)', [ TIME_NOW ]);
$suspensionList->getConditionBuilder()->add('revoked IS NULL');
$suspensionList->getConditionBuilder()->add('userID = ?', [ $user->userID ]);
$suspensionList->getConditionBuilder()->add('objectTypeID = ?', [ $objectTypeID ]);
$suspensionList->getConditionBuilder()->add('(roomID IS NULL OR roomID = ?)', [ $room->roomID ]);
$suspensionList->readObjects();
return array_filter($suspensionList->getObjects(), function (Suspension $suspension) {
return $suspension->isActive();
});
}
/**
* Returns the suspension object type of this message.
*
* @return \wcf\data\object\type\ObjectType
*/
public function getSuspensionType() {
return \wcf\data\object\type\ObjectTypeCache::getInstance()->getObjectType($this->objectTypeID);
}
/**
* Returns whether this suspension still is in effect.
*
* @return boolean
*/
public function isActive() {
if ($this->revoked !== null) return false;
if (!$this->getSuspensionType()->getProcessor()->hasEffect($this)) return false;
if ($this->expires === null) return true;
return $this->expires > TIME_NOW;
}
/**
* Returns the chat room this suspension is in effect.
* Returns null if this is a global suspension.
*
* @return \chat\data\room\Room
*/
public function getRoom() {
if ($this->roomID === null) {
return null;
}
return \chat\data\room\RoomCache::getInstance()->getRoom($this->roomID);
}
/**
* Returns the user that is affected by this suspension.
*
* @return \wcf\data\user\User
*/
public function getUser() {
return \wcf\system\cache\runtime\UserRuntimeCache::getInstance()->getObject($this->userID);
}
/**
* @inheritDoc
*/
public function jsonSerialize() {
return [ 'userID' => $this->userID
, 'username' => $this->getUser()->username
, 'roomID' => $this->roomID
, 'time' => $this->time
, 'expires' => $this->expires
, 'reason' => $this->reason
, 'objectType' => $this->getSuspensionType()->objectType
, 'judgeID' => $this->judgeID
, 'judge' => $this->judge
];
}
}

View File

@ -0,0 +1,68 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\data\suspension;
use \wcf\system\WCF;
/**
* Executes suspension-related actions.
*/
class SuspensionAction extends \wcf\data\AbstractDatabaseObjectAction {
/**
* @inheritDoc
*/
protected $requireACP = [ 'revoke' ];
/**
* Validates parameters and permissions.
*/
public function validateRevoke() {
if (empty($this->objects)) {
$this->readObjects();
if (empty($this->objects)) {
throw new UserInputException('objectIDs');
}
}
unset($this->parameters['revoker']);
WCF::getSession()->checkPermissions([ 'admin.chat.canManageSuspensions' ]);
foreach ($this->getObjects() as $object) {
if (!$object->isActive()) throw new UserInputException('objectIDs', 'nonActive');
}
}
/**
* Revokes the suspensions
*/
public function revoke() {
if (empty($this->objects)) {
$this->readObjects();
}
// User cannot be set during an AJAX request, but may be set by Tims Chat itself.
if (!isset($this->parameters['revoker'])) $this->parameters['revoker'] = WCF::getUser();
$data = [ 'revoked' => TIME_NOW
, 'revokerID' => $this->parameters['revoker']->userID
, 'revoker' => $this->parameters['revoker']->username
];
$objectAction = new static($this->getObjects(), 'update', [ 'data' => $data ]);
$objectAction->executeAction();
}
}

View File

@ -0,0 +1,25 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\data\suspension;
/**
* Represents a chat suspension editor.
*/
class SuspensionEditor extends \wcf\data\DatabaseObjectEditor {
/**
* @inheritDoc
*/
protected static $baseClass = Suspension::class;
}

View File

@ -0,0 +1,22 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\data\suspension;
/**
* Represents a list of chat suspensions.
*/
class SuspensionList extends \wcf\data\DatabaseObjectList {
}

View File

@ -0,0 +1,106 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\user;
use \wcf\system\WCF;
/**
* Decorates the User object to make it useful in context of Tims Chat.
*/
class User extends \wcf\data\DatabaseObjectDecorator implements \JsonSerializable {
/**
* @inheritDoc
*/
protected static $baseClass = \wcf\data\user\User::class;
/**
* array of room_to_user rows
*
* @var int[][]
*/
protected $roomToUser = null;
/**
* Returns an array of the room_to_user arrays for this user.
*
* @return mixed[]
*/
public function getRoomAssociations($skipCache = false) {
if ($this->roomToUser === null || $skipCache) {
$sql = "SELECT *
FROM chat".WCF_N."_room_to_user
WHERE userID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ $this->userID ]);
$this->roomToUser = [ ];
while (($row = $statement->fetchArray())) {
$this->roomToUser[$row['roomID']] = $row;
}
}
return $this->roomToUser;
}
/**
* Returns an array of Rooms this user is part of.
*
* @return \chat\data\room\Room[]
*/
public function getRooms($skipCache = false) {
return array_map(function ($assoc) {
return \chat\data\room\RoomCache::getInstance()->getRoom($assoc['roomID']);
}, array_filter($this->getRoomAssociations($skipCache), function ($assoc) {
return $assoc['active'] === 1;
}));
}
/**
* Returns whether the user is in the given room.
*
* @param \chat\data\room\Room $room
* @return boolean
*/
public function isInRoom(\chat\data\room\Room $room, $skipCache = false) {
$assoc = $this->getRoomAssociations($skipCache);
if (!isset($assoc[$room->roomID])) return false;
return $assoc[$room->roomID]['active'] === 1;
}
/**
* Returns (userID, roomID, sessionID) triples where the client died.
*
* @return mixed[][]
*/
public static function getDeadSessions() {
$sql = "SELECT userID, roomID, sessionID
FROM chat".WCF_N."_session
WHERE lastRequest < ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ TIME_NOW - 60 * 3 ]);
return $statement->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* @inheritDoc
*/
public function jsonSerialize() {
return [ 'userID' => $this->userID
, 'username' => $this->username
, 'link' => $this->getLink()
];
}
}

View File

@ -0,0 +1,118 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\user;
use \chat\data\room\RoomCache;
use \wcf\system\cache\runtime\UserProfileRuntimeCache;
use \wcf\system\cache\runtime\UserRuntimeCache;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\exception\UserInputException;
use \wcf\system\WCF;
/**
* Executes chat user-related actions.
*/
class UserAction extends \wcf\data\AbstractDatabaseObjectAction {
/**
* @inheritDoc
*/
protected $className = User::class;
/**
* Validates parameters and permissions.
*/
public function validateGetUsersByID() {
if (!\chat\data\room\Room::canSeeAny()) throw new PermissionDeniedException();
$this->readIntegerArray('userIDs');
}
/**
* Returns information about the users identified by the given userIDs.
*/
public function getUsersByID() {
$userList = UserProfileRuntimeCache::getInstance()->getObjects($this->parameters['userIDs']);
return array_map(function ($user) {
if (!$user) return null;
$payload = [ 'image16' => $user->getAvatar()->getImageTag(16)
, 'image24' => $user->getAvatar()->getImageTag(24)
, 'image32' => $user->getAvatar()->getImageTag(32)
, 'image48' => $user->getAvatar()->getImageTag(48)
, 'imageUrl' => $user->getAvatar()->getURL()
, 'link' => $user->getLink()
, 'anchor' => $user->getAnchorTag()
, 'userID' => $user->userID
, 'username' => $user->username
, 'userTitle' => $user->getUserTitle()
, 'userRankClass' => $user->getRank() ? $user->getRank()->cssClassName : null
, 'formattedUsername' => $user->getFormattedUsername()
, 'away' => $user->chatAway
, 'color1' => $user->chatColor1
, 'color2' => $user->chatColor2
];
\wcf\system\event\EventHandler::getInstance()->fireAction($this, 'getUsersByID', $payload);
return $payload;
}, $userList);
}
/**
* Clears dead clients.
*/
public function clearDeadSessions() {
$sessions = User::getDeadSessions();
if (empty($sessions)) return;
$userIDs = array_map(function ($item) {
return $item['userID'];
}, $sessions);
$users = UserRuntimeCache::getInstance()->getObjects($userIDs);
foreach ($sessions as $session) {
$parameters = [ 'user' => $users[$session['userID']]
, 'roomID' => $session['roomID']
, 'sessionID' => $session['sessionID']
];
try {
(new \chat\data\room\RoomAction([ ], 'leave', $parameters))->executeAction();
}
catch (UserInputException $e) {
// Probably some other request has been faster to remove this session, ignore
}
}
}
/**
* @inheritDoc
*/
public function create() {
throw new \BadMethodCallException();
}
/**
* @inheritDoc
*/
public function update() {
throw new \BadMethodCallException();
}
/**
* @inheritDoc
*/
public function delete() {
throw new \BadMethodCallException();
}
}

View File

@ -0,0 +1,25 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\data\user;
/**
* Represents a list of chat users.
*/
class UserList extends \wcf\data\user\UserList {
/**
* @inheritDoc
*/
public $decoratorClassName = User::class;
}

View File

@ -0,0 +1,170 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\page;
use \chat\data\message\MessageList;
use \wcf\system\exception\IllegalLinkException;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\page\PageLocationManager;
use \wcf\system\WCF;
/**
* Shows the log of a specific chat room.
*/
class LogPage extends \wcf\page\AbstractPage {
use TConfiguredPage;
/**
* @inheritDoc
*/
public $loginRequired = true;
/**
* The requested chat room ID.
* @var int
*/
public $roomID = 0;
/**
* The requested chat room.
* @var \chat\data\room\Room
*/
public $room = null;
/**
* The requested message ID.
* @var int
*/
public $messageID = 0;
/**
* The requested message.
* @var \chat\data\message\Message
*/
public $message = null;
/**
* The requested time.
* @var int
*/
public $datetime = 0;
/**
* @inheritDoc
*/
public function readParameters() {
parent::readParameters();
if (isset($_GET['id'])) $this->roomID = intval($_GET['id']);
$this->room = \chat\data\room\RoomCache::getInstance()->getRoom($this->roomID);
if ($this->room === null) throw new IllegalLinkException();
if (!$this->room->canSee($user = null, $reason)) throw $reason;
if (!$this->room->canSeeLog($user = null, $reason)) throw $reason;
if (isset($_GET['messageid'])) $this->messageID = intval($_GET['messageid']);
if ($this->messageID) {
$this->message = new \chat\data\message\Message($this->messageID);
if (!$this->message->getMessageType()->getProcessor()->canSeeInLog($this->message, $this->room)) {
throw new PermissionDeniedException();
}
}
if (isset($_REQUEST['datetime'])) $this->datetime = strtotime($_REQUEST['datetime']);
}
/**
* @inheritDoc
*/
public function readData() {
parent::readData();
if ($this->datetime) {
// Determine message types supporting fast select
$objectTypes = \wcf\data\object\type\ObjectTypeCache::getInstance()->getObjectTypes('be.bastelstu.chat.messageType');
$fastSelect = array_map(function ($item) {
return $item->objectTypeID;
}, array_filter($objectTypes, function ($item) {
// TODO: Consider a method couldAppearInLog(): bool
return $item->getProcessor()->supportsFastSelect();
}));
$minimum = 0;
$loops = 0;
do {
// Build fast select filter
$condition = new \wcf\system\database\util\PreparedStatementConditionBuilder();
$condition->add('((roomID = ? AND objectTypeID IN (?)) OR objectTypeID NOT IN (?))', [ $this->room->roomID, $fastSelect, $fastSelect ]);
$condition->add('time >= ?', [ $this->datetime ]);
if ($minimum) {
$condition->add('messageID > ?', [ $minimum ]);
}
$sql = "SELECT messageID
FROM chat".WCF_N."_message
".$condition."
ORDER BY messageID ASC";
$statement = WCF::getDB()->prepareStatement($sql, 20);
$statement->execute($condition->getParameters());
$messageIDs = $statement->fetchAll(\PDO::FETCH_COLUMN);
$objectList = new MessageList();
$objectList->setObjectIDs($messageIDs);
$objectList->readObjects();
$objects = $objectList->getObjects();
if (empty($objects)) {
// TODO: UserInputException?
throw new IllegalLinkException();
}
foreach ($objects as $message) {
if ($message->getMessageType()->getProcessor()->canSeeInLog($message, $this->room)) {
$parameters = [ 'application' => 'chat'
, 'messageid' => $message->messageID
, 'object' => $this->room
];
\wcf\util\HeaderUtil::redirect(\wcf\system\request\LinkHandler::getInstance()->getLink('Log', $parameters));
exit;
}
$minimum = $message->messageID;
}
}
while (++$loops <= 3);
// Do a best guess redirect to an ID that is as near as possible
$parameters = [ 'application' => 'chat'
, 'messageid' => $minimum
, 'object' => $this->room
];
\wcf\util\HeaderUtil::redirect(\wcf\system\request\LinkHandler::getInstance()->getLink('Log', $parameters));
exit;
}
}
/**
* @inheritDoc
*/
public function assignVariables() {
parent::assignVariables();
PageLocationManager::getInstance()->addParentLocation('be.bastelstu.chat.Room', $this->room->roomID, $this->room);
WCF::getTPL()->assign([ 'room' => $this->room
, 'roomList' => \chat\data\room\RoomCache::getInstance()->getRooms()
, 'messageID' => $this->messageID
, 'message' => $this->message
, 'config' => $this->getConfig()
]);
}
}

View File

@ -0,0 +1,61 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\page;
use \wcf\system\WCF;
/**
* Shows the list of available chat rooms.
*/
class RoomListPage extends \wcf\page\AbstractPage {
/**
* @inheritDoc
*/
public $loginRequired = true;
/**
* List of rooms.
*
* @var \chat\data\room\Room[]
*/
public $rooms = [ ];
/**
* @inheritDoc
*/
public function checkPermissions() {
parent::checkPermissions();
if (!\chat\data\room\Room::canSeeAny()) throw new \wcf\system\exception\PermissionDeniedException();
}
/**
* @inheritDoc
*/
public function readData() {
parent::readData();
$this->rooms = \chat\data\room\RoomCache::getInstance()->getRooms();
}
/**
* @inheritDoc
*/
public function assignVariables() {
parent::assignVariables();
WCF::getTPL()->assign([ 'rooms' => $this->rooms ]);
}
}

View File

@ -0,0 +1,109 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\page;
use \wcf\system\exception\IllegalLinkException;
use \wcf\system\exception\NamedUserException;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\WCF;
/**
* Shows a specific chat room.
*/
class RoomPage extends \wcf\page\AbstractPage {
use TConfiguredPage;
/**
* @inheritDoc
*/
public $loginRequired = true;
/**
* The requested chat room ID.
*
* @param int
*/
public $roomID = 0;
/**
* The requested chat room.
*
* @param \chat\data\room\Room
*/
public $room = null;
/**
* @inheritDoc
*/
public function readParameters() {
parent::readParameters();
if (isset($_GET['id'])) $this->roomID = intval($_GET['id']);
$this->room = \chat\data\room\RoomCache::getInstance()->getRoom($this->roomID);
if ($this->room === null) throw new IllegalLinkException();
if (!$this->room->canSee($user = null, $reason)) throw $reason;
if (!$this->room->canJoin($user = null, $reason)) throw $reason;
$this->canonicalURL = $this->room->getLink();
}
/**
* @inheritDoc
*/
public function checkPermissions() {
parent::checkPermissions();
$package = \wcf\data\package\PackageCache::getInstance()->getPackageByIdentifier('be.bastelstu.chat');
if (stripos($package->packageVersion, 'Alpha') !== false) {
$sql = "SELECT COUNT(*) FROM wcf".WCF_N."_user";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute();
$userCount = $statement->fetchSingleColumn();
if ((($userCount > 5 && !OFFLINE) || ($userCount > 30 && OFFLINE)) && sha1(WCF_UUID) !== '643a6b3af2a6ea3d393c4d8371e75d7d1b66e0d0') {
throw new PermissionDeniedException("Do not use alpha versions of Tims Chat in production communities!");
}
}
}
/**
* @inheritDoc
*/
public function readData() {
$sql = "SELECT 1";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute();
if ($statement->fetchSingleColumn() !== 1) {
throw new NamedUserException('PHP must be configured to use the MySQLnd driver, instead of libmysqlclient.');
}
parent::readData();
$pushHandler = \wcf\system\push\PushHandler::getInstance();
$pushHandler->joinChannel('be.bastelstu.chat');
$pushHandler->joinChannel('be.bastelstu.chat.room-'.$this->room->roomID);
}
/**
* @inheritDoc
*/
public function assignVariables() {
parent::assignVariables();
WCF::getTPL()->assign([ 'room' => $this->room
, 'config' => $this->getConfig()
]);
}
}

View File

@ -0,0 +1,60 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\page;
use \chat\data\command\Command;
use \chat\data\command\CommandCache;
use \wcf\data\object\type\ObjectTypeCache;
use \wcf\data\package\PackageCache;
/**
* Provides a getConfig() method, returning the JSON configuration
* for the chat's JavaSCript.
*/
trait TConfiguredPage {
/**
* Returns the configuration for the chat's JavaScript.
*/
public function getConfig() {
$triggers = CommandCache::getInstance()->getTriggers();
$commands = array_map(function (Command $item) {
$package = PackageCache::getInstance()->getPackage($item->packageID)->package;
return [ 'package' => $package
, 'identifier' => $item->identifier
, 'commandID' => $item->commandID
, 'module' => $item->getProcessor()->getJavaScriptModuleName()
, 'isAvailable' => $item->getProcessor()->isAvailable($this->room) && ($item->hasTriggers() || $item->getProcessor()->allowWithoutTrigger())
];
}, CommandCache::getInstance()->getCommands());
$messageTypes = array_map(function ($item) {
return [ 'module' => $item->getProcessor()->getJavaScriptModuleName()
];
}, ObjectTypeCache::getInstance()->getObjectTypes('be.bastelstu.chat.messageType'));
$config = [ 'clientVersion' => 1
, 'reloadTime' => (int) CHAT_RELOADTIME
, 'autoAwayTime' => (int) CHAT_AUTOAWAYTIME
, 'commands' => $commands
, 'triggers' => $triggers
, 'messageTypes' => $messageTypes
];
\wcf\system\event\EventHandler::getInstance()->fireAction($this, 'config', $config);
return \wcf\util\JSON::encode($config);
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system;
class CHATCore extends \wcf\system\application\AbstractApplication {
/**
* @inheritDoc
*/
protected $primaryController = \chat\page\RoomListPage::class;
/**
* @inheritDoc
*/
public function __run() {
$route = new \wcf\system\request\route\StaticRequestRoute();
$route->setStaticController('chat', 'Log');
$route->setBuildSchema('/{controller}/{id}-{title}/{messageid}');
$route->setPattern('~^/?(?P<controller>[^/]+)/(?P<id>\d+)(?:-(?P<title>[^/]+))?/(?P<messageid>\d+)~x');
$route->setRequiredComponents([ 'id' => '~^\d+$~'
, 'messageid' => '~^\d+$~'
]);
$route->setMatchController(true);
\wcf\system\request\RouteHandler::getInstance()->addRoute($route);
}
}

View File

@ -0,0 +1,120 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\box;
use \wcf\system\request\RequestHandler;
use \wcf\system\WCF;
/**
* Dynamic box controller implementation for a list of rooms.
*/
class RoomListBoxController extends \wcf\system\box\AbstractDatabaseObjectListBoxController {
/**
* @inheritDoc
*/
protected static $supportedPositions = [ 'contentBottom', 'contentTop', 'sidebarLeft', 'sidebarRight' ];
/**
* @inheritDoc
*/
protected $conditionDefinition = 'be.bastelstu.chat.box.roomList.condition';
/**
* @var int
*/
protected $activeRoomID = null;
/**
* @inheritDoc
*/
public function __construct() {
parent::__construct();
$activeRequest = RequestHandler::getInstance()->getActiveRequest();
if ($activeRequest && $activeRequest->getRequestObject() instanceof \chat\page\RoomPage) {
$this->activeRoomID = $activeRequest->getRequestObject()->room->roomID;
}
}
/**
* Sets the active room ID.
*/
public function setActiveRoomID($activeRoomID) {
$this->activeRoomID = $activeRoomID;
}
/**
* Returns the active room ID.
*
* @return int
*/
public function getActiveRoomID() {
return $this->activeRoomID;
}
/**
* @inheritDoc
*/
public function hasLink() {
return true;
}
/**
* @inheritDoc
*/
public function getLink() {
return \wcf\system\request\LinkHandler::getInstance()->getLink('RoomList', [ 'application' => 'chat' ]);
}
/**
* @inheritDoc
*/
protected function getObjectList() {
return new \chat\data\room\RoomList();
}
/**
* @inheritDoc
*/
protected function getTemplate() {
$templateName = 'boxRoomList';
if ($this->box->position === 'sidebarLeft' || $this->box->position === 'sidebarRight') {
$templateName = 'boxRoomListSidebar';
}
return WCF::getTPL()->fetch($templateName, 'chat', [ 'boxRoomList' => $this->objectList
, 'boxID' => $this->getBox()->boxID
, 'activeRoomID' => $this->activeRoomID ?: 0
], true);
}
/**
* @inheritDoc
*/
public function hasContent() {
if ($this->box->position === 'sidebarLeft' || $this->box->position === 'sidebarRight') {
parent::hasContent();
foreach ($this->objectList as $room) {
if ($room->canSee()) return true;
}
return false;
}
else {
return \chat\data\room\Room::canSeeAny();
}
}
}

View File

@ -0,0 +1,52 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\cache\builder;
use \wcf\system\WCF;
/**
* Caches all chat commands.
*/
class CommandCacheBuilder extends \wcf\system\cache\builder\AbstractCacheBuilder {
/**
* @see \wcf\system\cache\AbstractCacheBuilder::rebuild()
*/
public function rebuild(array $parameters) {
$data = [ 'commands' => [ ]
, 'triggers' => [ ]
, 'packages' => [ ]
];
$commandList = new \chat\data\command\CommandList();
$commandList->sqlOrderBy = 'command.commandID';
$commandList->readObjects();
$data['commands'] = $commandList->getObjects();
foreach ($data['commands'] as $command) {
if (!isset($data['packages'][$command->packageID])) $data['packages'][$command->packageID] = [ ];
$data['packages'][$command->packageID][$command->identifier] = $command;
}
$sql = "SELECT *
FROM chat".WCF_N."_command_trigger";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute();
$data['triggers'] = $statement->fetchMap('commandTrigger', 'commandID');
return $data;
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* Copyright (C) 2010-2017 Tim Düsterhus
* Copyright (C) 2010-2017 Woltlab GmbH
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
namespace chat\system\cache\builder;
use \wcf\system\acl\ACLHandler;
use \wcf\system\WCF;
/**
* Caches the chat permissions for a combination of user groups.
*/
class PermissionCacheBuilder extends \wcf\system\cache\builder\AbstractCacheBuilder {
/**
* @inheritDoc
*/
public function rebuild(array $parameters) {
$data = [ ];
if (!empty($parameters)) {
$conditionBuilder = new \wcf\system\database\util\PreparedStatementConditionBuilder();
$conditionBuilder->add('acl_option.objectTypeID = ?', [ ACLHandler::getInstance()->getObjectTypeID('be.bastelstu.chat.room') ]);
$conditionBuilder->add('option_to_group.groupID IN (?)', [ $parameters ]);
$sql = "SELECT option_to_group.objectID AS roomID,
option_to_group.optionValue,
acl_option.optionName AS permission
FROM wcf".WCF_N."_acl_option acl_option
INNER JOIN wcf".WCF_N."_acl_option_to_group option_to_group
ON option_to_group.optionID = acl_option.optionID
".$conditionBuilder;
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute($conditionBuilder->getParameters());
while (($row = $statement->fetchArray())) {
if (!isset($data[$row['roomID']][$row['permission']])) {
$data[$row['roomID']][$row['permission']] = $row['optionValue'];
}
else {
$data[$row['roomID']][$row['permission']] = $row['optionValue'] || $data[$row['roomID']][$row['permission']];
}
}
}
return $data;
}
}

View File

@ -0,0 +1,31 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\cache\builder;
/**
* Caches all chat rooms.
*/
class RoomCacheBuilder extends \wcf\system\cache\builder\AbstractCacheBuilder {
/**
* @inheritDoc
*/
public function rebuild(array $parameters) {
$roomList = new \chat\data\room\RoomList();
$roomList->sqlOrderBy = "room.position";
$roomList->readObjects();
return $roomList->getObjects();
}
}

View File

@ -0,0 +1,25 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\cache\runtime;
/**
* Runtime cache implementation for chat users.
*/
class UserRuntimeCache extends \wcf\system\cache\runtime\AbstractRuntimeCache {
/**
* @inheritDoc
*/
protected $listClassName = \chat\data\user\UserList::class;
}

View File

@ -0,0 +1,76 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\room\Room;
use \wcf\data\object\type\ObjectTypeCache;
use \wcf\data\user\UserProfile;
/**
* Default implemention for command processors.
*/
abstract class AbstractCommand extends \wcf\data\DatabaseObjectDecorator implements ICommand
, \wcf\data\IDatabaseObjectProcessor {
/**
* @inheritDoc
*/
protected static $baseClass = \chat\data\command\Command::class;
/**
* @inheritDoc
*/
public function isAvailable(Room $room, UserProfile $user = null) {
return true;
}
/**
* @inheritDoc
*/
public function allowWithoutTrigger() {
return false;
}
/**
* Returns the object type ID for the given message type.
*
* @param string
* @return int
*/
public function getMessageObjectTypeID($objectType) {
$objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('be.bastelstu.chat.messageType', $objectType);
if (!$objectType) {
throw new \LogicException('Missing object type');
}
return $objectTypeID;
}
/**
* Ensures that the given parameter exists in the parameter array and
* throws otherwise.
*
* @param array $parameters
* @param string $key
* @return mixed The value.
*/
public function assertParameter($parameters, $key) {
if (array_key_exists($key, $parameters)) {
return $parameters[$key];
}
throw new UserInputException('message');
}
}

View File

@ -0,0 +1,84 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \wcf\system\exception\UserInputException;
use \wcf\system\bbcode\BBCodeHandler;
use \wcf\system\message\censorship\Censorship;
use \wcf\system\WCF;
/**
* Represents a command that processes the input using HtmlInputProcessor.
*/
abstract class AbstractInputProcessedCommand extends AbstractCommand {
/**
* HtmlInputProcessor to use.
* @var \wcf\system\html\input\HtmlInputProcessor
*/
protected $processor = null;
/**
* The text processed last.
* @var string
*/
private $text = null;
public function __construct(\wcf\data\DatabaseObject $object) {
parent::__construct($object);
$this->processor = new \wcf\system\html\input\HtmlInputProcessor();
$this->setDisallowedBBCodes();
}
private function setDisallowedBBCodes() {
BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', WCF::getSession()->getPermission('user.chat.disallowedBBCodes')));
}
public function setText($text) {
if ($this->text === $text) return;
$this->text = $text;
$this->setDisallowedBBCodes();
$this->processor->process($text, 'be.bastelstu.chat.message', 0);
}
public function validateText() {
if ($this->processor->appearsToBeEmpty()) {
throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.global.form.error.empty'));
}
$message = $this->processor->getTextContent();
// validate message length
if (mb_strlen($message) > CHAT_MAX_LENGTH) {
throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.tooLong', [ 'maxTextLength' => CHAT_MAX_LENGTH ]));
}
// search for disallowed bbcodes
$this->setDisallowedBBCodes();
$disallowedBBCodes = $this->processor->validate();
if (!empty($disallowedBBCodes)) {
throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.disallowedBBCodes', [ 'disallowedBBCodes' => $disallowedBBCodes ]));
}
// search for censored words
if (ENABLE_CENSORSHIP) {
$result = Censorship::getInstance()->test($message);
if ($result) {
throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.censoredWordsFound', [ 'censoredWords' => $result ]));
}
}
}
}

View File

@ -0,0 +1,170 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\message\MessageAction;
use \chat\data\room\Room;
use \chat\data\suspension\Suspension;
use \chat\data\suspension\SuspensionAction;
use \wcf\data\object\type\ObjectTypeCache;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\UserInputException;
use \wcf\system\WCF;
/**
* Represents a command that creates suspensions
*/
abstract class AbstractSuspensionCommand extends AbstractCommand {
use TNeedsUser;
/**
* Returns the name of the object type for this suspension.
*
* @return string
*/
abstract public function getObjectTypeName();
/**
* Checks the permissions to execute this command.
* Throws if necessary.
*
* @see \chat\system\command\ICommand::validate()
*/
abstract protected function checkPermissions($parameters, Room $room, UserProfile $user);
/**
* @inheritDoc
*/
public function validate($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$this->assertParameter($parameters, 'username');
$this->assertParameter($parameters, 'globally');
$this->assertParameter($parameters, 'duration');
$this->assertParameter($parameters, 'reason');
$this->assertUser($parameters['username']);
if ($parameters['duration'] !== null && $parameters['duration'] < TIME_NOW) {
throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('chat.error.datePast'));
}
if (!empty($parameters['reason']) && mb_strlen($parameters['reason']) > 100) {
throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.tooLong', [ 'maxTextLength' => 250 ]));
}
$this->checkPermissions($parameters, $room, $user);
$test = new Suspension(null, $this->getSuspensionData($parameters, $room, $user));
if (!$test->isActive()) {
throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('chat.error.suspension.noEffect'));
}
}
/**
* @inheritDoc
*/
public function execute($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$data = $this->getSuspensionData($parameters, $room, $user);
$test = new Suspension(null, $data);
if (!$test->isActive()) {
return;
}
WCF::getDB()->beginTransaction();
$suspension = (new SuspensionAction([ ], 'create', [ 'data' => $data ]))->executeAction()['returnValues'];
$this->afterCreate($suspension, $parameters, $room, $user);
WCF::getDB()->commitTransaction();
}
/**
* Creates chat messages informing about the suspension.
*
* @param \chat\data\suspension\Suspension $suspension
* @param mixed[] $parameters
* @param \chat\data\room\Room $room
* @param \wcf\data\user\UserProfile $user
*/
protected function afterCreate(Suspension $suspension, $parameters, Room $room, UserProfile $user) {
$objectTypeID = $this->getMessageObjectTypeID('be.bastelstu.chat.messageType.suspend');
$target = $suspension->getUser();
if ($suspension->getRoom() === null) {
$roomIDs = array_map(function (Room $room) use ($user) {
return $room->roomID;
}, (new \chat\data\user\User($target))->getRooms());
$roomIDs[] = $room->roomID;
}
else {
$roomIDs = [ $suspension->getRoom()->roomID ];
}
(new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ 'suspension' => $suspension
, 'roomIDs' => $roomIDs
, 'globally' => $this->isGlobally($parameters)
, 'target' => [ 'userID' => $target->userID
, 'username' => $target->username
]
])
]
, 'updateTimestamp' => true
]
)
)->executeAction();
}
/**
* Returns the database fields.
*
* @param mixed[] $parameters
* @param \chat\data\room\Room $room
* @param \wcf\data\user\UserProfile $user
* @return mixed[]
*/
protected function getSuspensionData($parameters, Room $room, UserProfile $user = null) {
$target = $this->getUser($parameters['username']);
$globally = $this->isGlobally($parameters);
$expires = $parameters['duration'];
$reason = $parameters['reason'] ?: '';
$roomID = $globally ? null : $room->roomID;
$objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('be.bastelstu.chat.suspension', $this->getObjectTypeName());
return [ 'time' => TIME_NOW
, 'expires' => $expires
, 'roomID' => $roomID
, 'userID' => $target->userID
, 'objectTypeID' => $objectTypeID
, 'reason' => $reason
, 'judgeID' => $user->userID
, 'judge' => $user->username
];
}
/**
* Returns whether a global suspension was requested.
*
* @param mixed[] $parameters
* @return boolean
*/
protected function isGlobally($parameters) {
return $parameters['globally'] === true;
}
}

View File

@ -0,0 +1,162 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\message\MessageAction;
use \chat\data\room\Room;
use \chat\data\suspension\Suspension;
use \chat\data\suspension\SuspensionAction;
use \chat\data\suspension\SuspensionList;
use \wcf\data\object\type\ObjectTypeCache;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\UserInputException;
use \wcf\system\WCF;
/**
* Represents a command that revokes suspensions
*/
abstract class AbstractUnsuspensionCommand extends AbstractCommand {
use TNeedsUser;
/**
* Returns the name of the object type for this suspension.
*
* @return string
*/
abstract public function getObjectTypeName();
/**
* Checks the permissions to execute this command.
* Throws if necessary.
*
* @see \chat\system\command\ICommand::validate()
*/
abstract protected function checkPermissions($parameters, Room $room, UserProfile $user);
/**
* @inheritDoc
*/
public function validate($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$this->assertParameter($parameters, 'username');
$this->assertParameter($parameters, 'globally');
$this->assertUser($parameters['username']);
$suspensions = $this->getSuspensionData($parameters, $room, $user);
if (empty($suspensions)) {
throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('chat.error.suspension.remove.empty'));
}
}
/**
* @inheritDoc
*/
public function execute($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$suspensions = $this->getSuspensionData($parameters, $room, $user);
WCF::getDB()->beginTransaction();
(new SuspensionAction($suspensions, 'revoke', [ ]))->executeAction();
$this->afterCreate($suspensions, $parameters, $room, $user);
WCF::getDB()->commitTransaction();
}
/**
* Creates chat messages informing about the removed suspensions.
*
* @param \chat\data\suspension\Suspension[] $suspension
* @param mixed[] $parameters
* @param \chat\data\room\Room $room
* @param \wcf\data\user\UserProfile $user
*/
protected function afterCreate($suspensions, $parameters, Room $room, UserProfile $user) {
$objectTypeID = $this->getMessageObjectTypeID('be.bastelstu.chat.messageType.unsuspend');
$target = $this->getUser($parameters['username']);
if ($this->isGlobally($parameters)) {
$roomIDs = array_map(function (Room $room) use ($user) {
return $room->roomID;
}, (new \chat\data\user\User($target))->getRooms());
$roomIDs[] = $room->roomID;
}
else {
$roomIDs = [ $room->roomID ];
}
(new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ 'objectType' => $this->getObjectTypeName()
, 'roomIDs' => $roomIDs
, 'globally' => $this->isGlobally($parameters)
, 'target' => [ 'userID' => $target->userID
, 'username' => $target->username
]
])
]
, 'updateTimestamp' => true
]
)
)->executeAction();
}
/**
* Returns the active suspensions.
*
* @param mixed[] $parameters
* @param \chat\data\room\Room $room
* @param \wcf\data\user\UserProfile $user
* @return mixed[]
*/
protected function getSuspensionData($parameters, Room $room, UserProfile $user = null) {
$target = $this->getUser($parameters['username']);
$roomID = $this->isGlobally($parameters) ? null : $room->roomID;
$objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('be.bastelstu.chat.suspension', $this->getObjectTypeName());
$suspensionList = new SuspensionList();
$suspensionList->getConditionBuilder()->add('(expires IS NULL OR expires > ?)', [ TIME_NOW ]);
$suspensionList->getConditionBuilder()->add('revoked IS NULL');
$suspensionList->getConditionBuilder()->add('userID = ?', [ $target->userID ]);
$suspensionList->getConditionBuilder()->add('objectTypeID = ?', [ $objectTypeID ]);
if ($roomID === null) {
$suspensionList->getConditionBuilder()->add('roomID IS NULL');
}
else {
$suspensionList->getConditionBuilder()->add('roomID = ?', [ $room->roomID ]);
}
$suspensionList->readObjects();
return array_filter($suspensionList->getObjects(), function (Suspension $suspension) {
return $suspension->isActive();
});
}
/**
* Returns whether a global suspension removal was requested.
*
* @param mixed[] $parameters
* @return boolean
*/
protected function isGlobally($parameters) {
return $parameters['globally'] === true;
}
}

View File

@ -0,0 +1,87 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\message\MessageAction;
use \chat\data\room\Room;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\exception\UserInputException;
use \wcf\system\message\censorship\Censorship;
use \wcf\system\WCF;
/**
* The away command marks the user as being away.
*/
class AwayCommand extends AbstractCommand implements ICommand {
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Away';
}
/**
* @inheritDoc
*/
public function validate($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(WCF::getUser());
$reason = $this->assertParameter($parameters, 'reason');
// search for censored words
if (ENABLE_CENSORSHIP) {
$result = Censorship::getInstance()->test($reason);
if ($result) {
throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.censoredWordsFound', [ 'censoredWords' => $result ]));
}
}
}
/**
* @inheritDoc
*/
public function execute($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(WCF::getUser());
$reason = $this->assertParameter($parameters, 'reason');
$objectTypeID = $this->getMessageObjectTypeID('be.bastelstu.chat.messageType.away');
$rooms = array_map(function (Room $room) use ($user) {
return [ 'roomID' => $room->roomID
, 'isSilent' => !$room->canWritePublicly($user)
];
}, (new \chat\data\user\User($user->getDecoratedObject()))->getRooms());
WCF::getDB()->beginTransaction();
$editor = new \wcf\data\user\UserEditor($user->getDecoratedObject());
$editor->update([ 'chatAway' => $reason ]);
(new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ 'message' => $reason
, 'rooms' => array_values($rooms)
])
]
, 'updateTimestamp' => true
]
)
)->executeAction();
WCF::getDB()->commitTransaction();
}
}

View File

@ -0,0 +1,80 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\message\MessageAction;
use \chat\data\room\Room;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\WCF;
/**
* The back command marks the user as being back.
*/
class BackCommand extends AbstractCommand implements ICommand {
/**
* @inheritDoc
*/
public function allowWithoutTrigger() {
return true;
}
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Back';
}
/**
* @inheritDoc
*/
public function validate($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(WCF::getUser());
if ($user->chatAway === null) throw new PermissionDeniedException();
}
/**
* @inheritDoc
*/
public function execute($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(WCF::getUser());
$objectTypeID = $this->getMessageObjectTypeID('be.bastelstu.chat.messageType.back');
$rooms = array_map(function (Room $room) use ($user) {
return [ 'roomID' => $room->roomID
, 'isSilent' => !$room->canWritePublicly($user)
];
}, (new \chat\data\user\User($user->getDecoratedObject()))->getRooms());
WCF::getDB()->beginTransaction();
$editor = new \wcf\data\user\UserEditor($user->getDecoratedObject());
$editor->update([ 'chatAway' => null ]);
(new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ 'rooms' => array_values($rooms) ])
]
, 'updateTimestamp' => true
]
)
)->executeAction();
WCF::getDB()->commitTransaction();
}
}

View File

@ -0,0 +1,94 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\room\Room;
use \chat\data\suspension\Suspension;
use \chat\data\suspension\SuspensionAction;
use \chat\system\permission\PermissionHandler;
use \wcf\data\object\type\ObjectTypeCache;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\WCF;
/**
* The ban command creates a new be.bastelstu.chat.suspension.ban suspension.
*/
class BanCommand extends AbstractSuspensionCommand implements ICommand {
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Ban';
}
/**
* @inheritDoc
*/
public function isAvailable(Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(WCF::getUser());
return $user->getPermission('mod.chat.canBan') || PermissionHandler::get($user)->getPermission($room, 'mod.canBan');
}
/**
* @inheritDoc
*/
public function getObjectTypeName() {
return 'be.bastelstu.chat.suspension.ban';
}
/**
* @inheritDoc
*/
protected function checkPermissions($parameters, Room $room, UserProfile $user) {
$permission = $user->getPermission('mod.chat.canBan');
if (!$this->isGlobally($parameters)) {
$permission = $permission || PermissionHandler::get($user)->getPermission($room, 'mod.canBan');
}
if (!$permission) throw new PermissionDeniedException();
}
/**
* @inheritDoc
*/
protected function afterCreate(Suspension $suspension, $parameters, Room $room, UserProfile $user) {
parent::afterCreate($suspension, $parameters, $room, $user);
$user = new \chat\data\user\User($suspension->getUser());
$rooms = [ ];
if ($suspension->getRoom() === null) {
$rooms = $user->getRooms();
}
else {
if ($user->isInRoom($suspension->getRoom())) {
$rooms = [ $suspension->getRoom() ];
}
}
foreach ($rooms as $room) {
$parameters = [ 'user' => $suspension->getUser()
, 'roomID' => $room->roomID
];
try {
(new \chat\data\room\RoomAction([ ], 'leave', $parameters))->executeAction();
}
catch (UserInputException $e) {
// User already left
}
}
}
}

View File

@ -0,0 +1,85 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\message\MessageAction;
use \chat\data\message\MessageEditor;
use \chat\data\room\Room;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\WCF;
/**
* BroadcastCommand sends a broadcast into all channels.
*/
class BroadcastCommand extends AbstractInputProcessedCommand implements ICommand {
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Broadcast';
}
/**
* @inheritDoc
*/
public function isAvailable(Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(WCF::getUser());
return $user->getPermission('mod.chat.canBroadcast');
}
/**
* @inheritDoc
*/
public function validate($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
if (!$user->getPermission('mod.chat.canBroadcast')) throw new PermissionDeniedException();
$this->setText($this->assertParameter($parameters, 'text'));
$this->validateText();
}
/**
* @inheritDoc
*/
public function execute($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$objectTypeID = $this->getMessageObjectTypeID('be.bastelstu.chat.messageType.broadcast');
$this->setText($this->assertParameter($parameters, 'text'));
WCF::getDB()->beginTransaction();
$message = (new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ 'message' => $this->processor->getHtml() ])
]
, 'updateTimestamp' => true
]
)
)->executeAction()['returnValues'];
$this->processor->setObjectID($message->messageID);
if (\wcf\system\message\embedded\object\MessageEmbeddedObjectManager::getInstance()->registerObjects($this->processor)) {
(new MessageEditor($message))->update([
'hasEmbeddedObjects' => 1
]);
}
WCF::getDB()->commitTransaction();
}
}

View File

@ -0,0 +1,285 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\message\MessageAction;
use \chat\data\room\Room;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\exception\UserInputException;
use \wcf\system\WCF;
use \wcf\util\StringUtil;
/**
* The color command allows a user to set a color for their username
*/
class ColorCommand extends AbstractCommand implements ICommand {
/**
* Regular expression matching RGB values in hexadecimal notation
* @var \wcf\system\Regex
*/
protected $colorRegex = null;
public function __construct(\wcf\data\DatabaseObject $object) {
parent::__construct($object);
$this->colorRegex = new \wcf\system\Regex('^#?([a-f0-9]{6})$', \wcf\system\Regex::CASE_INSENSITIVE);
}
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Color';
}
/**
* Map CSS color names to hexcodes.
* See: https://www.w3.org/TR/css3-color/#svg-color
*
* @var int[]
*/
public static $colors = [
'aliceblue' => 0xF0F8FF,
'antiquewhite' => 0xFAEBD7,
'aqua' => 0x00FFFF,
'aquamarine' => 0x7FFFD4,
'azure' => 0xF0FFFF,
'beige' => 0xF5F5DC,
'bisque' => 0xFFE4C4,
'black' => 0x000000,
'blanchedalmond' => 0xFFEBCD,
'blue' => 0x0000FF,
'bluescreenblue' => 0x0000AA,
'blueviolet' => 0x8A2BE2,
'brown' => 0xA52A2A,
'burlywood' => 0xDEB887,
'cadetblue' => 0x5F9EA0,
'chartreuse' => 0x7FFF00,
'chocolate' => 0xD2691E,
'coral' => 0xFF7F50,
'cornflowerblue' => 0x6495ED,
'cornsilk' => 0xFFF8DC,
'crimson' => 0xDC143C,
'cyan' => 0x00FFFF,
'darkblue' => 0x00008B,
'darkcyan' => 0x008B8B,
'darkgoldenrod' => 0xB8860B,
'darkgray' => 0xA9A9A9,
'darkgrey' => 0xA9A9A9,
'darkgreen' => 0x006400,
'darkkhaki' => 0xBDB76B,
'darkmagenta' => 0x8B008B,
'darkolivegreen' => 0x556B2F,
'darkorange' => 0xFF8C00,
'darkorchid' => 0x9932CC,
'darkred' => 0x8B0000,
'darksalmon' => 0xE9967A,
'darkseagreen' => 0x8FBC8F,
'darkslateblue' => 0x483D8B,
'darkslategray' => 0x2F4F4F,
'darkslategrey' => 0x2F4F4F,
'darkturquoise' => 0x00CED1,
'darkviolet' => 0x9400D3,
'deeppink' => 0xFF1493,
'deepskyblue' => 0x00BFFF,
'dimgray' => 0x696969,
'dimgrey' => 0x696969,
'dodgerblue' => 0x1E90FF,
'firebrick' => 0xB22222,
'floralwhite' => 0xFFFAF0,
'forestgreen' => 0x228B22,
'fuchsia' => 0xFF00FF,
'gainsboro' => 0xDCDCDC,
'ghostwhite' => 0xF8F8FF,
'gold' => 0xFFD700,
'goldenrod' => 0xDAA520,
'gray' => 0x808080,
'grey' => 0x808080,
'green' => 0x008000,
'greenyellow' => 0xADFF2F,
'honeydew' => 0xF0FFF0,
'hotpink' => 0xFF69B4,
'indianred' => 0xCD5C5C,
'indigo' => 0x4B0082,
'ivory' => 0xFFFFF0,
'khaki' => 0xF0E68C,
'lavender' => 0xE6E6FA,
'lavenderblush' => 0xFFF0F5,
'lawngreen' => 0x7CFC00,
'lemonchiffon' => 0xFFFACD,
'lightblue' => 0xADD8E6,
'lightcoral' => 0xF08080,
'lightcyan' => 0xE0FFFF,
'lightgoldenrodyellow' => 0xFAFAD2,
'lightgray' => 0xD3D3D3,
'lightgrey' => 0xD3D3D3,
'lightgreen' => 0x90EE90,
'lightpink' => 0xFFB6C1,
'lightsalmon' => 0xFFA07A,
'lightseagreen' => 0x20B2AA,
'lightskyblue' => 0x87CEFA,
'lightslategray' => 0x778899,
'lightslategrey' => 0x778899,
'lightsteelblue' => 0xB0C4DE,
'lightyellow' => 0xFFFFE0,
'lime' => 0x00FF00,
'limegreen' => 0x32CD32,
'linen' => 0xFAF0E6,
'magenta' => 0xFF00FF,
'maroon' => 0x800000,
'mediumaquamarine' => 0x66CDAA,
'mediumblue' => 0x0000CD,
'mediumorchid' => 0xBA55D3,
'mediumpurple' => 0x9370D8,
'mediumseagreen' => 0x3CB371,
'mediumslateblue' => 0x7B68EE,
'mediumspringgreen' => 0x00FA9A,
'mediumturquoise' => 0x48D1CC,
'mediumvioletred' => 0xC71585,
'midnightblue' => 0x191970,
'mintcream' => 0xF5FFFA,
'mistyrose' => 0xFFE4E1,
'moccasin' => 0xFFE4B5,
'navajowhite' => 0xFFDEAD,
'navy' => 0x000080,
'oldlace' => 0xFDF5E6,
'olive' => 0x808000,
'olivedrab' => 0x6B8E23,
'orange' => 0xFFA500,
'orangered' => 0xFF4500,
'orchid' => 0xDA70D6,
'oxford' => 0xF02D, // looks like green
'palegoldenrod' => 0xEEE8AA,
'palegreen' => 0x98FB98,
'paleturquoise' => 0xAFEEEE,
'palevioletred' => 0xD87093,
'papayawhip' => 0xFFEFD5,
'peachpuff' => 0xFFDAB9,
'peru' => 0xCD853F,
'pink' => 0xFFC0CB,
'plum' => 0xDDA0DD,
'powderblue' => 0xB0E0E6,
'purple' => 0x800080,
'red' => 0xFF0000,
'rosybrown' => 0xBC8F8F,
'royalblue' => 0x4169E1,
'saddlebrown' => 0x8B4513,
'sadwin' => 0x2067B2,
'salmon' => 0xFA8072,
'sandybrown' => 0xF4A460,
'seagreen' => 0x2E8B57,
'seashell' => 0xFFF5EE,
'sienna' => 0xA0522D,
'silver' => 0xC0C0C0,
'skyblue' => 0x87CEEB,
'slateblue' => 0x6A5ACD,
'slategray' => 0x708090,
'slategrey' => 0x708090,
'snow' => 0xFFFAFA,
'springgreen' => 0x00FF7F,
'steelblue' => 0x4682B4,
'tan' => 0xD2B48C,
'teal' => 0x008080,
'thistle' => 0xD8BFD8,
'tomato' => 0xFF6347,
'turquoise' => 0x40E0D0,
'violet' => 0xEE82EE,
'wheat' => 0xF5DEB3,
'white' => 0xFFFFFF,
'whitesmoke' => 0xF5F5F5,
'yellow' => 0xFFFF00,
'yellowgreen' => 0x9ACD32
];
/**
* @inheritDoc
*/
public function validate($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(WCF::getUser());
if (!$user->getPermission('user.chat.canSetColor')) throw new PermissionDeniedException();
foreach ($parameters as $parameter) {
$value = StringUtil::trim($this->assertParameter($parameter, 'value'));
$valid = true;
switch ($this->assertParameter($parameter, 'type')) {
case 'hex':
if (!$this->colorRegex->match($value)) {
throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('chat.error.invalidColor', [ 'color' => $value ]));
}
break;
case 'word':
if (!isset(self::$colors[$value])) {
throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('chat.error.invalidColor', [ 'color' => $value ]));
}
break;
default:
throw new UserInputException('message');
}
}
}
/**
* @inheritDoc
*/
public function execute($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(WCF::getUser());
$objectTypeID = $this->getMessageObjectTypeID('be.bastelstu.chat.messageType.color');
$colors = [ ];
if (!isset($parameters[1])) $parameters[1] = $parameters[0];
foreach ($parameters as $key => $parameter) {
$value = StringUtil::trim($this->assertParameter($parameter, 'value'));
switch ($this->assertParameter($parameter, 'type')) {
case 'hex':
$colors[$key] = hexdec($value);
break;
case 'word':
if (!isset(self::$colors[$value])) throw new UserInputException('message');
$colors[$key] = self::$colors[$value];
break;
default:
throw new UserInputException('message');
}
}
WCF::getDB()->beginTransaction();
$editor = new \wcf\data\user\UserEditor($user->getDecoratedObject());
$editor->update([ 'chatColor1' => $colors[0]
, 'chatColor2' => $colors[1]
]);
(new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ 'color1' => $colors[0]
, 'color2' => $colors[1]
])
]
, 'updateTimestamp' => true
]
)
)->executeAction();
WCF::getDB()->commitTransaction();
}
}

View File

@ -0,0 +1,76 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\room\Room;
use \wcf\data\user\UserProfile;
/**
* Interface for Command processors.
*/
interface ICommand {
/**
* Returns whether the command can be used even when
* no trigger is configured for it.
*
* @return boolean
*/
public function allowWithoutTrigger();
/**
* Returns the name of the JavaScript module.
*
* @return string
*/
public function getJavaScriptModuleName();
/**
* Returns whether this command theoretically is available
* in the given room, for the given user.
* If no user is given the active user should be assumed.
*
* The return value sets a flag for the JavaScript to
* consume. You still need to validate() this as well!
*
* @param Room $room
* @param UserProfile $user
* @return boolean
*/
public function isAvailable(Room $room, UserProfile $user = null);
/**
* Validates the execution of the command with the given parameters
* in the given room for the given user.
* If no user is given the active user should be assumed.
* This method must throw if the command may not be executed in this form.
*
* @param mixed $parameters
* @param Room $room
* @param UserProfile $user
*/
public function validate($parameters, Room $room, UserProfile $user = null);
/**
* Executes the command with the given parameters in the given room in
* the context of the given user.
* If no user is given the active user should be assumed.
* This method must throw if the command may not be executed in this form.
*
* @param mixed $parameters
* @param Room $room
* @param UserProfile $user
*/
public function execute($parameters, Room $room, UserProfile $user = null);
}

View File

@ -0,0 +1,88 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\message\MessageAction;
use \chat\data\room\Room;
use \chat\data\room\RoomCache;
use \wcf\data\user\User;
use \wcf\data\user\UserProfile;
/**
* The info command shows information about a single user.
*/
class InfoCommand extends AbstractCommand implements ICommand {
use TNeedsUser;
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Info';
}
/**
* @inheritDoc
*/
public function validate($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$this->assertUser($this->assertParameter($parameters, 'username'));
}
/**
* @inheritDoc
*/
public function execute($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$objectTypeID = $this->getMessageObjectTypeID('be.bastelstu.chat.messageType.info');
$target = new \chat\data\user\User($this->getUser($this->assertParameter($parameters, 'username')));
$rooms = array_values(array_map(function ($assoc) {
$room = RoomCache::getInstance()->getRoom($assoc['roomID']);
return [ 'title' => (string) $room
, 'roomID' => $assoc['roomID']
, 'lastPush' => $assoc['lastPush']
, 'lastPull' => $assoc['lastPull']
, 'active' => $assoc['active']
, 'link' => $room->getLink()
];
}, array_filter($target->getRoomAssociations(), function ($assoc) {
return RoomCache::getInstance()->getRoom($assoc['roomID'])->canSee();
})));
$payload = [ 'data' => [ 'rooms' => $rooms
, 'away' => $target->chatAway
, 'user' => $target
]
, 'caller' => $user
];
\wcf\system\event\EventHandler::getInstance()->fireAction($this, 'execute', $payload);
(new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize($payload['data'])
]
, 'updateTimestamp' => true
]
)
)->executeAction();
}
}

View File

@ -0,0 +1,84 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\message\MessageAction;
use \chat\data\room\Room;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\exception\UserInputException;
use \wcf\system\message\censorship\Censorship;
use \wcf\system\WCF;
/**
* MeCommand represents an action message.
*/
class MeCommand extends AbstractCommand implements ICommand {
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Me';
}
/**
* @inheritDoc
*/
public function validate($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
if (!$room->canWritePublicly($user)) throw new PermissionDeniedException();
$text = $this->assertParameter($parameters, 'text');
if (mb_strlen($text) === 0) {
throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.global.form.error.empty'));
}
// validate message length
if (mb_strlen($text) > CHAT_MAX_LENGTH) {
throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.tooLong', [ 'maxTextLength' => CHAT_MAX_LENGTH ]));
}
// search for censored words
if (ENABLE_CENSORSHIP) {
$result = Censorship::getInstance()->test($text);
if ($result) {
throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('wcf.message.error.censoredWordsFound', [ 'censoredWords' => $result ]));
}
}
}
/**
* @inheritDoc
*/
public function execute($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$objectTypeID = $this->getMessageObjectTypeID('be.bastelstu.chat.messageType.me');
(new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ 'message' => $this->assertParameter($parameters, 'text') ])
]
, 'updateTimestamp' => true
, 'grantPoints' => true
]
)
)->executeAction();
}
}

View File

@ -0,0 +1,63 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\room\Room;
use \chat\data\suspension\SuspensionAction;
use \chat\system\permission\PermissionHandler;
use \wcf\data\object\type\ObjectTypeCache;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\WCF;
/**
* The mute command creates a new be.bastelstu.chat.suspension.mute suspension.
*/
class MuteCommand extends AbstractSuspensionCommand implements ICommand {
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Mute';
}
/**
* @inheritDoc
*/
public function isAvailable(Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(WCF::getUser());
return $user->getPermission('mod.chat.canMute') || PermissionHandler::get($user)->getPermission($room, 'mod.canMute');
}
/**
* @inheritDoc
*/
public function getObjectTypeName() {
return 'be.bastelstu.chat.suspension.mute';
}
/**
* @inheritDoc
*/
protected function checkPermissions($parameters, Room $room, UserProfile $user) {
$permission = $user->getPermission('mod.chat.canMute');
if (!$this->isGlobally($parameters)) {
$permission = $permission || PermissionHandler::get($user)->getPermission($room, 'mod.canMute');
}
if (!$permission) throw new PermissionDeniedException();
}
}

View File

@ -0,0 +1,81 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\message\MessageAction;
use \chat\data\message\MessageEditor;
use \chat\data\room\Room;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\PermissionDeniedException;
/**
* The plain command creates a normal chat message
*/
class PlainCommand extends AbstractInputProcessedCommand implements ICommand {
/**
* @inheritDoc
*/
public function allowWithoutTrigger() {
return true;
}
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Plain';
}
/**
* @inheritDoc
*/
public function validate($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
if (!$room->canWritePublicly($user)) throw new PermissionDeniedException();
$this->setText($this->assertParameter($parameters, 'text'));
$this->validateText();
}
/**
* @inheritDoc
*/
public function execute($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$objectTypeID = $this->getMessageObjectTypeID('be.bastelstu.chat.messageType.plain');
$this->setText($this->assertParameter($parameters, 'text'));
$message = (new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ 'message' => $this->processor->getHtml() ])
]
, 'updateTimestamp' => true
, 'grantPoints' => true
]
)
)->executeAction()['returnValues'];
$this->processor->setObjectID($message->messageID);
if (\wcf\system\message\embedded\object\MessageEmbeddedObjectManager::getInstance()->registerObjects($this->processor)) {
(new MessageEditor($message))->update([
'hasEmbeddedObjects' => 1
]);
}
}
}

View File

@ -0,0 +1,53 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \wcf\data\user\User;
use \wcf\system\exception\UserInputException;
use \wcf\system\WCF;
/**
* Adds helpful functions for commands that operate on a user.
*/
trait TNeedsUser {
/**
* Returns the user with the given username.
*
* @param string $username
* @return \wcf\data\user\User
*/
protected function getUser($username) {
static $cache = [ ];
if (!isset($cache[$username])) {
$cache[$username] = User::getUserByUsername($username);
}
return $cache[$username];
}
/**
* Checks whether the given username is valid and throws otherwise.
*
* @param string $username
* @return \wcf\data\user\User
*/
protected function assertUser($username) {
$user = $this->getUser($username);
if (!$user->userID) throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('chat.error.userNotFound', [ 'username' => $username ]));
return $user;
}
}

View File

@ -0,0 +1,86 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\message\MessageAction;
use \chat\data\message\MessageEditor;
use \chat\data\room\Room;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\exception\UserInputException;
use \wcf\system\WCF;
/**
* TeamCommand sends a broadcast to all team members.
*/
class TeamCommand extends AbstractInputProcessedCommand implements ICommand {
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Team';
}
/**
* @inheritDoc
*/
public function isAvailable(Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(WCF::getUser());
return $user->getPermission('mod.chat.canTeam');
}
/**
* @inheritDoc
*/
public function validate($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
if (!$user->getPermission('mod.chat.canTeam')) throw new PermissionDeniedException();
$this->setText($this->assertParameter($parameters, 'text'));
$this->validateText();
}
/**
* @inheritDoc
*/
public function execute($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$objectTypeID = $this->getMessageObjectTypeID('be.bastelstu.chat.messageType.team');
$this->setText($this->assertParameter($parameters, 'text'));
WCF::getDB()->beginTransaction();
$message = (new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ 'message' => $this->processor->getHtml() ])
]
, 'updateTimestamp' => true
]
)
)->executeAction()['returnValues'];
$this->processor->setObjectID($message->messageID);
if (\wcf\system\message\embedded\object\MessageEmbeddedObjectManager::getInstance()->registerObjects($this->processor)) {
(new MessageEditor($message))->update([
'hasEmbeddedObjects' => 1
]);
}
WCF::getDB()->commitTransaction();
}
}

View File

@ -0,0 +1,136 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\message\MessageAction;
use \chat\data\room\Room;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\exception\UserInputException;
use \wcf\system\WCF;
/**
* The temproom command allows a user to manage temporary rooms.
*/
class TemproomCommand extends AbstractCommand implements ICommand {
use TNeedsUser;
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Temproom';
}
/**
* @inheritDoc
*/
public function validate($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(WCF::getUser());
switch ($this->assertParameter($parameters, 'type')) {
case 'create':
if (!$user->getPermission('user.chat.canTemproom')) throw new PermissionDeniedException();
break;
case 'invite':
if (!$room->isTemporary) throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('chat.error.notInTemproom'));
if ($room->ownerID !== $user->userID) throw new PermissionDeniedException();
$recipient = new UserProfile($this->assertUser($this->assertParameter($parameters, 'username')));
if ($recipient->isIgnoredUser($user->userID)) throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('chat.error.userIgnoresYou', [ 'user' => $recipient ]));
break;
case 'delete':
if (!$room->isTemporary) throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('chat.error.notInTemproom'));
if ($room->ownerID !== $user->userID) throw new PermissionDeniedException();
break;
default:
throw new UserInputException('message');
}
}
/**
* @inheritDoc
*/
public function execute($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(WCF::getUser());
switch ($this->assertParameter($parameters, 'type')) {
case 'create':
$fields = [ 'title' => WCF::getLanguage()->getDynamicVariable('chat.room.temporary.blueprint', [ 'user' => $user ])
, 'topic' => ''
, 'position' => 999
, 'isTemporary' => true
, 'ownerID' => $user->userID
];
WCF::getDB()->beginTransaction();
// create room
$tempRoom = (new \chat\data\room\RoomAction([], 'create', [ 'data' => $fields ]))->executeAction()['returnValues'];
$objectTypeID = $this->getMessageObjectTypeID('be.bastelstu.chat.messageType.temproomCreated');
(new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ 'room' => $tempRoom ])
]
, 'updateTimestamp' => true
]
)
)->executeAction();
WCF::getDB()->commitTransaction();
return;
case 'invite':
$recipient = $this->getUser($this->assertParameter($parameters, 'username'));
WCF::getDB()->beginTransaction();
try {
$sql = "INSERT INTO chat".WCF_N."_room_temporary_invite
(userID, roomID)
VALUES (?, ?)";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ $recipient->userID, $room->roomID ]);
}
catch (\wcf\system\database\DatabaseException $e) {
WCF::getDB()->rollBackTransaction();
// Duplicate key errors don't cause harm.
if ((string) $e->getCode() !== '23000') throw $e;
return;
}
$objectTypeID = $this->getMessageObjectTypeID('be.bastelstu.chat.messageType.temproomInvited');
(new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ 'recipient' => $recipient->userID
, 'recipientName' => $recipient->username
])
]
, 'updateTimestamp' => true
]
)
)->executeAction();
WCF::getDB()->commitTransaction();
return;
case 'delete':
(new \chat\data\room\RoomAction([ $room ], 'delete'))->executeAction();
return;
default:
throw new UserInputException('message');
}
}
}

View File

@ -0,0 +1,64 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\room\Room;
use \chat\data\suspension\Suspension;
use \chat\data\suspension\SuspensionAction;
use \chat\system\permission\PermissionHandler;
use \wcf\data\object\type\ObjectTypeCache;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\WCF;
/**
* The unban command revokes a new be.bastelstu.chat.suspension.ban suspension.
*/
class UnbanCommand extends AbstractUnsuspensionCommand implements ICommand {
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Unban';
}
/**
* @inheritDoc
*/
public function isAvailable(Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(WCF::getUser());
return $user->getPermission('mod.chat.canBan') || PermissionHandler::get($user)->getPermission($room, 'mod.canBan');
}
/**
* @inheritDoc
*/
public function getObjectTypeName() {
return 'be.bastelstu.chat.suspension.ban';
}
/**
* @inheritDoc
*/
protected function checkPermissions($parameters, Room $room, UserProfile $user) {
$permission = $user->getPermission('mod.chat.canBan');
if (!$this->isGlobally($parameters)) {
$permission = $permission || PermissionHandler::get($user)->getPermission($room, 'mod.canBan');
}
if (!$permission) throw new PermissionDeniedException();
}
}

View File

@ -0,0 +1,63 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\room\Room;
use \chat\data\suspension\SuspensionAction;
use \chat\system\permission\PermissionHandler;
use \wcf\data\object\type\ObjectTypeCache;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\WCF;
/**
* The unmute command revokes a new be.bastelstu.chat.suspension.mute suspension.
*/
class UnmuteCommand extends AbstractUnsuspensionCommand implements ICommand {
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Unmute';
}
/**
* @inheritDoc
*/
public function isAvailable(Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(WCF::getUser());
return $user->getPermission('mod.chat.canMute') || PermissionHandler::get($user)->getPermission($room, 'mod.canMute');
}
/**
* @inheritDoc
*/
public function getObjectTypeName() {
return 'be.bastelstu.chat.suspension.mute';
}
/**
* @inheritDoc
*/
protected function checkPermissions($parameters, Room $room, UserProfile $user) {
$permission = $user->getPermission('mod.chat.canMute');
if (!$this->isGlobally($parameters)) {
$permission = $permission || PermissionHandler::get($user)->getPermission($room, 'mod.canMute');
}
if (!$permission) throw new PermissionDeniedException();
}
}

View File

@ -0,0 +1,74 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\message\MessageAction;
use \chat\data\room\Room;
use \wcf\data\user\User;
use \wcf\data\user\UserProfile;
/**
* The where command shows the distribution of users among
* the different chat rooms.
*/
class WhereCommand extends AbstractCommand implements ICommand {
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Where';
}
/**
* @inheritDoc
*/
public function validate($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
}
/**
* @inheritDoc
*/
public function execute($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$objectTypeID = $this->getMessageObjectTypeID('be.bastelstu.chat.messageType.where');
$roomList = new \chat\data\room\RoomList();
$roomList->readObjects();
$rooms = array_map(function (Room $room) {
$users = array_map(function (\chat\data\user\User $user) {
return $user->jsonSerialize();
}, $room->getUsers());
return [ 'roomID' => $room->roomID
, 'users' => array_values($users)
];
}, array_filter($roomList->getObjects(), function (Room $room) {
return $room->canSee();
}));
(new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize($rooms)
]
, 'updateTimestamp' => true
]
)
)->executeAction();
}
}

View File

@ -0,0 +1,83 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\command;
use \chat\data\message\MessageAction;
use \chat\data\message\MessageEditor;
use \chat\data\room\Room;
use \wcf\data\user\User;
use \wcf\data\user\UserProfile;
use \wcf\system\exception\UserInputException;
use \wcf\system\WCF;
/**
* The whisper command creates a private message
* between two chat users.
*/
class WhisperCommand extends AbstractInputProcessedCommand implements ICommand {
use TNeedsUser;
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/Command/Whisper';
}
/**
* @inheritDoc
*/
public function validate($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$recipient = new UserProfile($this->assertUser($this->assertParameter($parameters, 'username')));
if ($recipient->isIgnoredUser($user->userID)) throw new UserInputException('message', WCF::getLanguage()->getDynamicVariable('chat.error.userIgnoresYou', [ 'user' => $recipient ]));
$this->setText($this->assertParameter($parameters, 'text'));
$this->validateText();
}
/**
* @inheritDoc
*/
public function execute($parameters, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$objectTypeID = $this->getMessageObjectTypeID('be.bastelstu.chat.messageType.whisper');
$recipient = $this->assertUser($this->assertParameter($parameters, 'username'));
$this->setText($this->assertParameter($parameters, 'text'));
$message = (new MessageAction([ ], 'create', [ 'data' => [ 'roomID' => $room->roomID
, 'userID' => $user->userID
, 'username' => $user->username
, 'time' => TIME_NOW
, 'objectTypeID' => $objectTypeID
, 'payload' => serialize([ 'message' => $this->processor->getHtml()
, 'recipient' => $recipient->userID
, 'recipientName' => $recipient->username
])
]
, 'updateTimestamp' => true
]
)
)->executeAction()['returnValues'];
$this->processor->setObjectID($message->messageID);
if (\wcf\system\message\embedded\object\MessageEmbeddedObjectManager::getInstance()->registerObjects($this->processor)) {
(new MessageEditor($message))->update([
'hasEmbeddedObjects' => 1
]);
}
}
}

View File

@ -0,0 +1,46 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\condition\room;
use \chat\data\room\RoomList;
use \wcf\data\DatabaseObject;
use \wcf\data\DatabaseObjectList;
use \wcf\system\exception\SystemException;
/**
* Condition implementation for rooms to only include non-empty rooms in lists.
*/
class RoomFilledCondition extends \wcf\system\condition\AbstractCheckboxCondition implements \wcf\system\condition\IObjectListCondition {
/**
* @inheritDoc
*/
protected $fieldName = 'chatRoomIsFilled';
/**
* @inheritDoc
*/
protected $label = 'chat.room.condition.isFilled';
/**
* @inheritDoc
*/
public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
if (!($objectList instanceof RoomList)) {
throw new \wcf\system\exception\ParentClassException(get_class($objectList), RoomList::class);
}
$objectList->getConditionBuilder()->add("EXISTS (SELECT 1 FROM chat".WCF_N."_room_to_user r2u WHERE r2u.roomID = room.roomID AND active = ?)", [ 1 ]);
}
}

View File

@ -0,0 +1,40 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\event\listener;
use \wcf\system\WCF;
/**
* Vaporizes unneeded data.
*/
class HourlyCleanUpCronjobExecuteChatCleanUpListener implements \wcf\system\event\listener\IParameterizedEventListener {
/**
* @see \wcf\system\event\listener\IParameterizedEventListener::execute()
*/
public function execute($eventObj, $className, $eventName, array &$parameters) {
(new \chat\data\message\MessageAction([ ], 'prune'))->executeAction();
(new \chat\data\user\UserAction([], 'clearDeadSessions'))->executeAction();
$sql = "UPDATE chat".WCF_N."_room_to_user
SET active = ?
WHERE (roomID, userID) NOT IN (SELECT roomID, userID FROM chat".WCF_N."_session)
AND active = ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ 0, 1 ]);
if ($statement->getAffectedRows()) {
\wcf\functions\exception\logThrowable(new \Exception('Unreachable'));
}
}
}

View File

@ -0,0 +1,43 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\event\listener;
use \wcf\system\WCF;
/**
* Removes empty temporary rooms.
*/
class HourlyCleanUpCronjobExecuteTemproomListener implements \wcf\system\event\listener\IParameterizedEventListener {
/**
* @see \wcf\system\event\listener\IParameterizedEventListener::execute()
*/
public function execute($eventObj, $className, $eventName, array &$parameters) {
$roomList = new \chat\data\room\RoomList();
$roomList->getConditionBuilder()->add('isTemporary = ?', [ 1 ]);
$roomList->readObjects();
$toDelete = [ ];
WCF::getDB()->beginTransaction();
foreach ($roomList as $room) {
if (count($room->getUsers()) === 0) {
$toDelete[] = $room;
}
}
if (!empty($toDelete)) {
(new \chat\data\room\RoomAction($toDelete, 'delete'))->executeAction();
}
WCF::getDB()->commitTransaction();
}
}

View File

@ -0,0 +1,61 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\event\listener;
use \chat\data\suspension\Suspension;
use \wcf\data\object\type\ObjectTypeCache;
use \wcf\system\WCF;
/**
* Fetches information about the users suspensions
*/
class InfoCommandSuspensionsListener implements \wcf\system\event\listener\IParameterizedEventListener {
/**
* @see \wcf\system\event\listener\IParameterizedEventListener::execute()
*/
public function execute($eventObj, $className, $eventName, array &$parameters) {
if (!$parameters['caller']->getPermission('admin.chat.canManageSuspensions')) {
return;
}
$target = $parameters['data']['user'];
$parameters['data']['suspensions'] = [ ];
$suspensionList = new \chat\data\suspension\SuspensionList();
$suspensionList->getConditionBuilder()->add('(expires IS NULL OR expires > ?)', [ TIME_NOW ]);
$suspensionList->getConditionBuilder()->add('revoked IS NULL');
$suspensionList->getConditionBuilder()->add('userID = ?', [ $target->userID ]);
$suspensionList->sqlOrderBy = 'expires ASC, time ASC';
$suspensionList->readObjects();
$suspensions = array_filter($suspensionList->getObjects(), function (Suspension $suspension) {
return $suspension->isActive();
});
$parameters['data']['suspensions'] = array_values(array_map(function ($suspension) {
$room = \chat\data\room\RoomCache::getInstance()->getRoom($suspension->roomID);
$suspension = $suspension->jsonSerialize();
if ($room) {
$suspension['room'] = [ 'title' => $room->getTitle()
, 'link' => $room->getLink()
];
}
return $suspension;
}, $suspensions));
}
}

View File

@ -0,0 +1,44 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\event\listener;
use \chat\data\command\CommandCache;
use \wcf\system\cache\runtime\UserProfileRuntimeCache;
/**
* Adds moderator permissiosn to the user object.
*/
class RoomActionGetUsersModeratorListener implements \wcf\system\event\listener\IParameterizedEventListener {
/**
* @see \wcf\system\event\listener\IParameterizedEventListener::execute()
*/
public function execute($eventObj, $className, $eventName, array &$users) {
$room = $eventObj->getObjects()[0]->getDecoratedObject();
$package = \wcf\data\package\PackageCache::getInstance()->getPackageByIdentifier('be.bastelstu.chat');
$muteCommand = CommandCache::getInstance()->getCommandByPackageAndIdentifier($package, 'mute')->getProcessor();
$banCommand = CommandCache::getInstance()->getCommandByPackageAndIdentifier($package, 'ban')->getProcessor();
$users = array_map(function (array $user) use ($room, $muteCommand, $banCommand) {
$userProfile = UserProfileRuntimeCache::getInstance()->getObject($user['userID']);
if (!isset($user['permissions'])) $user['permissions'] = [];
$user['permissions']['canMute'] = $muteCommand->isAvailable($room, $userProfile);
$user['permissions']['canBan'] = $banCommand->isAvailable($room, $userProfile);
return $user;
}, $users);
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\event\listener;
use \chat\data\suspension\Suspension;
use \wcf\data\object\type\ObjectTypeCache;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\WCF;
/**
* Denies access to banned users.
*/
class RoomCanJoinBanListener implements \wcf\system\event\listener\IParameterizedEventListener {
/**
* @see \wcf\system\event\listener\IParameterizedEventListener::execute()
*/
public function execute($eventObj, $className, $eventName, array &$parameters) {
$objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('be.bastelstu.chat.suspension', 'be.bastelstu.chat.suspension.ban');
if (!$objectTypeID) throw new \LogicException('Unreachable');
$suspensions = Suspension::getActiveSuspensionsByTriple($objectTypeID, $parameters['user']->getDecoratedObject(), $eventObj);
if (!empty($suspensions)) {
$parameters['result'] = new PermissionDeniedException(WCF::getLanguage()->getDynamicVariable('chat.suspension.info.be.bastelstu.chat.suspension.ban'));
}
}
}

View File

@ -0,0 +1,42 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\event\listener;
use \chat\system\permission\PermissionHandler;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\WCF;
/**
* Denies access when room is full.
*/
class RoomCanJoinUserLimitListener implements \wcf\system\event\listener\IParameterizedEventListener {
/**
* @see \wcf\system\event\listener\IParameterizedEventListener::execute()
*/
public function execute($eventObj, $className, $eventName, array &$parameters) {
if ($eventObj->userLimit === 0) return;
$users = $eventObj->getUsers();
if (count($users) < $eventObj->userLimit) return;
$user = new \chat\data\user\User($parameters['user']->getDecoratedObject());
if ($user->isInRoom($eventObj)) return;
$canIgnoreLimit = PermissionHandler::get($parameters['user'])->getPermission($eventObj, 'mod.canIgnoreUserLimit');
if ($canIgnoreLimit) return;
$parameters['result'] = new PermissionDeniedException(WCF::getLanguage()->get('chat.error.roomFull'));
}
}

View File

@ -0,0 +1,44 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\event\listener;
use \chat\system\permission\PermissionHandler;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\WCF;
/**
* Denies access to temporary rooms, unless invited.
*/
class RoomCanSeeTemproomListener implements \wcf\system\event\listener\IParameterizedEventListener {
/**
* @see \wcf\system\event\listener\IParameterizedEventListener::execute()
*/
public function execute($eventObj, $className, $eventName, array &$parameters) {
if (!$eventObj->isTemporary) return;
$user = new \chat\data\user\User($parameters['user']->getDecoratedObject());
if ($eventObj->ownerID === $user->userID) return;
$sql = "SELECT COUNT(*)
FROM chat".WCF_N."_room_temporary_invite
WHERE userID = ?
AND roomID = ?";
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute([ $user->userID, $eventObj->roomID ]);
if ($statement->fetchSingleColumn() > 0) return;
$parameters['result'] = new PermissionDeniedException();
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\event\listener;
use \chat\data\suspension\Suspension;
use \wcf\data\object\type\ObjectTypeCache;
use \wcf\system\exception\PermissionDeniedException;
use \wcf\system\WCF;
/**
* Denies access to muted users.
*/
class RoomCanWritePubliclyMuteListener implements \wcf\system\event\listener\IParameterizedEventListener {
/**
* @see \wcf\system\event\listener\IParameterizedEventListener::execute()
*/
public function execute($eventObj, $className, $eventName, array &$parameters) {
$objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('be.bastelstu.chat.suspension', 'be.bastelstu.chat.suspension.mute');
if (!$objectTypeID) throw new \LogicException('Unreachable');
$suspensions = Suspension::getActiveSuspensionsByTriple($objectTypeID, $parameters['user']->getDecoratedObject(), $eventObj);
if (!empty($suspensions)) {
$parameters['result'] = new PermissionDeniedException(WCF::getLanguage()->getDynamicVariable('chat.suspension.info.be.bastelstu.chat.suspension.mute'));
}
}
}

View File

@ -0,0 +1,29 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\event\listener;
/**
* Disallow editing of temprooms in ACP.
*/
class RoomEditFormTemproomListener implements \wcf\system\event\listener\IParameterizedEventListener {
/**
* @see \wcf\system\event\listener\IParameterizedEventListener::execute()
*/
public function execute($eventObj, $className, $eventName, array &$parameters) {
if ($eventObj->room->isTemporary) {
throw new \wcf\system\exception\PermissionDeniedException();
}
}
}

View File

@ -0,0 +1,27 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\event\listener;
/**
* Hides temprooms in ACP.
*/
class RoomListPageTemproomListener implements \wcf\system\event\listener\IParameterizedEventListener {
/**
* @see \wcf\system\event\listener\IParameterizedEventListener::execute()
*/
public function execute($eventObj, $className, $eventName, array &$parameters) {
$eventObj->objectList->getConditionBuilder()->add('isTemporary = ?', [ 0 ]);
}
}

View File

@ -0,0 +1,31 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 3
* or later of the General Public License.
*/
namespace chat\system\event\listener;
use \chat\data\room\Room;
/**
* Hides temprooms in ACP.
*/
class SuspensionListPageTemproomListener implements \wcf\system\event\listener\IParameterizedEventListener {
/**
* @see \wcf\system\event\listener\IParameterizedEventListener::execute()
*/
public function execute($eventObj, $className, $eventName, array &$parameters) {
$eventObj->availableRooms = array_filter($eventObj->availableRooms, function (Room $room) {
return !$room->isTemporary;
});
}
}

View File

@ -0,0 +1,80 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\message\type;
use \chat\data\message\Message;
use \chat\data\room\Room;
use \wcf\data\user\UserProfile;
/**
* AwayMessageType represents a notice that a user now is away from chat.
*/
class AwayMessageType implements IMessageType {
use TDefaultPayload;
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/MessageType/Away';
}
/**
* @see \chat\system\message\type\IMessageType::canSee()
*/
public function canSee(Message $message, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$roomIDs = array_map(function ($item) {
return $item['roomID'];
}, $message->payload['rooms']);
$parameters = [ 'message' => $message
, 'room' => $room
, 'user' => $user
, 'canSee' => in_array($room->roomID, $roomIDs, true)
];
\wcf\system\event\EventHandler::getInstance()->fireAction($this, 'canSee', $parameters);
return $parameters['canSee'];
}
/**
* @see \chat\system\message\type\IMessageType::canSeeInLog()
*/
public function canSeeInLog(Message $message, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$roomIDs = array_map(function ($item) {
return $item['roomID'];
}, $message->payload['rooms']);
$parameters = [ 'message' => $message
, 'room' => $room
, 'user' => $user
, 'canSee' => in_array($room->roomID, $roomIDs, true)
];
\wcf\system\event\EventHandler::getInstance()->fireAction($this, 'canSeeInLog', $parameters);
return $parameters['canSee'];
}
/**
* @see»\chat\system\message\type\IMessageType::supportsFastSelect()
*/
public function supportsFastSelect() {
return false;
}
}

View File

@ -0,0 +1,80 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\message\type;
use \chat\data\message\Message;
use \chat\data\room\Room;
use \wcf\data\user\UserProfile;
/**
* BackMessageType represents a notice that a user now is now back.
*/
class BackMessageType implements IMessageType {
use TDefaultPayload;
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/MessageType/Back';
}
/**
* @see \chat\system\message\type\IMessageType::canSee()
*/
public function canSee(Message $message, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$roomIDs = array_map(function ($item) {
return $item['roomID'];
}, $message->payload['rooms']);
$parameters = [ 'message' => $message
, 'room' => $room
, 'user' => $user
, 'canSee' => in_array($room->roomID, $roomIDs, true)
];
\wcf\system\event\EventHandler::getInstance()->fireAction($this, 'canSee', $parameters);
return $parameters['canSee'];
}
/**
* @see \chat\system\message\type\IMessageType::canSeeInLog()
*/
public function canSeeInLog(Message $message, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$roomIDs = array_map(function ($item) {
return $item['roomID'];
}, $message->payload['rooms']);
$parameters = [ 'message' => $message
, 'room' => $room
, 'user' => $user
, 'canSee' => in_array($room->roomID, $roomIDs, true)
];
\wcf\system\event\EventHandler::getInstance()->fireAction($this, 'canSeeInLog', $parameters);
return $parameters['canSee'];
}
/**
* @see»\chat\system\message\type\IMessageType::supportsFastSelect()
*/
public function supportsFastSelect() {
return false;
}
}

View File

@ -0,0 +1,89 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\message\type;
use \chat\data\message\Message;
use \chat\data\room\Room;
use \wcf\data\user\UserProfile;
/**
* BroadcastMessageType represents a broadcasted message.
*/
class BroadcastMessageType extends PlainMessageType {
/**
* HtmlOutputProcessor to use.
* @var \wcf\system\html\output\HtmlOutputProcessor
*/
protected $processor = null;
public function __construct() {
$this->processor = new \wcf\system\html\output\HtmlOutputProcessor();
}
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/MessageType/Broadcast';
}
/**
* @see \chat\system\message\type\IMessageType::canSee()
*/
public function canSee(Message $message, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$parameters = [ 'message' => $message
, 'room' => $room
, 'user' => $user
, 'canSee' => true
];
\wcf\system\event\EventHandler::getInstance()->fireAction($this, 'canSee', $parameters);
return $parameters['canSee'];
}
/**
* @see \chat\system\message\type\IMessageType::canSeeInLog()
*/
public function canSeeInLog(Message $message, Room $room, UserProfile $user = null) {
if ($user === null) $user = new UserProfile(\wcf\system\WCF::getUser());
$parameters = [ 'message' => $message
, 'room' => $room
, 'user' => $user
, 'canSee' => true
];
\wcf\system\event\EventHandler::getInstance()->fireAction($this, 'canSeeInLog', $parameters);
return $parameters['canSee'];
}
/**
* @inheritDoc
*/
public function canDelete(\chat\data\message\Message $message, \wcf\data\user\UserProfile $user = null) {
if ($user === null) $user = new \wcf\data\user\UserProfile(\wcf\system\WCF::getUser());
return $user->getPermission('mod.chat.canDelete');
}
/**
* @see»\chat\system\message\type\IMessageType::supportsFastSelect()
*/
public function supportsFastSelect() {
return false;
}
}

View File

@ -0,0 +1,54 @@
<?php
/*
* Copyright (c) 2010-2018 Tim Düsterhus.
*
* Use of this software is governed by the Business Source License
* included in the LICENSE file.
*
* Change Date: 2022-08-16
*
* On the date above, in accordance with the Business Source
* License, use of this software will be governed by version 2
* or later of the General Public License.
*/
namespace chat\system\message\type;
use \chat\data\message\Message;
use \chat\data\room\Room;
use \wcf\data\user\UserProfile;
/**
* ChatUpdateMessageType informs the chat about a back end update.
*/
class ChatUpdateMessageType implements IMessageType {
use TDefaultPayload;
/**
* @inheritDoc
*/
public function getJavaScriptModuleName() {
return 'Bastelstu.be/Chat/MessageType/ChatUpdate';
}
/**
* @see \chat\system\message\type\IMessageType::canSee()
*/
public function canSee(Message $message, Room $room, UserProfile $user = null) {
return true;
}
/**
* @see \chat\system\message\type\IMessageType::canSeeInLog()
*/
public function canSeeInLog(Message $message, Room $room, UserProfile $user = null) {
return true;
}
/**
* @see»\chat\system\message\type\IMessageType::supportsFastSelect()
*/
public function supportsFastSelect() {
return false;
}
}

Some files were not shown because too many files have changed in this diff Show More