<allowedchildren>none</allowedchildren>
<sourcecode>1</sourcecode>
</bbcode>
+
+ <bbcode name="attach">
+ <classname>wcf\system\bbcode\AttachmentBBCode</classname>
+ <attributes>
+ <attribute name="0">
+ <validationpattern>^\d+$</validationpattern>
+ <required>1</required>
+ <usetext>1</usetext>
+ </attribute>
+ <attribute name="1">
+ <validationpattern>^(left|right)$</validationpattern>
+ </attribute>
+ </attributes>
+ </bbcode>
</import>
</data>
\ No newline at end of file
<definition>
<name>com.woltlab.wcf.attachment.objectType</name>
<interfacename>wcf\system\attachment\IAttachmentObjectType</interfacename>
- </definition>
+ </definition>
+
+ <definition>
+ <name>com.woltlab.wcf.message.quote</name>
+ <interfacename>wcf\system\message\IMessageQuoteHandler</interfacename>
+ </definition>
</import>
-</data>
\ No newline at end of file
+</data>
<category name="security.censorship">
<parent>security</parent>
</category>
+ <category name="message.censorship">
+ <parent>security.censorship</parent>
+ </category>
<!-- /security -->
<!-- message -->
<category name="message.general">
<parent>message</parent>
</category>
+ <category name="message.general.share">
+ <parent>message.general</parent>
+ </category>
<category name="message.attachment">
<parent>message</parent>
<optiontype>integer</optiontype>
<defaultvalue><![CDATA[280]]></defaultvalue>
</option>
+
+ <!-- message.general -->
+ <option name="enable_bbcodes_default_value">
+ <categoryname>message.general</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="enable_html_default_value">
+ <categoryname>message.general</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ </option>
+ <option name="enable_smilies_default_value">
+ <categoryname>message.general</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="pre_parse_default_value">
+ <categoryname>message.general</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="show_signature_default_value">
+ <categoryname>message.general</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <!-- /message.general -->
+
+ <!-- message.general.share -->
+ <option name="enable_share_buttons">
+ <categoryname>message.general.share</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ <enableoptions><![CDATA[share_buttons_show_count]]></enableoptions>
+ </option>
+ <option name="share_buttons_show_count">
+ <categoryname>message.general.share</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <!-- /message.general.share -->
+
+ <!-- message.censorship -->
+ <option name="enable_censorship">
+ <categoryname>message.censorship</categoryname>
+ <optiontype>boolean</optiontype>
+ <enableoptions><![CDATA[censored_words]]></enableoptions>
+ </option>
+ <option name="censored_words">
+ <categoryname>message.censorship</categoryname>
+ <optiontype>textarea</optiontype>
+ </option>
+ <!-- /message.censorship -->
</options>
</import>
</data>
--- /dev/null
+<ul class="smileyList">
+ {foreach from=$smilies item=smiley}
+ <li><a title="{lang}{$smiley->smileyTitle}{/lang}" class="jsTooltip jsSmiley" data-smiley-code="{$smiley->smileyCode}"><img src="{$smiley->getURL()}" alt="{$smiley->smileyCode}" class="icon24" /></a></li>
+ {/foreach}
+</ul>
\ No newline at end of file
--- /dev/null
+WCF.Language.addObject({
+ 'wcf.message.quote.insertAllQuotes': '{lang}wcf.message.quote.insertAllQuotes{/lang}',
+ 'wcf.message.quote.insertSelectedQuotes': '{lang}wcf.message.quote.insertSelectedQuotes{/lang}',
+ 'wcf.message.quote.manageQuotes': '{lang}wcf.message.quote.manageQuotes{/lang}',
+ 'wcf.message.quote.quoteSelected': '{lang}wcf.message.quote.quoteSelected{/lang}',
+ 'wcf.message.quote.removeAllQuotes': '{lang}wcf.message.quote.removeAllQuotes{/lang}',
+ 'wcf.message.quote.removeSelectedQuotes': '{lang}wcf.message.quote.removeSelectedQuotes{/lang}',
+ 'wcf.message.quote.showQuotes': '{lang}wcf.message.quote.showQuotes{/lang}'
+});
+
+{if !$wysiwygSelector|isset}{assign var=wysiwygSelector value=''}{/if}
+{if !$supportPaste|isset}{assign var=supportPaste value=false}{/if}
+var $quoteManager = new WCF.Message.Quote.Manager({@$__quoteCount}, '{$wysiwygSelector|encodeJS}', {if $supportPaste}true{else}false{/if}, [ {implode from=$__quoteRemove item=quoteID}'{$quoteID}'{/implode} ]);
\ No newline at end of file
--- /dev/null
+<div id="attachments" class="jsOnly formAttachmentContent tabMenuContent container containerPadding">
+ <ul class="formAttachmentList clearfix"{if !$attachmentHandler->getAttachmentList()|count} style="display: none"{/if}>
+ {foreach from=$attachmentHandler->getAttachmentList() item=$attachment}
+ <li class="box48">
+ {if $attachment->tinyThumbnailType}
+ <img src="{link controller='Attachment' object=$attachment}tiny=1{/link}" alt="" class="attachmentTinyThumbnail" />
+ {else}
+ <span class="icon icon48 icon-paper-clip"></span>
+ {/if}
+
+ <div>
+ <div>
+ <p><a href="{link controller='Attachment' object=$attachment}{/link}"{if $attachment->isImage} title="{$attachment->filename}" class="jsImageViewer"{/if}>{$attachment->filename}</a></p>
+ <small>{@$attachment->filesize|filesize}</small>
+ </div>
+
+ <ul>
+ <li><span class="icon icon16 icon-remove pointer jsTooltip jsDeleteButton " title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$attachment->attachmentID}" data-confirm-message="{lang}wcf.attachment.delete.sure{/lang}"></span></li>
+ <li><span class="icon icon16 icon-paste pointer jsTooltip jsButtonInsertAttachment" title="{lang}wcf.attachment.insert{/lang}" data-object-id="{@$attachment->attachmentID}" /></li>
+ </ul>
+ </div>
+ </li>
+ {/foreach}
+ </ul>
+
+ <dl class="wide">
+ <dd>
+ <div></div>
+ <small>{lang}wcf.attachment.upload.limits{/lang}</small>
+ </dd>
+ </dl>
+
+ {event name='fields'}
+</div>
+
+<script type="text/javascript" src="{@$__wcf->getPath()}js/WCF.Attachment{if !ENABLE_DEBUG_MODE}.min{/if}.js"></script>
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.attachment.upload.error.invalidExtension': '{lang}wcf.attachment.upload.error.invalidExtension{/lang}',
+ 'wcf.attachment.upload.error.tooLarge': '{lang}wcf.attachment.upload.error.tooLarge{/lang}',
+ 'wcf.attachment.upload.error.reachedLimit': '{lang}wcf.attachment.upload.error.reachedLimit{/lang}',
+ 'wcf.attachment.upload.error.reachedRemainingLimit': '{lang}wcf.attachment.upload.error.reachedRemainingLimit{/lang}',
+ 'wcf.attachment.upload.error.uploadFailed': '{lang}wcf.attachment.upload.error.uploadFailed{/lang}',
+ 'wcf.global.button.upload': '{lang}wcf.global.button.upload{/lang}',
+ 'wcf.attachment.insert': '{lang}wcf.attachment.insert{/lang}',
+ 'wcf.attachment.delete.sure': '{lang}wcf.attachment.delete.sure{/lang}'
+ });
+
+ new WCF.Attachment.Upload($('#attachments > dl > dd > div'), $('#attachments > ul'), '{@$attachmentObjectType}', '{@$attachmentObjectID}', '{$tmpHash|encodeJS}', '{@$attachmentParentObjectID}', {@$attachmentHandler->getMaxCount()}, '{@$wysiwygContainerID}');
+ new WCF.Action.Delete('wcf\\data\\attachment\\AttachmentAction', '.formAttachmentList > li');
+ });
+ //]]>
+</script>
+
+<input type="hidden" name="tmpHash" value="{$tmpHash}" />
\ No newline at end of file
--- /dev/null
+{if $availableContentLanguages|count}
+ <dl{if $errorField == 'languageID'} class="formError"{/if}>
+ <dt>{lang}wcf.user.language{/lang}</dt>
+ <dd id="languageIDContainer">
+ <noscript>
+ <select name="languageID" id="languageID">
+ {foreach from=$availableContentLanguages item=contentLanguage}
+ <option value="{@$contentLanguage->languageID}">{$contentLanguage}</option>
+ {/foreach}
+ </select>
+ </noscript>
+ </dd>
+ </dl>
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ var $languages = {
+ {implode from=$availableContentLanguages item=contentLanguage}
+ '{@$contentLanguage->languageID}': {
+ iconPath: '{@$contentLanguage->getIconPath()}',
+ languageName: '{$contentLanguage}'
+ }
+ {/implode}
+ };
+
+ new WCF.Language.Chooser('languageIDContainer', 'languageID', {$languageID}, $languages);
+ });
+ //]]>
+ </script>
+{/if}
\ No newline at end of file
--- /dev/null
+<button id="previewButton" class="jsOnly" accesskey="p">{lang}wcf.global.button.preview{/lang}</button>
+
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.global.preview': '{lang}wcf.global.preview{/lang}'
+ });
+
+ new WCF.Message.DefaultPreview({if MODULE_ATTACHMENT && $attachmentHandler !== null}'{@$attachmentObjectType}', '{@$attachmentObjectID}', '{$tmpHash|encodeJS}'{/if});
+ });
+ //]]>
+</script>
\ No newline at end of file
--- /dev/null
+<fieldset id="settings" class="settingsContent tabMenuContent container containerPadding">
+ <dl class="wide">
+ {if $__wcf->getSession()->getPermission('user.message.canUseBBCodes')}
+ <dd>
+ <label><input id="preParse" name="preParse" type="checkbox" value="1"{if $preParse} checked="checked"{/if} /> {lang}wcf.message.settings.preParse{/lang}</label>
+ <small>{lang}wcf.message.settings.preParse.description{/lang}</small>
+ </dd>
+ {/if}
+ {if $__wcf->getSession()->getPermission('user.message.canUseSmilies')}
+ <dd>
+ <label><input id="enableSmilies" name="enableSmilies" type="checkbox" value="1"{if $enableSmilies} checked="checked"{/if} /> {lang}wcf.message.settings.enableSmilies{/lang}</label>
+ <small>{lang}wcf.message.settings.enableSmilies.description{/lang}</small>
+ </dd>
+ {/if}
+ {if $__wcf->getSession()->getPermission('user.message.canUseBBCodes')}
+ <dd>
+ <label><input id="enableBBCodes" name="enableBBCodes" type="checkbox" value="1"{if $enableBBCodes} checked="checked"{/if} /> {lang}wcf.message.settings.enableBBCodes{/lang}</label>
+ <small>{lang}wcf.message.settings.enableBBCodes.description{/lang}</small>
+ </dd>
+ {/if}
+ {if $__wcf->getSession()->getPermission('user.message.canUseHtml')}
+ <dd>
+ <label><input id="enableHtml" name="enableHtml" type="checkbox" value="1"{if $enableHtml} checked="checked"{/if} /> {lang}wcf.message.settings.enableHtml{/lang}</label>
+ <small>{lang}wcf.message.settings.enableHtml.description{/lang}</small>
+ </dd>
+ {/if}
+ {if 'MODULE_USER_SIGNATURE'|defined && MODULE_USER_SIGNATURE && $showSignatureSetting && $__wcf->user->userID}
+ <dd>
+ <label><input id="showSignature" name="showSignature" type="checkbox" value="1"{if $showSignature} checked="checked"{/if} /> {lang}wcf.message.settings.showSignature{/lang}</label>
+ <small>{lang}wcf.message.settings.showSignature.description{/lang}</small>
+ </dd>
+ {/if}
+
+ {event name='settings'}
+ </dl>
+</fieldset>
--- /dev/null
+{assign var=__tabCount value=0}
+{capture assign=__categoryTabs}
+ {foreach from=$smileyCategories item=smileyCategory}
+ {assign var=__tabCount value=$__tabCount + 1}
+ {assign var='__smileyAnchor' value='smilies-'|concat:$smileyCategory->categoryID}
+ <li><a href="{$__wcf->getAnchor($__smileyAnchor)}" data-smiley-category-id="{@$smileyCategory->categoryID}">{$smileyCategory->title|language}</a></li>
+ {/foreach}
+{/capture}
+
+<div id="smilies" class="jsOnly smiliesContent tabMenuContent container containerPadding{if $__tabCount} tabMenuContainer{/if}">
+ {capture assign=__defaultSmilies}
+ {include file='__messageFormSmilies' smilies=$defaultSmilies}
+ {/capture}
+
+ {if $__tabCount > 1}
+ <nav class="menu">
+ <ul>
+ {@$__categoryTabs}
+ </ul>
+ </nav>
+
+ {foreach from=$smileyCategories item=smileyCategory}
+ {if !$smileyCategory->isDisabled}
+ <div id="smilies-{@$smileyCategory->categoryID}" class="hidden">
+ {if !$smileyCategory->categoryID}{@$__defaultSmilies}{/if}
+ </div>
+ {/if}
+ {/foreach}
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ new WCF.Message.SmileyCategories();
+ });
+ //]]>
+ </script>
+ {else}
+ {@$__defaultSmilies}
+ {/if}
+
+ {event name='fields'}
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ new WCF.Message.Smilies('{if $wysiwygSelector|isset}{$wysiwygSelector|encodeJS}{else}text{/if}');
+ });
+ //]]>
+ </script>
+</div>
--- /dev/null
+<div class="tabMenuContainer" data-active="{$activeTabMenuItem}" data-store="activeTabMenuItem">
+ <nav class="tabMenu jsOnly">
+ <ul>
+ {if MODULE_SMILEY && $smileyCategories|count}<li id="smiliesTab"><a href="{@$__wcf->getAnchor('smilies')}" title="{lang}wcf.message.smilies{/lang}">{lang}wcf.message.smilies{/lang}</a></li>{/if}
+ {if MODULE_ATTACHMENT && $attachmentHandler !== null && $attachmentHandler->canUpload()}<li id="attachmentsTab"><a href="{@$__wcf->getAnchor('attachments')}" title="{lang}wcf.attachment.attachments{/lang}">{lang}wcf.attachment.attachments{/lang}</a></li>{/if}
+ <li><a href="{@$__wcf->getAnchor('settings')}" title="{lang}wcf.message.settings{/lang}">{lang}wcf.message.settings{/lang}</a></li>
+ {event name='tabMenuTabs'}
+ </ul>
+ </nav>
+
+ {if MODULE_SMILEY && $smileyCategories|count}{include file='messageFormSmilies'}{/if}
+ {if MODULE_ATTACHMENT && $attachmentHandler !== null && $attachmentHandler->canUpload()}{include file='messageFormAttachments'}{/if}
+
+ {include file='messageFormSettings'}
+
+ {event name='tabMenuContents'}
+</div>
+
+<script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ if (jQuery.browser.mobile) $('#smiliesTab, #smilies').remove();
+ WCF.TabMenu.init();
+ });
+ //]]>
+</script>
\ No newline at end of file
--- /dev/null
+{if !$supportPaste|isset}{assign var=supportPaste value=false}{/if}
+{foreach from=$messages item=message}
+ <article class="message messageReduced marginTop jsInvalidQuoteTarget" data-link="{@$message->getLink()}" data-username="{$message->getUsername()}">
+ <div>
+ <section class="messageContent">
+ <div>
+ <header class="messageHeader">
+ <div class="box32">
+ {if $userProfiles[$message->getUserID()]|isset}
+ <a href="{link controller='User' object=$userProfiles[$message->getUserID()]}{/link}" class="framed">{@$userProfiles[$message->getUserID()]->getAvatar()->getImageTag(32)}</a>
+ {/if}
+
+ <div class="messageHeadline">
+ <h1><a href="{@$message->getLink()}">{$message->getTitle()}</a></h1>
+ <p>
+ <span class="username">{if $userProfiles[$message->getUserID()]|isset}<a href="{link controller='User' object=$userProfiles[$message->getUserID()]}{/link}">{$message->getUsername()}</a>{else}{$message->getUsername()}{/if}</span>
+ {@$message->getTime()|time}
+ </p>
+ </div>
+ </div>
+ </header>
+
+ <div class="messageBody">
+ <div>
+ <div class="messageText">
+ <ul>
+ {foreach from=$message key=quoteID item=quote}
+ <li data-quote-id="{@$quoteID}">
+ <span>
+ <input type="checkbox" value="1" id="quote_{@$quoteID}" class="jsCheckbox" />
+ {if $supportPaste}<span class="icon icon16 icon-plus jsTooltip jsInsertQuote" title="{lang}wcf.message.quote.insertQuote{/lang}"></span>{/if}
+ </span>
+
+ <div class="jsQuote">
+ <label for="quote_{@$quoteID}">{@$quote}</label>
+ </div>
+ <div class="jsFullQuote">
+ {$message->getFullQuote($quoteID)}
+ </div>
+ </li>
+ {/foreach}
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </section>
+ </div>
+ </article>
+{/foreach}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<rss version="2.0"
+ xmlns:atom="http://www.w3.org/2005/Atom"
+ xmlns:content="http://purl.org/rss/1.0/modules/content/"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
+>
+ <channel>
+ <title><![CDATA[{if $title}{$title} - {/if}{@PAGE_TITLE|language|escapeCDATA}]]></title>
+ <link><![CDATA[{@$baseHref|escapeCDATA}]]></link>
+ <description><![CDATA[{@PAGE_DESCRIPTION|escapeCDATA}]]></description>
+ <language>{@$__wcf->language->getFixedLanguageCode()}</language>
+ <pubDate>{'r'|gmdate:TIME_NOW}</pubDate>
+{assign var='dummy' value=$items->rewind()}
+ <lastBuildDate>{if $items->valid()}{'r'|gmdate:$items->current()->getTime()}{else}{'r'|gmdate:TIME_NOW}{/if}</lastBuildDate>
+ <ttl>60</ttl>
+ <generator><![CDATA[WoltLab Community Framework {@WCF_VERSION}]]></generator>
+ <atom:link href="{$__wcf->getRequestURI()}" rel="self" type="application/rss+xml" />
+{* *}{foreach from=$items item='item'}
+ <item>
+ <title><![CDATA[{@$item->getTitle()|escapeCDATA}]]></title>
+ <link><![CDATA[{@$item->getLink()|escapeCDATA}]]></link>
+ {hascontent}<description><![CDATA[{content}{@$item->getExcerpt()|escapeCDATA}{/content}]]></description>{/hascontent}
+ <pubDate>{'r'|gmdate:$item->getTime()}</pubDate>
+ <dc:creator>{@$item->getUsername()|escapeCDATA}</dc:creator>
+ <guid><![CDATA[{@$item->getLink()|escapeCDATA}]]></guid>
+ {foreach from=$item->getCategories() item='category'}
+ <category><![CDATA[{@$category|escapeCDATA}]]></category>
+ {/foreach}
+ {hascontent}<content:encoded><![CDATA[{content}{@$item->getFormattedMessage()|escapeCDATA}{/content}]]></content:encoded>{/hascontent}
+ <slash:comments>{@$item->getComments()|escapeCDATA}</slash:comments>
+ </item>
+{* *}{/foreach}
+ </channel>
+{if ENABLE_BENCHMARK}
+ <!--
+ Execution time: {@$__wcf->getBenchmark()->getExecutionTime()}s ({#($__wcf->getBenchmark()->getExecutionTime()-$__wcf->getBenchmark()->getQueryExecutionTime())/$__wcf->getBenchmark()->getExecutionTime()*100}% PHP, {#$__wcf->getBenchmark()->getQueryExecutionTime()/$__wcf->getBenchmark()->getExecutionTime()*100}% SQL) | SQL queries: {#$__wcf->getBenchmark()->getQueryCount()} | Memory-Usage: {$__wcf->getBenchmark()->getMemoryUsage()}
+
+{* *}{if ENABLE_DEBUG_MODE}
+{* *}{foreach from=$__wcf->getBenchmark()->getItems() item=item}
+{* *} {if $item.type == 1}(SQL Query) {/if}{$item.text} ({@$item.use}s)
+{* *}{/foreach}
+{* *}{/if}
+ -->
+{/if}
+</rss>
\ No newline at end of file
--- /dev/null
+<div class="messageShareButtons jsOnly">
+ <ul>
+ <li class="jsShareFacebook">
+ <a>
+ <span class="icon icon32 icon-facebook-sign jsTooltip" title="{lang}wcf.message.share.facebook{/lang}"></span>
+ <span class="invisible">{lang}wcf.message.share.facebook{/lang}</span>
+ </a>
+ <span class="badge" style="display: none">0</span>
+ </li>
+ <li class="jsShareTwitter">
+ <a>
+ <span class="icon icon32 icon-twitter-sign jsTooltip" title="{lang}wcf.message.share.twitter{/lang}"></span>
+ <span class="invisible">{lang}wcf.message.share.twitter{/lang}</span>
+ </a>
+ <span class="badge" style="display: none">0</span>
+ </li>
+ <li class="jsShareGoogle">
+ <a>
+ <span class="icon icon32 icon-google-plus-sign jsTooltip" title="{lang}wcf.message.share.google{/lang}"></span>
+ <span class="invisible">{lang}wcf.message.share.google{/lang}</span>
+ </a>
+ <span class="badge" style="display: none">0</span>
+ </li>
+ <li class="jsShareReddit">
+ <a>
+ <img class="jsTooltip" src="{$__wcf->getPath()}icon/reddit.png" alt="{lang}wcf.message.share.reddit{/lang}" title="{lang}wcf.message.share.reddit{/lang}" />
+ <span class="invisible">{lang}wcf.message.share.reddit{/lang}</span>
+ </a>
+ <span class="badge" style="display: none">0</span>
+ </li>
+
+ {event name='buttons'}
+ </ul>
+
+ <script type="text/javascript">
+ //<![CDATA[
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.message.share.facebook': '{lang}wcf.message.share.facebook{/lang}',
+ 'wcf.message.share.google': '{lang}wcf.message.share.google{/lang}',
+ 'wcf.message.share.reddit': '{lang}wcf.message.share.reddit{/lang}',
+ 'wcf.message.share.twitter': '{lang}wcf.message.share.twitter{/lang}'
+ });
+
+ new WCF.Message.Share.Page({if SHARE_BUTTONS_SHOW_COUNT}true{else}false{/if});
+ });
+ //]]>
+ </script>
+</div>
--- /dev/null
+<script type="text/javascript">
+//<![CDATA[
+ var CKEDITOR_BASEPATH = '{@$__wcf->getPath()}js/3rdParty/ckeditor/';
+ var __CKEDITOR_BUTTONS = [ {implode from=$__wcf->getBBCodeHandler()->getButtonBBCodes() item=__bbcode}{ icon: '{$__bbcode->wysiwygIcon}', label: '{$__bbcode->buttonLabel|language}', name: '{$__bbcode->bbcodeTag}' }{/implode} ];
+//]]>
+</script>
+<script type="text/javascript" src="{@$__wcf->getPath()}js/3rdParty/ckeditor/ckeditor.js"></script>
+<script type="text/javascript" src="{@$__wcf->getPath()}js/3rdParty/ckeditor/adapters/jquery.js"></script>
+{event name='javascriptIncludes'}
+
+<script type="text/javascript">
+//<![CDATA[
+$(function() {
+ if ($.browser.mobile) {
+ return;
+ }
+
+ var __CKEDITOR_TOOLBAR = [
+ ['Source', '-', 'Undo', 'Redo'],
+ ['Bold', 'Italic', 'Underline', '-', 'Strike', 'Subscript','Superscript'],
+ ['NumberedList', 'BulletedList', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
+ '/',
+ ['Font', 'FontSize', 'TextColor'],
+ ['Link', 'Unlink', 'Image', 'Table', 'Smiley'],
+ ['Maximize']
+ ];
+ if (__CKEDITOR_BUTTONS.length) {
+ var $buttons = [ ];
+
+ for (var $i = 0, $length = __CKEDITOR_BUTTONS.length; $i < $length; $i++) {
+ $buttons.push('__wcf_' + __CKEDITOR_BUTTONS[$i].name);
+ }
+
+ __CKEDITOR_TOOLBAR.push($buttons);
+ }
+
+ var $config = {
+ smiley_path: '{@$__wcf->getPath()|encodeJS}',
+ extraPlugins: 'wbbcode,wbutton',
+ removePlugins: 'contextmenu,tabletools,liststyle,elementspath,menubutton,forms,scayt',
+ language: '{@$__wcf->language->getFixedLanguageCode()}',
+ fontSize_sizes: '8/8pt;10/10pt;12/12pt;14/14pt;18/18pt;24/24pt;36/36pt;',
+ disableObjectResizing: true,
+ disableNativeSpellChecker: false,
+ toolbarCanCollapse: false,
+ enterMode: CKEDITOR.ENTER_BR,
+ minHeight: 200,
+ toolbar: __CKEDITOR_TOOLBAR,
+ smiley_images: [
+ {implode from=$defaultSmilies item=smiley}'{@$smiley->smileyPath|encodeJS}'{/implode}
+ ],
+ smiley_descriptions: [
+ {implode from=$defaultSmilies item=smiley}'{@$smiley->smileyCode|encodeJS}'{/implode}
+ ]
+ };
+
+ {event name='javascriptInit'}
+
+ var $editor = CKEDITOR.instances['{if $wysiwygSelector|isset}{$wysiwygSelector|encodeJS}{else}text{/if}'];
+ if ($editor) $editor.destroy(true);
+
+ $('{if $wysiwygSelector|isset}#{$wysiwygSelector|encodeJS}{else}#text{/if}').ckeditor($config);
+});
+//]]>
+</script>
<defaultvalue>0</defaultvalue>
<admindefaultvalue>1</admindefaultvalue>
</option>
+
+ <!-- user.message -->
+ <option name="user.message.canUseSmilies">
+ <categoryname>user.message</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="user.message.canUseHtml">
+ <categoryname>user.message</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>0</defaultvalue>
+ <admindefaultvalue>1</admindefaultvalue>
+ </option>
+ <option name="user.message.canUseBBCodes">
+ <categoryname>user.message</categoryname>
+ <optiontype>boolean</optiontype>
+ <defaultvalue>1</defaultvalue>
+ </option>
+ <option name="user.message.allowedBBCodes">
+ <categoryname>user.message</categoryname>
+ <optiontype>BBCodeSelect</optiontype>
+ <defaultvalue>all</defaultvalue>
+ </option>
+ <!-- /user.message -->
</options>
</import>
</data>
--- /dev/null
+/*
+ * BBCode Plugin v1.0 for CKEditor - http://www.site-top.com/
+ * Copyright (c) 2010, PitBult.
+ * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ */
+
+(function() {
+ var $pasted = false;
+ var $insertedText = null;
+
+ CKEDITOR.on('instanceReady', function(event) {
+ /**
+ * Fixes issues with pasted html.
+ */
+ event.editor.on('paste', function(ev) {
+ if (ev.data.type == 'html') {
+ var $value = ev.data.dataValue;
+
+ // Convert <br> to line breaks.
+ $value = $value.replace(/<br><\/p>/gi,"\n\n");
+ $value = $value.replace(/<br>/gi, "\n");
+ $value = $value.replace(/<\/p>/gi,"\n\n");
+ $value = $value.replace(/ /gi," ");
+
+ // remove html tags
+ $value = $value.replace(/<[^>]+>/g, '');
+
+ // fix multiple new lines
+ $value = $value.replace(/\n{3,}/gi,"\n\n");
+
+ ev.data.dataValue = $value;
+
+ $pasted = true;
+ }
+ }, null, null, 9);
+
+ event.editor.on('insertText', function(ev) {
+ $insertedText = ev.data;
+ }, null, null, 1);
+ event.editor.on('mode', function(ev) {
+ ev.editor.focus();
+
+ insertFakeSubmitButton(ev);
+ });
+ event.editor.on('afterSetData', function(ev) {
+ insertFakeSubmitButton(ev);
+ });
+
+ event.editor.on('key', function(ev) {
+ if (ev.data.keyCode == CKEDITOR.ALT + 83) { // [Alt] + [S]
+ WCF.Message.Submit.execute(ev.editor.name);
+ }
+ });
+
+ insertFakeSubmitButton(event);
+ });
+
+ /**
+ * Inserts a fake submit button, Chrome only.
+ *
+ * @param object event
+ */
+ function insertFakeSubmitButton(event) {
+ if (event.editor.mode === 'source' || !WCF.Browser.isChrome()) {
+ return;
+ }
+
+ // place button outside of <body> to prevent it being removed once deleting content
+ $('<button accesskey="s" />').hide().appendTo($(event.editor.document.$).find('html'));
+
+ }
+
+ /**
+ * Removes obsolete dialog elements.
+ */
+ CKEDITOR.on('dialogDefinition', function(event) {
+ var $tab;
+ var $name = event.data.name;
+ var $definition = event.data.definition;
+
+ if ($name == 'link') {
+ $definition.removeContents('target');
+ $definition.removeContents('upload');
+ $definition.removeContents('advanced');
+ $tab = $definition.getContents('info');
+ $tab.remove('emailSubject');
+ $tab.remove('emailBody');
+ }
+ else if ($name == 'image') {
+ $definition.removeContents('advanced');
+ $tab = $definition.getContents('Link');
+ $tab.remove('cmbTarget');
+ $tab = $definition.getContents('info');
+ $tab.remove('txtAlt');
+ $tab.remove('basic');
+ }
+ else if ($name == 'table') {
+ $definition.removeContents('advanced');
+ $definition.width = 210;
+ $definition.height = 1;
+
+ $tab = $definition.getContents('info');
+
+ $tab.remove('selHeaders');
+ $tab.remove('cmbAlign');
+ $tab.remove('txtHeight');
+ $tab.remove('txtCaption');
+ $tab.remove('txtSummary');
+
+ // don't remove these fields as we need their default values
+ $tab.get('txtBorder').style = 'display: none';
+ $tab.get('txtWidth').style = 'display: none';
+ $tab.get('txtCellSpace').style = 'display: none';
+ $tab.get('txtCellPad').style = 'display: none';
+ }
+ else if ($name == 'smiley') {
+ $definition.contents[0].elements[0].onClick = function(ev) {
+ var $target = ev.data.getTarget();
+ var $targetName = $target.getName();
+
+ if ($targetName == 'a') {
+ $target = $target.getChild( 0 );
+ }
+ else if ($targetName != 'img') {
+ return;
+ }
+
+ var $src = $target.getAttribute('cke_src');
+ var $title = $target.getAttribute('title');
+
+ event.editor.insertText(' ' + $title + ' ');
+
+ $definition.dialog.hide();
+ ev.data.preventDefault();
+ };
+ }
+ });
+
+ /**
+ * Enables this plugin.
+ */
+ CKEDITOR.plugins.add('wbbcode', {
+ requires: ['htmlwriter'],
+ init: function(editor) {
+ editor.dataProcessor = new CKEDITOR.htmlDataProcessor(editor);
+ editor.dataProcessor.toHtml = toHtml;
+ editor.dataProcessor.toDataFormat = toDataFormat;
+ }
+ });
+
+ /**
+ * Removes the unicode zero width space (0x200B).
+ *
+ * @param string string
+ * @return string
+ */
+ var removeCrap = function(string) {
+ var $string = '';
+
+ for (var $i = 0, $length = string.length; $i < $length; $i++) {
+ var $byte = string.charCodeAt($i).toString(16);
+ if ($byte != '200b') {
+ $string += string[$i];
+ }
+ }
+
+ return $string;
+ }
+
+ /**
+ * Converts bbcodes to html.
+ */
+ var toHtml = function(data, fixForBody) {
+ // remove 0x200B (unicode zero width space)
+ data = removeCrap(data);
+
+ if ($insertedText !== null) {
+ data = $insertedText;
+ $insertedText = null;
+
+ if (data == ' ') return ' ';
+ }
+
+ if (!$pasted) {
+ // Convert & to its HTML entity.
+ data = data.replace(/&/g, '&');
+
+ // Convert < and > to their HTML entities.
+ data = data.replace(/</g, '<');
+ data = data.replace(/>/g, '>');
+ }
+
+ // Convert line breaks to <br>.
+ data = data.replace(/(?:\r\n|\n|\r)/g, '<br>');
+
+ if ($pasted) {
+ $pasted = false;
+ // skip
+ return data;
+ }
+
+ // cache code tags
+ var $cachedCodes = { };
+ data = data.replace(/\[code(.+?)\[\/code]/gi, function(match) {
+ var $key = match.hashCode();
+ $cachedCodes[$key] = match;
+ return '@@' + $key + '@@';
+ });
+
+ // [url]
+ data = data.replace(/\[url\]([^"]+?)\[\/url]/gi, '<a href="$1">$1</a>');
+ data = data.replace(/\[url\='?([^'"\]]+)'?](.+?)\[\/url]/gi, '<a href="$1">$2</a>');
+
+ // [email]
+ data = data.replace(/\[email\]([^"]+?)\[\/email]/gi, '<a href="mailto:$1">$1</a>');
+ data = data.replace(/\[email\=([^"\]]+)](.+?)\[\/email]/gi, '<a href="mailto:$1">$2</a>');
+
+ // [b]
+ data = data.replace(/\[b\](.*?)\[\/b]/gi, '<b>$1</b>');
+
+ // [i]
+ data = data.replace(/\[i\](.*?)\[\/i]/gi, '<i>$1</i>');
+
+ // [u]
+ data = data.replace(/\[u\](.*?)\[\/u]/gi, '<u>$1</u>');
+
+ // [s]
+ data = data.replace(/\[s\](.*?)\[\/s]/gi, '<strike>$1</strike>');
+
+ // [sub]
+ data = data.replace(/\[sub\](.*?)\[\/sub]/gi, '<sub>$1</sub>');
+
+ // [sup]
+ data = data.replace(/\[sup\](.*?)\[\/sup]/gi, '<sup>$1</sup>');
+
+ // [img]
+ data = data.replace(/\[img\]([^"]+?)\[\/img\]/gi,'<img src="$1" />');
+ data = data.replace(/\[img='?([^"]*?)'?,(left|right)\]\[\/img\]/gi,'<img src="$1" style="float: $2" />');
+ data = data.replace(/\[img='?([^"]*?)'?\]\[\/img\]/gi,'<img src="$1" />');
+
+ // [quote]
+ // data = data.replace(/\[quote\]/gi, '<blockquote>');
+ // data = data.replace(/\[\/quote\]/gi, '</blockquote>');
+
+ // [size]
+ data = data.replace(/\[size=(\d+)\](.*?)\[\/size\]/gi,'<span style="font-size: $1pt">$2</span>');
+
+ // [color]
+ data = data.replace(/\[color=([#a-z0-9]*?)\](.*?)\[\/color\]/gi,'<span style="color: $1">$2</span>');
+
+ // [font]
+ data = data.replace(/\[font='?([a-z,\- ]*?)'?\](.*?)\[\/font\]/gi,'<span style="font-family: $1">$2</span>');
+
+ // [align]
+ data = data.replace(/\[align=(left|right|center|justify)\](.*?)\[\/align\]/gi,'<div style="text-align: $1">$2</div>');
+
+ // [*]
+ data = data.replace(/\[\*\](.*?)(?=\[\*\]|\[\/list\])/gi,'<li>$1</li>');
+
+ // [list]
+ data = data.replace(/\[list\]/gi, '<ul>');
+ data = data.replace(/\[list=1\]/gi, '<ul style="list-style-type: decimal">');
+ data = data.replace(/\[\/list]/gi, '</ul>');
+
+ // [table]
+ data = data.replace(/\[table\]/gi, '<table border="1" cellspacing="1" cellpadding="1" style="width: 500px;">');
+ data = data.replace(/\[\/table\]/gi, '</table>');
+ // [tr]
+ data = data.replace(/\[tr\]/gi, '<tr>');
+ data = data.replace(/\[\/tr\]/gi, '</tr>');
+ // [td]
+ data = data.replace(/\[td\]/gi, '<td>');
+ data = data.replace(/\[\/td\]/gi, '</td>');
+
+ // smileys
+ for (var i = 0; i < this.editor.config.smiley_descriptions.length; i++) {
+ var smileyCode = this.editor.config.smiley_descriptions[i].replace(/</g, '<').replace(/>/g, '>');
+ var regExp = new RegExp('(\\s|>|^)'+WCF.String.escapeRegExp(smileyCode)+'(?=\\s|<|$)', 'gi');
+ data = data.replace(regExp, '$1<img src="' + this.editor.config.smiley_path + this.editor.config.smiley_images[i] + '" class="smiley" alt="'+smileyCode+'" />');
+ }
+
+ // remove "javascript:"
+ data = data.replace(/(javascript):/gi, '$1<span></span>:');
+
+ // insert codes
+ if ($.getLength($cachedCodes)) {
+ for (var $key in $cachedCodes) {
+ var $regex = new RegExp('@@' + $key + '@@', 'g');
+ data = data.replace($regex, $cachedCodes[$key]);
+ }
+ }
+
+ return data;
+ };
+
+ /**
+ * Converts html to bbcodes.
+ */
+ var toDataFormat = function(html, fixForBody) {
+ if (html == '<br>' || html == '<p><br></p>') {
+ return "";
+ }
+
+ // Convert <br> to line breaks.
+ html = html.replace(/<br><\/p>/gi,"\n");
+ html = html.replace(/<br(?=[ \/>]).*?>/gi, '\r\n');
+ html = html.replace(/<p>/gi,"");
+ html = html.replace(/<\/p>/gi,"\n");
+ html = html.replace(/ /gi," ");
+
+ // [email]
+ html = html.replace(/<a .*?href=(["'])mailto:(.+?)\1.*?>([\s\S]+?)<\/a>/gi, '[email=$2]$3[/email]');
+
+ // [url]
+ html = html.replace(/<a .*?href=(["'])(.+?)\1.*?>([\s\S]+?)<\/a>/gi, '[url=\'$2\']$3[/url]');
+
+ // [b]
+ html = html.replace(/<(?:b|strong)>/gi, '[b]');
+ html = html.replace(/<\/(?:b|strong)>/gi, '[/b]');
+
+ // [i]
+ html = html.replace(/<(?:i|em)>/gi, '[i]');
+ html = html.replace(/<\/(?:i|em)>/gi, '[/i]');
+
+ // [u]
+ html = html.replace(/<u>/gi, '[u]');
+ html = html.replace(/<\/u>/gi, '[/u]');
+
+ // [s]
+ html = html.replace(/<strike>/gi, '[s]');
+ html = html.replace(/<\/strike>/gi, '[/s]');
+
+ // [sub
+ html = html.replace(/<sub>/gi, '[sub]');
+ html = html.replace(/<\/sub>/gi, '[/sub]');
+
+ // [sup]
+ html = html.replace(/<sup>/gi, '[sup]');
+ html = html.replace(/<\/sup>/gi, '[/sup]');
+
+ // smileys
+ html = html.replace(/<img [^>]*?alt="([^"]+?)" class="smiley".*?>/gi, '$1'); // firefox
+ html = html.replace(/<img [^>]*?class="smiley" alt="([^"]+?)".*?>/gi, '$1'); // chrome, ie
+
+ // [img]
+ html = html.replace(/<img [^>]*?src=(["'])([^"']+?)\1 style="float: (left|right)".*?>/gi, "[img='$2',$3][/img]");
+ html = html.replace(/<img [^>]*?src=(["'])([^"']+?)\1.*?>/gi, '[img]$2[/img]');
+
+ // [quote]
+ // html = html.replace(/<blockquote>/gi, '[quote]');
+ // html = html.replace(/\n*<\/blockquote>/gi, '[/quote]');
+
+ // [color]
+ html = html.replace(/<span style="color: ?rgb\((\d{1,3}), ?(\d{1,3}), ?(\d{1,3})\);?">([\s\S]*?)<\/span>/gi, function(match, r, g, b, text) {
+ var $hex = ("0123456789ABCDEF".charAt((r - r % 16) / 16) + '' + "0123456789ABCDEF".charAt(r % 16)) + '' + ("0123456789ABCDEF".charAt((g - g % 16) / 16) + '' + "0123456789ABCDEF".charAt(g % 16)) + '' + ("0123456789ABCDEF".charAt((b - b % 16) / 16) + '' + "0123456789ABCDEF".charAt(b % 16));
+
+ return "[color=#" + $hex + "]" + text + "[/color]";
+ });
+ html = html.replace(/<span style="color: ?(.*?);?">([\s\S]*?)<\/span>/gi, "[color=$1]$2[/color]");
+
+ // [size]
+ html = html.replace(/<span style="font-size: ?(\d+)pt;?">([\s\S]*?)<\/span>/gi, "[size=$1]$2[/size]");
+
+ // [font]
+ html = html.replace(/<span style="font-family: ?(.*?);?">([\s\S]*?)<\/span>/gi, "[font='$1']$2[/font]");
+
+ // [align]
+ html = html.replace(/<div style="text-align: ?(left|center|right|justify);? ?">([\s\S]*?)<\/div>/gi, "[align=$1]$2[/align]");
+
+ // [*]
+ html = html.replace(/<li>/gi, '[*]');
+ html = html.replace(/<\/li>/gi, '');
+
+ // [list]
+ html = html.replace(/<ul>/gi, '[list]');
+ html = html.replace(/<(ol|ul style="list-style-type: decimal")>/gi, '[list=1]');
+ html = html.replace(/<\/(ul|ol)>/gi, '[/list]');
+
+ // [table]
+ html = html.replace(/<table[^>]*>/gi, '[table]');
+ html = html.replace(/<\/table>/gi, '[/table]');
+
+ // remove empty <tr>s
+ html = html.replace(/<tr><\/tr>/gi, '');
+ // [tr]
+ html = html.replace(/<tr>/gi, '[tr]');
+ html = html.replace(/<\/tr>/gi, '[/tr]');
+
+ // [td]
+ html = html.replace(/<td>/gi, '[td]');
+ html = html.replace(/<\/td>/gi, '[/td]');
+
+ // Remove remaining tags.
+ html = html.replace(/<[^>]+>/g, '');
+
+ // Restore <, > and &
+ html = html.replace(/</g, '<');
+ html = html.replace(/>/g, '>');
+ html = html.replace(/&/g, '&');
+
+ // Restore (and )
+ html = html.replace(/%28/g, '(');
+ html = html.replace(/%29/g, ')');
+
+ // Restore %20
+ html = html.replace(/%20/g, ' ');
+
+ return html;
+ }
+})();
--- /dev/null
+/**
+ * Provides custom buttons for CKEditor.
+ *
+ * In short we're applying a style element on the current selection which will be replaced
+ * with the plain BBCode tag (e.g. [tt]) afterwards. Using insertText() or insertHtml() does
+ * not work here as it discards the inline styles set for the selection.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ */
+(function() {
+ /**
+ * Transforms the BBCode span-element into a plain BBCode.
+ *
+ * @param CKEDITOR editor
+ */
+ function transformBBCode(editor) {
+ $(editor.document.$).find('span.wcfBBCode').replaceWith(function() {
+ var $bbcode = $(this).data('bbcode');
+ return '[' + $bbcode + ']' + $(this).html() + '[/' + $bbcode + ']';
+ });
+ }
+
+ // listens for 'afterCommandExec' to transform BBCodes into plain text
+ CKEDITOR.on('instanceReady', function(event) {
+ event.editor.on('afterCommandExec', function(ev) {
+ if (ev.data.name.indexOf('__wcf_') == 0) {
+ transformBBCode(ev.editor);
+ }
+ });
+ });
+
+ /**
+ * Enables this plugin.
+ */
+ CKEDITOR.plugins.add('wbutton', {
+ /**
+ * list of required plugins
+ * @var array<string>
+ */
+ requires: [ 'button' ],
+
+ /**
+ * Initializes the 'wbutton' plugin.
+ *
+ * @param CKEDITOR editor
+ */
+ init: function(editor) {
+ if (!__CKEDITOR_BUTTONS.length) {
+ return;
+ }
+
+ for (var $i = 0, $length = __CKEDITOR_BUTTONS.length; $i < $length; $i++) {
+ this._wcfAddButton(editor, __CKEDITOR_BUTTONS[$i]);
+ }
+ },
+
+ /**
+ * Adds command and button for given BBCode.
+ *
+ * @param CKEDITOR editor
+ * @param object button
+ */
+ _wcfAddButton: function(editor, button) {
+ var $style = new CKEDITOR.style({
+ element: 'span',
+ attributes: {
+ 'class': 'wcfBBCode',
+ 'data-bbcode': button.name
+ }
+ });
+ editor.addCommand('__wcf_' + button.name, new CKEDITOR.styleCommand($style));
+ editor.ui.addButton('__wcf_' + button.name, {
+ command: '__wcf_' + button.name,
+ icon: button.icon,
+ label: button.label
+ });
+ }
+ });
+})();
--- /dev/null
+/**
+ * Message related classes for WCF
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ */
+WCF.Message = { };
+
+/**
+ * Namespace for BBCode related classes.
+ */
+WCF.Message.BBCode = { };
+
+/**
+ * BBCode Viewer for WCF.
+ */
+WCF.Message.BBCode.CodeViewer = Class.extend({
+ /**
+ * dialog overlay
+ * @var jQuery
+ */
+ _dialog: null,
+
+ /**
+ * Initializes the WCF.Message.BBCode.CodeViewer class.
+ */
+ init: function() {
+ this._dialog = null;
+
+ this._initCodeBoxes();
+
+ WCF.DOMNodeInsertedHandler.addCallback('WCF.Message.BBCode.CodeViewer', $.proxy(this._initCodeBoxes, this));
+ WCF.DOMNodeInsertedHandler.forceExecution();
+ },
+
+ /**
+ * Initializes available code boxes.
+ */
+ _initCodeBoxes: function() {
+ $('.codeBox:not(.jsCodeViewer)').each($.proxy(function(index, codeBox) {
+ var $codeBox = $(codeBox).addClass('jsCodeViewer');
+
+ $('<span class="icon icon16 icon-copy pointer jsTooltip" title="' + WCF.Language.get('wcf.message.bbcode.code.copy') + '" />').appendTo($codeBox.find('div > h3')).click($.proxy(this._click, this));
+ }, this));
+ },
+
+ /**
+ * Shows a code viewer for a specific code box.
+ *
+ * @param object event
+ */
+ _click: function(event) {
+ var $content = '';
+ $(event.currentTarget).parents('div').next('ol').children('li').each(function(index, listItem) {
+ if ($content) {
+ $content += "\n";
+ }
+
+ // do *not* use $.trim here, as we want to preserve whitespaces whitespaces
+ $content += $(listItem).text().replace(/\n+$/, '');
+ });
+
+
+ if (this._dialog === null) {
+ this._dialog = $('<div><textarea cols="60" rows="12" readonly="readonly" /></div>').hide().appendTo(document.body);
+ this._dialog.children('textarea').val($content);
+ this._dialog.wcfDialog({
+ title: WCF.Language.get('wcf.message.bbcode.code.copy')
+ });
+ }
+ else {
+ this._dialog.children('textarea').val($content);
+ this._dialog.wcfDialog('open');
+ }
+
+ this._dialog.children('textarea').select();
+ }
+});
+
+/**
+ * Prevents multiple submits of the same form by disabling the submit button.
+ */
+WCF.Message.FormGuard = Class.extend({
+ /**
+ * Initializes the WCF.Message.FormGuard class.
+ */
+ init: function() {
+ var $forms = $('form.jsFormGuard').removeClass('jsFormGuard').submit(function() {
+ $(this).find('.formSubmit input[type=submit]').disable();
+ });
+
+ // restore buttons, prevents disabled buttons on back navigation in Opera
+ $(window).unload(function() {
+ $forms.find('.formSubmit input[type=submit]').enable();
+ });
+ }
+});
+
+/**
+ * Provides previews for ckEditor message fields.
+ *
+ * @param string className
+ * @param string messageFieldID
+ * @param string previewButtonID
+ */
+WCF.Message.Preview = Class.extend({
+ /**
+ * class name
+ * @var string
+ */
+ _className: '',
+
+ /**
+ * message field id
+ * @var string
+ */
+ _messageFieldID: '',
+
+ /**
+ * message field
+ * @var jQuery
+ */
+ _messageField: null,
+
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * preview button
+ * @var jQuery
+ */
+ _previewButton: null,
+
+ /**
+ * previous button label
+ * @var string
+ */
+ _previewButtonLabel: '',
+
+ /**
+ * Initializes a new WCF.Message.Preview object.
+ *
+ * @param string className
+ * @param string messageFieldID
+ * @param string previewButtonID
+ */
+ init: function(className, messageFieldID, previewButtonID) {
+ this._className = className;
+
+ // validate message field
+ this._messageFieldID = $.wcfEscapeID(messageFieldID);
+ this._messageField = $('#' + this._messageFieldID);
+ if (!this._messageField.length) {
+ console.debug("[WCF.Message.Preview] Unable to find message field identified by '" + this._messageFieldID + "'");
+ return;
+ }
+
+ // validate preview button
+ previewButtonID = $.wcfEscapeID(previewButtonID);
+ this._previewButton = $('#' + previewButtonID);
+ if (!this._previewButton.length) {
+ console.debug("[WCF.Message.Preview] Unable to find preview button identified by '" + previewButtonID + "'");
+ return;
+ }
+
+ this._previewButton.click($.proxy(this._click, this));
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+ },
+
+ /**
+ * Reads message field input and triggers an AJAX request.
+ */
+ _click: function(event) {
+ var $message = this._getMessage();
+ if ($message === null) {
+ console.debug("[WCF.Message.Preview] Unable to access ckEditor instance of '" + this._messageFieldID + "'");
+ return;
+ }
+
+ this._proxy.setOption('data', {
+ actionName: 'getMessagePreview',
+ className: this._className,
+ parameters: this._getParameters($message)
+ });
+ this._proxy.sendRequest();
+
+ // update button label
+ this._previewButtonLabel = this._previewButton.html();
+ this._previewButton.html(WCF.Language.get('wcf.global.loading')).disable();
+
+ // poke event
+ event.stopPropagation();
+ return false;
+ },
+
+ /**
+ * Returns request parameters.
+ *
+ * @param string message
+ * @return object
+ */
+ _getParameters: function(message) {
+ // collect message form options
+ var $options = { };
+ $('#settings').find('input[type=checkbox]').each(function(index, checkbox) {
+ var $checkbox = $(checkbox);
+ if ($checkbox.is(':checked')) {
+ $options[$checkbox.prop('name')] = $checkbox.prop('value');
+ }
+ });
+
+ // build parameters
+ return {
+ data: {
+ message: message
+ },
+ options: $options
+ };
+ },
+
+ /**
+ * Returns parsed message from ckEditor or null if editor was not accessible.
+ *
+ * @return string
+ */
+ _getMessage: function() {
+ if ($.browser.mobile) {
+ return this._messageField.val();
+ }
+ else if (this._messageField.data('ckeditorInstance')) {
+ var $ckEditor = this._messageField.ckeditorGet();
+ return $ckEditor.getData();
+ }
+
+ return null;
+ },
+
+ /**
+ * Handles successful AJAX requests.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ // restore preview button
+ this._previewButton.html(this._previewButtonLabel).enable();
+
+ // evaluate message
+ this._handleResponse(data);
+ },
+
+ /**
+ * Evaluates response data.
+ *
+ * @param object data
+ */
+ _handleResponse: function(data) { }
+});
+
+/**
+ * Default implementation for message previews.
+ *
+ * @see WCF.Message.Preview
+ */
+WCF.Message.DefaultPreview = WCF.Message.Preview.extend({
+ _attachmentObjectType: null,
+ _attachmentObjectID: null,
+ _tmpHash: null,
+
+ /**
+ * @see WCF.Message.Preview.init()
+ */
+ init: function(attachmentObjectType, attachmentObjectID, tmpHash) {
+ this._super('wcf\\data\\bbcode\\MessagePreviewAction', 'text', 'previewButton');
+
+ this._attachmentObjectType = attachmentObjectType || null;
+ this._attachmentObjectID = attachmentObjectID || null;
+ this._tmpHash = tmpHash || null;
+ },
+
+ /**
+ * @see WCF.Message.Preview._handleResponse()
+ */
+ _handleResponse: function(data) {
+ var $preview = $('#previewContainer');
+ if (!$preview.length) {
+ $preview = $('<div class="container containerPadding marginTop" id="previewContainer"><fieldset><legend>' + WCF.Language.get('wcf.global.preview') + '</legend><div></div></fieldset>').prependTo($('#messageContainer')).wcfFadeIn();
+ }
+
+ $preview.find('div:eq(0)').html(data.returnValues.message);
+ },
+
+ /**
+ * @see WCF.Message.Preview._getParameters()
+ */
+ _getParameters: function(message) {
+ var $parameters = this._super(message);
+
+ if (this._attachmentObjectType != null) {
+ $parameters.attachmentObjectType = this._attachmentObjectType;
+ $parameters.attachmentObjectID = this._attachmentObjectID;
+ $parameters.tmpHash = this._tmpHash;
+ }
+
+ return $parameters;
+ }
+});
+
+/**
+ * Handles multilingualism for messages.
+ *
+ * @param integer languageID
+ * @param object availableLanguages
+ * @param boolean forceSelection
+ */
+WCF.Message.Multilingualism = Class.extend({
+ /**
+ * list of available languages
+ * @var object
+ */
+ _availableLanguages: { },
+
+ /**
+ * language id
+ * @var integer
+ */
+ _languageID: 0,
+
+ /**
+ * language input element
+ * @var jQuery
+ */
+ _languageInput: null,
+
+ /**
+ * Initializes WCF.Message.Multilingualism
+ *
+ * @param integer languageID
+ * @param object availableLanguages
+ * @param boolean forceSelection
+ */
+ init: function(languageID, availableLanguages, forceSelection) {
+ this._availableLanguages = availableLanguages;
+ this._languageID = languageID || 0;
+
+ this._languageInput = $('#languageID');
+
+ // preselect current language id
+ this._updateLabel();
+
+ // register event listener
+ this._languageInput.find('.dropdownMenu > li').click($.proxy(this._click, this));
+
+ // add element to disable multilingualism
+ if (!forceSelection) {
+ var $dropdownMenu = this._languageInput.find('.dropdownMenu');
+ $('<li class="dropdownDivider" />').appendTo($dropdownMenu);
+ $('<li><span><span class="badge">' + this._availableLanguages[0] + '</span></span></li>').click($.proxy(this._disable, this)).appendTo($dropdownMenu);
+ }
+
+ // bind submit event
+ this._languageInput.parents('form').submit($.proxy(this._submit, this));
+ },
+
+ /**
+ * Handles language selections.
+ *
+ * @param object event
+ */
+ _click: function(event) {
+ this._languageID = $(event.currentTarget).data('languageID');
+ this._updateLabel();
+ },
+
+ /**
+ * Disables language selection.
+ */
+ _disable: function() {
+ this._languageID = 0;
+ this._updateLabel();
+ },
+
+ /**
+ * Updates selected language.
+ */
+ _updateLabel: function() {
+ this._languageInput.find('.dropdownToggle > span').text(this._availableLanguages[this._languageID]);
+ },
+
+ /**
+ * Sets language id upon submit.
+ */
+ _submit: function() {
+ this._languageInput.next('input[name=languageID]').prop('value', this._languageID);
+ }
+});
+
+/**
+ * Loads smiley categories upon user request.
+ */
+WCF.Message.SmileyCategories = Class.extend({
+ /**
+ * list of already loaded category ids
+ * @var array<integer>
+ */
+ _cache: [ ],
+
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * ckEditor element
+ * @var jQuery
+ */
+ _ckEditor: null,
+
+ /**
+ * Initializes the smiley loader.
+ *
+ * @param string ckEditorID
+ */
+ init: function() {
+ this._cache = [ ];
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ $('#smilies').on('wcftabsbeforeactivate', $.proxy(this._click, this));
+
+ // handle onload
+ var self = this;
+ new WCF.PeriodicalExecuter(function(pe) {
+ pe.stop();
+
+ self._click({ }, { newTab: $('#smilies > .menu li.ui-state-active') });
+ }, 100);
+ },
+
+ /**
+ * Handles tab menu clicks.
+ *
+ * @param object event
+ * @param object ui
+ */
+ _click: function(event, ui) {
+ var $categoryID = parseInt($(ui.newTab).children('a').data('smileyCategoryID'));
+
+ if ($categoryID && !WCF.inArray($categoryID, this._cache)) {
+ this._proxy.setOption('data', {
+ actionName: 'getSmilies',
+ className: 'wcf\\data\\smiley\\category\\SmileyCategoryAction',
+ objectIDs: [ $categoryID ]
+ });
+ this._proxy.sendRequest();
+ }
+ },
+
+ /**
+ * Handles successful AJAX requests.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ var $categoryID = parseInt(data.returnValues.smileyCategoryID);
+ this._cache.push($categoryID);
+
+ $('#smilies-' + $categoryID).html(data.returnValues.template);
+ }
+});
+
+/**
+ * Handles smiley clicks.
+ */
+WCF.Message.Smilies = Class.extend({
+ /**
+ * ckEditor element
+ * @var jQuery
+ */
+ _ckEditor: null,
+
+ /**
+ * Initializes the smiley handler.
+ *
+ * @param string ckEditorID
+ */
+ init: function(ckEditorID) {
+ // get ck editor
+ if (ckEditorID) {
+ this._ckEditor = $('#' + ckEditorID);
+
+ // add smiley click handler
+ $(document).on('click', '.jsSmiley', $.proxy(this._smileyClick, this));
+ }
+ },
+
+ /**
+ * Handles tab smiley clicks.
+ *
+ * @param object event
+ */
+ _smileyClick: function(event) {
+ var $target = $(event.currentTarget);
+ var $smileyCode = $target.data('smileyCode');
+
+ // get ckEditor
+ var $ckEditor = this._ckEditor.ckeditorGet();
+ // get smiley path
+ var $smileyPath = $target.find('img').attr('src');
+
+ // add smiley to config
+ if (!WCF.inArray($smileyCode, $ckEditor.config.smiley_descriptions)) {
+ $ckEditor.config.smiley_descriptions.push($smileyCode);
+ $ckEditor.config.smiley_images.push($smileyPath);
+ }
+
+ if ($ckEditor.mode === 'wysiwyg') {
+ // in design mode
+ var $img = $ckEditor.document.createElement('img', {
+ attributes: {
+ src: $smileyPath,
+ 'class': 'smiley',
+ alt: $smileyCode
+ }
+ });
+ $ckEditor.insertText(' ');
+ $ckEditor.insertElement($img);
+ $ckEditor.insertText(' ');
+ }
+ else {
+ // in source mode
+ var $textarea = this._ckEditor.next('.cke_editor_text').find('textarea');
+ var $value = $textarea.val();
+ if ($value.length == 0) {
+ $textarea.val($smileyCode);
+ $textarea.setCaret($smileyCode.length);
+ }
+ else {
+ var $position = $textarea.getCaret();
+ var $string = (($value.substr($position - 1, 1) !== ' ') ? ' ' : '') + $smileyCode + ' ';
+ $textarea.val( $value.substr(0, $position) + $string + $value.substr($position) );
+ $textarea.setCaret($position + $string.length);
+ }
+ }
+ }
+});
+
+/**
+ * Provides an AJAX-based quick reply for messages.
+ */
+WCF.Message.QuickReply = Class.extend({
+ /**
+ * quick reply container
+ * @var jQuery
+ */
+ _container: null,
+
+ /**
+ * message field
+ * @var jQuery
+ */
+ _messageField: null,
+
+ /**
+ * notification object
+ * @var WCF.System.Notification
+ */
+ _notification: null,
+
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * quote manager object
+ * @var WCF.Message.Quote.Manager
+ */
+ _quoteManager: null,
+
+ /**
+ * scroll handler
+ * @var WCF.Effect.Scroll
+ */
+ _scrollHandler: null,
+
+ /**
+ * success message for created but invisible messages
+ * @var string
+ */
+ _successMessageNonVisible: '',
+
+ /**
+ * Initializes a new WCF.Message.QuickReply object.
+ *
+ * @param boolean supportExtendedForm
+ * @param WCF.Message.Quote.Manager quoteManager
+ */
+ init: function(supportExtendedForm, quoteManager) {
+ this._container = $('#messageQuickReply');
+ this._messageField = $('#text');
+ if (!this._container || !this._messageField) {
+ return;
+ }
+
+ // button actions
+ var $formSubmit = this._container.find('.formSubmit');
+ $formSubmit.find('button[data-type=save]').click($.proxy(this._save, this));
+ if (supportExtendedForm) $formSubmit.find('button[data-type=extended]').click($.proxy(this._prepareExtended, this));
+ $formSubmit.find('button[data-type=cancel]').click($.proxy(this._cancel, this));
+
+ if (quoteManager) this._quoteManager = quoteManager;
+
+ $('.jsQuickReply').data('__api', this).click($.proxy(this.click, this));
+
+ this._proxy = new WCF.Action.Proxy({
+ failure: $.proxy(this._failure, this),
+ showLoadingOverlay: false,
+ success: $.proxy(this._success, this)
+ });
+ this._scroll = new WCF.Effect.Scroll();
+ this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.add'));
+ this._successMessageNonVisible = '';
+ },
+
+ /**
+ * Handles clicks on reply button.
+ *
+ * @param object event
+ */
+ click: function(event) {
+ this._container.toggle();
+
+ if (this._container.is(':visible')) {
+ this._scroll.scrollTo(this._container, true);
+
+ WCF.Message.Submit.registerButton('text', this._container.find('.formSubmit button[data-type=save]'));
+
+ if (this._quoteManager) {
+ // check if message field is empty
+ var $empty = true;
+ if ($.browser.touch) {
+ $empty = (!this._messageField.val().length);
+ }
+ else {
+ $empty = (!this._messageField.ckeditorGet().getData().length);
+ }
+
+ if ($empty) {
+ this._quoteManager.insertQuotes(this._getClassName(), this._getObjectID(), $.proxy(this._insertQuotes, this));
+ }
+ }
+
+ new WCF.PeriodicalExecuter($.proxy(function(pe) {
+ pe.stop();
+
+ if ($.browser.mobile) {
+ this._messageField.focus();
+ }
+ else {
+ this._messageField.ckeditorGet().ui.editor.focus();
+ }
+ }, this), 250);
+ }
+
+ // discard event
+ if (event !== null) {
+ event.stopPropagation();
+ return false;
+ }
+ },
+
+ /**
+ * Returns container element.
+ *
+ * @return jQuery
+ */
+ getContainer: function() {
+ return this._container;
+ },
+
+ /**
+ * Insertes quotes into the quick reply editor.
+ *
+ * @param object data
+ */
+ _insertQuotes: function(data) {
+ if (!data.returnValues.template) {
+ return;
+ }
+
+ if ($.browser.mobile) {
+ this._messageField.val(data.returnValues.template);
+ }
+ else {
+ this._messageField.ckeditorGet().insertText(data.returnValues.template);
+ }
+ },
+
+ /**
+ * Saves message.
+ */
+ _save: function() {
+ var $message = '';
+
+ if ($.browser.mobile) {
+ $message = $.trim(this._messageField.val());
+ }
+ else {
+ var $ckEditor = this._messageField.ckeditorGet();
+ $message = $.trim($ckEditor.getData());
+ }
+
+ // check if message is empty
+ var $innerError = this._messageField.parent().find('small.innerError');
+ if ($message === '') {
+ if (!$innerError.length) {
+ $innerError = $('<small class="innerError" />').appendTo(this._messageField.parent());
+ }
+
+ $innerError.html(WCF.Language.get('wcf.global.form.error.empty'));
+ return;
+ }
+ else {
+ $innerError.remove();
+ }
+
+ this._proxy.setOption('data', {
+ actionName: 'quickReply',
+ className: this._getClassName(),
+ interfaceName: 'wcf\\data\\IMessageQuickReplyAction',
+ parameters: this._getParameters($message)
+ });
+ this._proxy.sendRequest();
+
+ // show spinner and hide CKEditor
+ var $messageBody = this._container.find('.messageQuickReplyContent .messageBody');
+ $('<span class="icon icon48 icon-spinner" />').appendTo($messageBody);
+ $messageBody.children('#cke_text').hide().end().next().hide();
+ },
+
+ /**
+ * Returns the parameters for the save request.
+ *
+ * @param string message
+ * @return object
+ */
+ _getParameters: function(message) {
+ var $parameters = {
+ objectID: this._getObjectID(),
+ data: {
+ message: message
+ },
+ lastPostTime: this._container.data('lastPostTime'),
+ pageNo: this._container.data('pageNo'),
+ removeQuoteIDs: (this._quoteManager === null ? [ ] : this._quoteManager.getQuotesMarkedForRemoval())
+ };
+ if (this._container.data('anchor')) {
+ $parameters.anchor = this._container.data('anchor');
+ }
+
+ return $parameters;
+ },
+
+ /**
+ * Cancels quick reply.
+ */
+ _cancel: function() {
+ this._revertQuickReply(true);
+
+ if ($.browser.mobile) {
+ this._messageField.val('');
+ }
+ else {
+ // revert CKEditor
+ this._messageField.ckeditorGet().setData('');
+ }
+ },
+
+ /**
+ * Reverts quick reply to original state and optionally hiding it.
+ *
+ * @param boolean hide
+ */
+ _revertQuickReply: function(hide) {
+ var $messageBody = this._container.find('.messageQuickReplyContent .messageBody');
+
+ if (hide) {
+ this._container.hide();
+
+ // remove previous error messages
+ $messageBody.children('small.innerError').remove();
+ }
+
+ // display CKEditor
+ $messageBody.children('.icon-spinner').remove();
+ $messageBody.children('#cke_text').show();
+
+ // display form submit
+ $messageBody.next().show();
+ },
+
+ /**
+ * Prepares jump to extended message add form.
+ */
+ _prepareExtended: function() {
+ // mark quotes for removal
+ if (this._quoteManager !== null) {
+ this._quoteManager.markQuotesForRemoval();
+ }
+
+ var $message = '';
+
+ if ($.browser.mobile) {
+ $message = this._messageField.val();
+ }
+ else {
+ var $ckEditor = this._messageField.ckeditorGet();
+ $message = $ckEditor.getData();
+ }
+
+ new WCF.Action.Proxy({
+ autoSend: true,
+ data: {
+ actionName: 'jumpToExtended',
+ className: this._getClassName(),
+ interfaceName: 'wcf\\data\\IExtendedMessageQuickReplyAction',
+ parameters: {
+ containerID: this._getObjectID(),
+ message: $message
+ }
+ },
+ success: function(data, textStatus, jqXHR) {
+ window.location = data.returnValues.url;
+ }
+ });
+ },
+
+ /**
+ * Handles successful AJAX calls.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ // redirect to new page
+ if (data.returnValues.url) {
+ window.location = data.returnValues.url;
+ }
+ else {
+ if (data.returnValues.template) {
+ // insert HTML
+ var $message = $('' + data.returnValues.template);
+ $message.insertBefore(this._container);
+
+ // update last post time
+ this._container.data('lastPostTime', data.returnValues.lastPostTime);
+
+ // show notification
+ this._notification.show(undefined, undefined, WCF.Language.get('wcf.global.success.add'));
+
+ this._updateHistory($message.wcfIdentify());
+ }
+ else {
+ // show notification
+ var $message = (this._successMessageNonVisible) ? this._successMessageNonVisible : 'wcf.global.success.add';
+ this._notification.show(undefined, 5000, WCF.Language.get($message));
+ }
+
+ if ($.browser.mobile) {
+ this._messageField.val('');
+ }
+ else {
+ // remove CKEditor contents
+ this._messageField.ckeditorGet().setData('');
+ }
+
+ // hide quick reply and revert it
+ this._revertQuickReply(true);
+
+ // count stored quotes
+ if (this._quoteManager !== null) {
+ this._quoteManager.countQuotes();
+ }
+ }
+ },
+
+ /**
+ * Reverts quick reply on failure to preserve entered message.
+ */
+ _failure: function(data) {
+ this._revertQuickReply(false);
+
+ if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
+ return true;
+ }
+
+ var $messageBody = this._container.find('.messageQuickReplyContent .messageBody');
+ var $innerError = $messageBody.children('small.innerError').empty();
+ if (!$innerError.length) {
+ $innerError = $('<small class="innerError" />').appendTo($messageBody);
+ }
+
+ $innerError.html(data.returnValues.errorType);
+
+ return false;
+ },
+
+ /**
+ * Returns action class name.
+ *
+ * @return string
+ */
+ _getClassName: function() {
+ return '';
+ },
+
+ /**
+ * Returns object id.
+ *
+ * @return integer
+ */
+ _getObjectID: function() {
+ return 0;
+ },
+
+ /**
+ * Updates the history to avoid old content when going back in the browser
+ * history.
+ *
+ * @param hash
+ */
+ _updateHistory: function(hash) {
+ window.location.hash = hash;
+ }
+});
+
+/**
+ * Provides an inline message editor.
+ *
+ * @param integer containerID
+ */
+WCF.Message.InlineEditor = Class.extend({
+ /**
+ * currently active message
+ * @var string
+ */
+ _activeElementID: '',
+
+ /**
+ * message cache
+ * @var string
+ */
+ _cache: '',
+
+ /**
+ * list of messages
+ * @var object
+ */
+ _container: { },
+
+ /**
+ * container id
+ * @var integer
+ */
+ _containerID: 0,
+
+ /**
+ * list of dropdowns
+ * @var object
+ */
+ _dropdowns: { },
+
+ /**
+ * CSS selector for the message container
+ * @var string
+ */
+ _messageContainerSelector: '.jsMessage',
+
+ /**
+ * prefix of the message editor CSS id
+ * @var string
+ */
+ _messageEditorIDPrefix: 'messageEditor',
+
+ /**
+ * notification object
+ * @var WCF.System.Notification
+ */
+ _notification: null,
+
+ /**
+ * proxy object
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * support for extended editing form
+ * @var boolean
+ */
+ _supportExtendedForm: false,
+
+ /**
+ * Initializes a new WCF.Message.InlineEditor object.
+ *
+ * @param integer containerID
+ * @param boolean supportExtendedForm
+ */
+ init: function(containerID, supportExtendedForm) {
+ this._activeElementID = '';
+ this._cache = '';
+ this._container = { };
+ this._containerID = parseInt(containerID);
+ this._dropdowns = { };
+ this._supportExtendedForm = (supportExtendedForm) ? true : false;
+ this._proxy = new WCF.Action.Proxy({
+ failure: $.proxy(this._failure, this),
+ showLoadingOverlay: false,
+ success: $.proxy(this._success, this)
+ });
+ this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit'));
+
+ this.initContainers();
+
+ WCF.DOMNodeInsertedHandler.addCallback('WCF.Message.InlineEditor', $.proxy(this.initContainers, this));
+ },
+
+ /**
+ * Initializes editing capability for all messages.
+ */
+ initContainers: function() {
+ $(this._messageContainerSelector).each($.proxy(function(index, container) {
+ var $container = $(container);
+ var $containerID = $container.wcfIdentify();
+
+ if (!this._container[$containerID]) {
+ this._container[$containerID] = $container;
+
+ if ($container.data('canEditInline')) {
+ $container.find('.jsMessageEditButton:eq(0)').data('containerID', $containerID).click($.proxy(this._clickInline, this)).dblclick($.proxy(this._click, this));
+ }
+ else if ($container.data('canEdit')) {
+ $container.find('.jsMessageEditButton:eq(0)').data('containerID', $containerID).click($.proxy(this._click, this));
+ }
+ }
+ }, this));
+ },
+
+ /**
+ * Loads WYSIWYG editor for selected message.
+ *
+ * @param object event
+ * @param integer containerID
+ * @return boolean
+ */
+ _click: function(event, containerID) {
+ var $containerID = (event === null) ? containerID : $(event.currentTarget).data('containerID');
+
+ if (this._activeElementID === '') {
+ this._activeElementID = $containerID;
+ this._prepare();
+
+ this._proxy.setOption('data', {
+ actionName: 'beginEdit',
+ className: this._getClassName(),
+ interfaceName: 'wcf\\data\\IMessageInlineEditorAction',
+ parameters: {
+ containerID: this._containerID,
+ objectID: this._container[$containerID].data('objectID')
+ }
+ });
+ this._proxy.sendRequest();
+ }
+ else {
+ var $notification = new WCF.System.Notification(WCF.Language.get('wcf.message.error.editorAlreadyInUse'), 'warning');
+ $notification.show();
+ }
+
+ if (event !== null) {
+ event.stopPropagation();
+ return false;
+ }
+ },
+
+ /**
+ * Provides an inline dropdown menu instead of directly loading the WYSIWYG editor.
+ *
+ * @param object event
+ * @return boolean
+ */
+ _clickInline: function(event) {
+ var $button = $(event.currentTarget);
+
+ if (!$button.hasClass('dropdownToggle')) {
+ var $containerID = $button.data('containerID');
+
+ WCF.DOMNodeInsertedHandler.enable();
+
+ $button.addClass('dropdownToggle').parent().addClass('dropdown');
+
+ var $dropdownMenu = $('<ul class="dropdownMenu" />').insertAfter($button);
+ this._initDropdownMenu($containerID, $dropdownMenu);
+
+ WCF.DOMNodeInsertedHandler.disable();
+
+ this._dropdowns[this._container[$containerID].data('objectID')] = $dropdownMenu;
+
+ WCF.Dropdown.registerCallback($button.parent().wcfIdentify(), $.proxy(this._toggleDropdown, this));
+
+ // trigger click event
+ $button.trigger('click');
+ }
+
+ event.stopPropagation();
+ return false;
+ },
+
+ /**
+ * Handles errorneus editing requests.
+ *
+ * @param object data
+ */
+ _failure: function(data) {
+ this._revertEditor();
+
+ if (data === null || data.returnValues === undefined || data.returnValues.errorType === undefined) {
+ return true;
+ }
+
+ var $messageBody = this._container[this._activeElementID].find('.messageBody .messageInlineEditor');
+ var $innerError = $messageBody.children('small.innerError').empty();
+ if (!$innerError.length) {
+ $innerError = $('<small class="innerError" />').insertBefore($messageBody.children('.formSubmit'));
+ }
+
+ $innerError.html(data.returnValues.errorType);
+
+ return false;
+ },
+
+ /**
+ * Forces message options to stay visible if toggling dropdown menu.
+ *
+ * @param jQuery dropdown
+ * @param string action
+ */
+ _toggleDropdown: function(dropdown, action) {
+ dropdown.parents('.messageOptions').toggleClass('forceOpen');
+ },
+
+ /**
+ * Initializes the inline edit dropdown menu.
+ *
+ * @param integer containerID
+ * @param jQuery dropdownMenu
+ */
+ _initDropdownMenu: function(containerID, dropdownMenu) { },
+
+ /**
+ * Prepares message for WYSIWYG display.
+ */
+ _prepare: function() {
+ var $messageBody = this._container[this._activeElementID].find('.messageBody');
+ $('<span class="icon icon48 icon-spinner" />').appendTo($messageBody);
+
+ var $content = $messageBody.find('.messageText');
+ this._cache = $content.html();
+ $content.empty();
+ },
+
+ /**
+ * Cancels editing and reverts to original message.
+ */
+ _cancel: function() {
+ var $container = this._container[this._activeElementID];
+
+ // remove ckEditor
+ try {
+ var $ckEditor = $('#' + this._messageEditorIDPrefix + $container.data('objectID')).ckeditorGet();
+ $ckEditor.destroy();
+ }
+ catch (e) {
+ // CKEditor might be not initialized yet, ignore
+ }
+
+ // restore message
+ var $messageBody = $container.find('.messageBody');
+ $messageBody.children('.icon-spinner').remove();
+ $messageBody.find('.messageText').html(this._cache);
+
+ // revert message options
+ this._container[this._activeElementID].find('.messageOptions').removeClass('forceHidden');
+
+ this._activeElementID = '';
+ },
+
+ /**
+ * Handles successful AJAX calls.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ switch (data.returnValues.actionName) {
+ case 'beginEdit':
+ this._showEditor(data);
+ break;
+
+ case 'save':
+ this._showMessage(data);
+ break;
+ }
+ },
+
+ /**
+ * Shows WYSIWYG editor for active message.
+ *
+ * @param object data
+ */
+ _showEditor: function(data) {
+ var $messageBody = this._container[this._activeElementID].find('.messageBody');
+ $messageBody.children('.icon-spinner').remove();
+ var $content = $messageBody.find('.messageText');
+
+ // insert wysiwyg
+ $('' + data.returnValues.template).appendTo($content);
+
+ // bind buttons
+ var $formSubmit = $content.find('.formSubmit');
+ var $saveButton = $formSubmit.find('button[data-type=save]').click($.proxy(this._save, this));
+ if (this._supportExtendedForm) $formSubmit.find('button[data-type=extended]').click($.proxy(this._prepareExtended, this));
+ $formSubmit.find('button[data-type=cancel]').click($.proxy(this._cancel, this));
+
+ WCF.Message.Submit.registerButton(
+ this._messageEditorIDPrefix + this._container[this._activeElementID].data('objectID'),
+ $saveButton
+ );
+
+ // hide message options
+ this._container[this._activeElementID].find('.messageOptions').addClass('forceHidden');
+
+ new WCF.PeriodicalExecuter($.proxy(function(pe) {
+ pe.stop();
+
+ $('#' + this._messageEditorIDPrefix + this._container[this._activeElementID].data('objectID')).ckeditorGet().ui.editor.focus();
+ }, this), 250);
+ },
+
+ /**
+ * Reverts editor.
+ */
+ _revertEditor: function() {
+ var $messageBody = this._container[this._activeElementID].find('.messageBody');
+ $messageBody.children('span.icon-spinner').remove();
+ $messageBody.find('.messageText').children().show();
+ },
+
+ /**
+ * Saves editor contents.
+ */
+ _save: function() {
+ var $container = this._container[this._activeElementID];
+ var $objectID = $container.data('objectID');
+ var $message = '';
+
+ if ($.browser.mobile) {
+ $message = $('#' + this._messageEditorIDPrefix + $objectID).val();
+ }
+ else {
+ var $ckEditor = $('#' + this._messageEditorIDPrefix + $objectID).ckeditorGet();
+ $message = $ckEditor.getData();
+ }
+
+ this._proxy.setOption('data', {
+ actionName: 'save',
+ className: this._getClassName(),
+ interfaceName: 'wcf\\data\\IMessageInlineEditorAction',
+ parameters: {
+ containerID: this._containerID,
+ data: {
+ message: $message
+ },
+ objectID: $objectID
+ }
+ });
+ this._proxy.sendRequest();
+
+ this._hideEditor();
+ },
+
+ /**
+ * Prepares jumping to extended editing mode.
+ */
+ _prepareExtended: function() {
+ var $container = this._container[this._activeElementID];
+ var $objectID = $container.data('objectID');
+ var $message = '';
+
+ if ($.browser.mobile) {
+ $message = $('#' + this._messageEditorIDPrefix + $objectID).val();
+ }
+ else {
+ var $ckEditor = $('#' + this._messageEditorIDPrefix + $objectID).ckeditorGet();
+ $message = $ckEditor.getData();
+ }
+
+ new WCF.Action.Proxy({
+ autoSend: true,
+ data: {
+ actionName: 'jumpToExtended',
+ className: this._getClassName(),
+ parameters: {
+ containerID: this._containerID,
+ message: $message,
+ messageID: $objectID
+ }
+ },
+ success: function(data, textStatus, jqXHR) {
+ window.location = data.returnValues.url;
+ }
+ });
+ },
+
+ /**
+ * Hides WYSIWYG editor.
+ */
+ _hideEditor: function() {
+ var $messageBody = this._container[this._activeElementID].find('.messageBody');
+ $('<span class="icon icon48 icon-spinner" />').appendTo($messageBody);
+ $messageBody.find('.messageText').children().hide();
+ },
+
+ /**
+ * Shows rendered message.
+ *
+ * @param object data
+ */
+ _showMessage: function(data) {
+ var $container = this._container[this._activeElementID];
+ var $messageBody = $container.find('.messageBody');
+ $messageBody.children('.icon-spinner').remove();
+ var $content = $messageBody.find('.messageText');
+
+ // revert message options
+ this._container[this._activeElementID].find('.messageOptions').removeClass('forceHidden');
+
+ // remove editor
+ if (!$.browser.mobile) {
+ var $ckEditor = $('#' + this._messageEditorIDPrefix + $container.data('objectID')).ckeditorGet();
+ $ckEditor.destroy();
+ }
+
+ $content.empty();
+
+ // insert new message
+ $content.html(data.returnValues.message);
+
+ this._activeElementID = '';
+
+ this._updateHistory(this._getHash($container.data('objectID')));
+
+ this._notification.show();
+ },
+
+ /**
+ * Returns message action class name.
+ *
+ * @return string
+ */
+ _getClassName: function() {
+ return '';
+ },
+
+ /**
+ * Returns the hash added to the url after successfully editing a message.
+ *
+ * @return string
+ */
+ _getHash: function(objectID) {
+ return '#message' + objectID;
+ },
+
+ /**
+ * Updates the history to avoid old content when going back in the browser
+ * history.
+ *
+ * @param hash
+ */
+ _updateHistory: function(hash) {
+ window.location.hash = hash;
+ }
+});
+
+/**
+ * Handles submit buttons for forms with an embedded WYSIWYG editor.
+ */
+WCF.Message.Submit = {
+ /**
+ * list of registered buttons
+ * @var object
+ */
+ _buttons: { },
+
+ /**
+ * Registers submit button for specified wysiwyg container id.
+ *
+ * @param string wysiwygContainerID
+ * @param string selector
+ */
+ registerButton: function(wysiwygContainerID, selector) {
+ if (!WCF.Browser.isChrome()) {
+ return;
+ }
+
+ this._buttons[wysiwygContainerID] = $(selector);
+ },
+
+ /**
+ * Triggers 'click' event for registered buttons.
+ */
+ execute: function(wysiwygContainerID) {
+ if (!this._buttons[wysiwygContainerID]) {
+ return;
+ }
+
+ this._buttons[wysiwygContainerID].trigger('click');
+ }
+};
+
+/**
+ * Namespace for message quotes.
+ */
+WCF.Message.Quote = { };
+
+/**
+ * Handles message quotes.
+ *
+ * @param string className
+ * @param string objectType
+ * @param string containerSelector
+ * @param string messageBodySelector
+ */
+WCF.Message.Quote.Handler = Class.extend({
+ /**
+ * active container id
+ * @var string
+ */
+ _activeContainerID: '',
+
+ /**
+ * action class name
+ * @var string
+ */
+ _className: '',
+
+ /**
+ * list of message containers
+ * @var object
+ */
+ _containers: { },
+
+ /**
+ * container selector
+ * @var string
+ */
+ _containerSelector: '',
+
+ /**
+ * 'copy quote' overlay
+ * @var jQuery
+ */
+ _copyQuote: null,
+
+ /**
+ * marked message
+ * @var string
+ */
+ _message: '',
+
+ /**
+ * message body selector
+ * @var string
+ */
+ _messageBodySelector: '',
+
+ /**
+ * object id
+ * @var integer
+ */
+ _objectID: 0,
+
+ /**
+ * object type name
+ * @var string
+ */
+ _objectType: '',
+
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * quote manager
+ * @var WCF.Message.Quote.Manager
+ */
+ _quoteManager: null,
+
+ /**
+ * Initializes the quote handler for given object type.
+ *
+ * @param WCF.Message.Quote.Manager quoteManager
+ * @param string className
+ * @param string objectType
+ * @param string containerSelector
+ * @param string messageBodySelector
+ * @param string messageContentSelector
+ */
+ init: function(quoteManager, className, objectType, containerSelector, messageBodySelector, messageContentSelector) {
+ this._className = className;
+ if (this._className == '') {
+ console.debug("[WCF.Message.QuoteManager] Empty class name given, aborting.");
+ return;
+ }
+
+ this._objectType = objectType;
+ if (this._objectType == '') {
+ console.debug("[WCF.Message.QuoteManager] Empty object type name given, aborting.");
+ return;
+ }
+
+ this._containerSelector = containerSelector;
+ this._message = '';
+ this._messageBodySelector = messageBodySelector;
+ this._messageContentSelector = messageContentSelector;
+ this._objectID = 0;
+ this._proxy = new WCF.Action.Proxy({
+ success: $.proxy(this._success, this)
+ });
+
+ this._initContainers();
+ this._initCopyQuote();
+
+ $(document).mouseup($.proxy(this._mouseUp, this));
+
+ // register with quote manager
+ this._quoteManager = quoteManager;
+ this._quoteManager.register(this._objectType, this);
+
+ // register with DOMNodeInsertedHandler
+ WCF.DOMNodeInsertedHandler.addCallback('WCF.Message.Quote.Handler' + objectType.hashCode(), $.proxy(this._initContainers, this));
+ },
+
+ /**
+ * Initializes message containers.
+ */
+ _initContainers: function() {
+ var self = this;
+ $(this._containerSelector).each(function(index, container) {
+ var $container = $(container);
+ var $containerID = $container.wcfIdentify();
+
+ if (!self._containers[$containerID]) {
+ self._containers[$containerID] = $container;
+ if ($container.hasClass('jsInvalidQuoteTarget')) {
+ return true;
+ }
+
+ if (self._messageBodySelector !== null) {
+ $container = $container.find(self._messageBodySelector).data('containerID', $containerID);
+ }
+
+ $container.mousedown($.proxy(self._mouseDown, self));
+
+ // bind event to quote whole message
+ self._containers[$containerID].find('.jsQuoteMessage').click($.proxy(self._saveFullQuote, self));
+ }
+ });
+ },
+
+ /**
+ * Handles mouse down event.
+ *
+ * @param object event
+ */
+ _mouseDown: function(event) {
+ // hide copy quote
+ this._copyQuote.hide();
+
+ // store container ID
+ var $container = $(event.currentTarget);
+ if (this._messageBodySelector) {
+ $container = this._containers[$container.data('containerID')];
+ }
+ this._activeContainerID = $container.wcfIdentify();
+
+ // remove alt-tag from all images, fixes quoting in Firefox
+ if ($.browser.mozilla) {
+ $container.find('img').each(function() {
+ var $image = $(this);
+ $image.data('__alt', $image.attr('alt')).removeAttr('alt');
+ });
+ }
+ },
+
+ /**
+ * Returns the text of a node and its children.
+ *
+ * @param object node
+ * @return string
+ */
+ _getNodeText: function(node) {
+ var nodeText = '';
+
+ for (var i = 0; i < node.childNodes.length; i++) {
+ if (node.childNodes[i].nodeType == 3) {
+ // text node
+ nodeText += node.childNodes[i].nodeValue;
+ }
+ else {
+ var $tagName = node.childNodes[i].tagName.toLowerCase();
+ if ($tagName === 'li') {
+ nodeText += "\r\n";
+ }
+
+ nodeText += this._getNodeText(node.childNodes[i]);
+
+ if ($tagName === 'ul') {
+ nodeText += "\n";
+ }
+ }
+ }
+
+ return nodeText;
+ },
+
+ /**
+ * Handles the mouse up event.
+ *
+ * @param object event
+ */
+ _mouseUp: function(event) {
+ // ignore event
+ if (this._activeContainerID == '') {
+ this._copyQuote.hide();
+
+ return;
+ }
+
+ var $container = this._containers[this._activeContainerID];
+ var $selection = this._getSelectedText();
+ var $text = $.trim($selection);
+ if ($text == '') {
+ this._copyQuote.hide();
+
+ return;
+ }
+
+ // compare selection with message text of given container
+ var $messageText = null;
+ if (this._messageBodySelector) {
+ $messageText = this._getNodeText($container.find(this._messageContentSelector).get(0));
+ }
+ else {
+ $messageText = this._getNodeText($container.get(0));
+ }
+
+ // selected text is not part of $messageText or contains text from unrelated nodes
+ if (this._normalize($messageText).indexOf(this._normalize($text)) === -1) {
+ return;
+ }
+ this._copyQuote.show();
+
+ var $coordinates = this._getBoundingRectangle($selection);
+ var $dimensions = this._copyQuote.getDimensions('outer');
+ var $left = ($coordinates.right - $coordinates.left) / 2 - ($dimensions.width / 2) + $coordinates.left;
+
+ this._copyQuote.css({
+ top: $coordinates.top - $dimensions.height - 7 + 'px',
+ left: $left + 'px'
+ });
+ this._copyQuote.hide();
+
+ // reset containerID
+ this._activeContainerID = '';
+
+ // show element after a delay, to prevent display if text was unmarked again (clicking into marked text)
+ var self = this;
+ new WCF.PeriodicalExecuter(function(pe) {
+ pe.stop();
+
+ var $text = $.trim(self._getSelectedText());
+ if ($text != '') {
+ self._copyQuote.show();
+ self._message = $text;
+ self._objectID = $container.data('objectID');
+
+ // revert alt tags, fixes quoting in Firefox
+ if ($.browser.mozilla) {
+ $container.find('img').each(function() {
+ var $image = $(this);
+ $image.attr('alt', $image.data('__alt'));
+ });
+ }
+ }
+ }, 10);
+ },
+
+ /**
+ * Normalizes a text for comparison.
+ *
+ * @param string text
+ * @return string
+ */
+ _normalize: function(text) {
+ return text.replace(/\r?\n|\r/g, "\n").replace(/\s{2,}/g, ' ');
+ },
+
+ /**
+ * Returns the left or right offset of the current text selection.
+ *
+ * @param objct range
+ * @param boolean before
+ * @return object
+ */
+ _getOffset: function(range, before) {
+ range.collapse(before);
+
+ var $elementID = WCF.getRandomID();
+ var $element = document.createElement('span');
+ $element.innerHTML = '<span id="' + $elementID + '"></span>';
+ var $fragment = document.createDocumentFragment(), $node, $lastNode;
+ while ($node = $element.firstChild) {
+ $lastNode = $fragment.appendChild($node);
+ }
+ range.insertNode($fragment);
+
+ $element = $('#' + $elementID);
+ var $position = $element.offset();
+ $position.top = $position.top - $(window).scrollTop();
+ $element.remove();
+
+ return $position;
+ },
+
+ /**
+ * Returns the offsets of the selection's bounding rectangle.
+ *
+ * @return object
+ */
+ _getBoundingRectangle: function(selection) {
+ var $coordinates = null;
+
+ if (document.createRange && typeof document.createRange().getBoundingClientRect != "undefined") { // Opera, Firefox, Safari, Chrome
+ if (selection.rangeCount > 0) {
+ // the coordinates returned by getBoundingClientRect() is relative to the window, not the document!
+ //var $rect = selection.getRangeAt(0).getBoundingClientRect();
+ var $rects = selection.getRangeAt(0).getClientRects();
+ if (!$.browser.mozilla && $rects.length > 1) {
+ var $range = selection.getRangeAt(0);
+ var $position1 = this._getOffset($range, true);
+
+ var $range = selection.getRangeAt(0);
+ var $position2 = this._getOffset($range, false);
+
+ var $rect = {
+ left: ($position1.left > $position2.left) ? $position2.left : $position1.left,
+ right: ($position1.left > $position2.left) ? $position1.left : $position2.left,
+ top: ($position1.top > $position2.top) ? $position2.top : $position1.top
+ };
+ }
+ else {
+ var $rect = selection.getRangeAt(0).getBoundingClientRect();
+ }
+
+ var $document = $(document);
+ var $offsetTop = $document.scrollTop();
+
+ $coordinates = {
+ left: $rect.left,
+ right: $rect.right,
+ top: $rect.top + $offsetTop
+ };
+ }
+ }
+ else if (document.selection && document.selection.type != "Control") { // IE
+ var $range = document.selection.createRange();
+ // TODO: Check coordinates if they're relative too!
+ $coordinates = {
+ left: $range.boundingLeft,
+ right: $range.boundingRight,
+ top: $range.boundingTop
+ };
+ }
+
+ return $coordinates;
+ },
+
+ /**
+ * Initializes the 'copy quote' element.
+ */
+ _initCopyQuote: function() {
+ this._copyQuote = $('#quoteManagerCopy');
+ if (!this._copyQuote.length) {
+ this._copyQuote = $('<div id="quoteManagerCopy" class="balloonTooltip"><span>' + WCF.Language.get('wcf.message.quote.quoteSelected') + '</span><span class="pointer"><span></span></span></div>').hide().appendTo(document.body);
+ this._copyQuote.click($.proxy(this._saveQuote, this));
+ }
+ },
+
+ /**
+ * Returns the text selection.
+ *
+ * @return object
+ */
+ _getSelectedText: function() {
+ if (window.getSelection) { // Opera, Firefox, Safari, Chrome, IE 9+
+ return window.getSelection();
+ }
+ else if (document.getSelection) { // Opera, Firefox, Safari, Chrome, IE 9+
+ return document.getSelection();
+ }
+ else if (document.selection) { // IE 8
+ return document.selection.createRange().text;
+ }
+
+ return '';
+ },
+
+ /**
+ * Saves a full quote.
+ *
+ * @param object event
+ */
+ _saveFullQuote: function(event) {
+ var $listItem = $(event.currentTarget);
+
+ this._proxy.setOption('data', {
+ actionName: 'saveFullQuote',
+ className: this._className,
+ interfaceName: 'wcf\\data\\IMessageQuoteAction',
+ objectIDs: [ $listItem.data('objectID') ]
+ });
+ this._proxy.sendRequest();
+
+ // mark element as quoted
+ if ($listItem.data('isQuoted')) {
+ $listItem.data('isQuoted', false).children('a').removeClass('active');
+ }
+ else {
+ $listItem.data('isQuoted', true).children('a').addClass('active');
+ }
+
+ // discard event
+ event.stopPropagation();
+ return false;
+ },
+
+ /**
+ * Saves a quote.
+ */
+ _saveQuote: function() {
+ this._proxy.setOption('data', {
+ actionName: 'saveQuote',
+ className: this._className,
+ interfaceName: 'wcf\\data\\IMessageQuoteAction',
+ objectIDs: [ this._objectID ],
+ parameters: {
+ message: this._message
+ }
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Handles successful AJAX requests.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ if (data.returnValues.count !== undefined) {
+ var $fullQuoteObjectIDs = (data.fullQuoteObjectIDs !== undefined) ? data.fullQuoteObjectIDs : { };
+ this._quoteManager.updateCount(data.returnValues.count, $fullQuoteObjectIDs);
+ }
+ },
+
+ /**
+ * Updates the full quote data for all matching objects.
+ *
+ * @param array<integer> $objectIDs
+ */
+ updateFullQuoteObjectIDs: function(objectIDs) {
+ for (var $containerID in this._containers) {
+ this._containers[$containerID].find('.jsQuoteMessage').each(function(index, button) {
+ // reset all markings
+ var $button = $(button).data('isQuoted', 0);
+ $button.children('a').removeClass('active');
+
+ // mark as active
+ if (WCF.inArray($button.data('objectID'), objectIDs)) {
+ $button.data('isQuoted', 1).children('a').addClass('active');
+ }
+ });
+ }
+ }
+});
+
+/**
+ * Manages stored quotes.
+ *
+ * @param integer count
+ */
+WCF.Message.Quote.Manager = Class.extend({
+ /**
+ * list of form buttons
+ * @var object
+ */
+ _buttons: { },
+
+ /**
+ * ckEditor element
+ * @var jQuery
+ */
+ _ckEditor: null,
+
+ /**
+ * number of stored quotes
+ * @var integer
+ */
+ _count: 0,
+
+ /**
+ * dialog overlay
+ * @var jQuery
+ */
+ _dialog: null,
+
+ /**
+ * form element
+ * @var jQuery
+ */
+ _form: null,
+
+ /**
+ * list of quote handlers
+ * @var object
+ */
+ _handlers: { },
+
+ /**
+ * true, if an up-to-date template exists
+ * @var boolean
+ */
+ _hasTemplate: false,
+
+ /**
+ * true, if related quotes should be inserted
+ * @var boolean
+ */
+ _insertQuotes: true,
+
+ /**
+ * action proxy
+ * @var WCF.Action.Proxy
+ */
+ _proxy: null,
+
+ /**
+ * list of quotes to remove upon submit
+ * @var array<string>
+ */
+ _removeOnSubmit: [ ],
+
+ /**
+ * show quotes element
+ * @var jQuery
+ */
+ _showQuotes: null,
+
+ /**
+ * allow pasting
+ * @var boolean
+ */
+ _supportPaste: false,
+
+ /**
+ * Initializes the quote manager.
+ *
+ * @param integer count
+ * @param string ckEditorID
+ * @param boolean supportPaste
+ * @param array<string> removeOnSubmit
+ */
+ init: function(count, ckEditorID, supportPaste, removeOnSubmit) {
+ this._buttons = {
+ insert: null,
+ remove: null
+ };
+ this._ckEditor = null;
+ this._count = parseInt(count) || 0;
+ this._dialog = null;
+ this._form = null;
+ this._handlers = { };
+ this._hasTemplate = false;
+ this._insertQuotes = true;
+ this._removeOnSubmit = [ ];
+ this._showQuotes = null;
+ this._supportPaste = false;
+
+ if (ckEditorID) {
+ this._ckEditor = $('#' + ckEditorID);
+ if (this._ckEditor.length) {
+ this._supportPaste = true;
+
+ // get surrounding form-tag
+ this._form = this._ckEditor.parents('form:eq(0)');
+ if (this._form.length) {
+ this._form.submit($.proxy(this._submit, this));
+ this._removeOnSubmit = removeOnSubmit || [ ];
+ }
+ else {
+ this._form = null;
+
+ // allow override
+ this._supportPaste = (supportPaste === true) ? true : false;
+ }
+ }
+ }
+
+ this._proxy = new WCF.Action.Proxy({
+ showLoadingOverlay: false,
+ success: $.proxy(this._success, this),
+ url: 'index.php/MessageQuote/?t=' + SECURITY_TOKEN + SID_ARG_2ND
+ });
+
+ this._toggleShowQuotes();
+ },
+
+ /**
+ * Registers a quote handler.
+ *
+ * @param string objectType
+ * @param WCF.Message.Quote.Handler handler
+ */
+ register: function(objectType, handler) {
+ this._handlers[objectType] = handler;
+ },
+
+ /**
+ * Updates number of stored quotes.
+ *
+ * @param integer count
+ * @param object fullQuoteObjectIDs
+ */
+ updateCount: function(count, fullQuoteObjectIDs) {
+ this._count = parseInt(count) || 0;
+
+ this._toggleShowQuotes();
+
+ // update full quote ids of handlers
+ for (var $objectType in this._handlers) {
+ if (fullQuoteObjectIDs[$objectType]) {
+ this._handlers[$objectType].updateFullQuoteObjectIDs(fullQuoteObjectIDs[$objectType]);
+ }
+ }
+ },
+
+ /**
+ * Inserts all associated quotes upon first time using quick reply.
+ *
+ * @param string className
+ * @param integer parentObjectID
+ * @param object callback
+ */
+ insertQuotes: function(className, parentObjectID, callback) {
+ if (!this._insertQuotes) {
+ this._insertQuotes = true;
+
+ return;
+ }
+
+ new WCF.Action.Proxy({
+ autoSend: true,
+ data: {
+ actionName: 'getRenderedQuotes',
+ className: className,
+ interfaceName: 'wcf\\data\\IMessageQuoteAction',
+ parameters: {
+ parentObjectID: parentObjectID
+ }
+ },
+ success: callback
+ });
+ },
+
+ /**
+ * Toggles the display of the 'Show quotes' button
+ */
+ _toggleShowQuotes: function() {
+ if (!this._count) {
+ if (this._showQuotes !== null) {
+ this._showQuotes.hide();
+ }
+ }
+ else {
+ if (this._showQuotes === null) {
+ this._showQuotes = $('#showQuotes');
+ if (!this._showQuotes.length) {
+ this._showQuotes = $('<div id="showQuotes" class="balloonTooltip" />').click($.proxy(this._click, this)).appendTo(document.body);
+ }
+ }
+
+ var $text = WCF.Language.get('wcf.message.quote.showQuotes').replace(/#count#/, this._count);
+ this._showQuotes.text($text).show();
+ }
+
+ this._hasTemplate = false;
+ },
+
+ /**
+ * Handles clicks on 'Show quotes'.
+ */
+ _click: function() {
+ if (this._hasTemplate) {
+ this._dialog.wcfDialog('open');
+ }
+ else {
+ this._proxy.showLoadingOverlayOnce();
+
+ this._proxy.setOption('data', {
+ actionName: 'getQuotes',
+ supportPaste: this._supportPaste
+ });
+ this._proxy.sendRequest();
+ }
+ },
+
+ /**
+ * Renders the dialog.
+ *
+ * @param string template
+ */
+ renderDialog: function(template) {
+ // create dialog if not exists
+ if (this._dialog === null) {
+ this._dialog = $('#messageQuoteList');
+ if (!this._dialog.length) {
+ this._dialog = $('<div id="messageQuoteList" />').hide().appendTo(document.body);
+ }
+ }
+
+ // add template
+ this._dialog.html(template);
+
+ // add 'insert' and 'delete' buttons
+ var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
+ if (this._supportPaste) this._buttons.insert = $('<button>' + WCF.Language.get('wcf.message.quote.insertAllQuotes') + '</button>').click($.proxy(this._insertSelected, this)).appendTo($formSubmit);
+ this._buttons.remove = $('<button>' + WCF.Language.get('wcf.message.quote.removeAllQuotes') + '</button>').click($.proxy(this._removeSelected, this)).appendTo($formSubmit);
+
+ // show dialog
+ this._dialog.wcfDialog({
+ title: WCF.Language.get('wcf.message.quote.manageQuotes')
+ });
+ this._dialog.wcfDialog('render');
+ this._hasTemplate = true;
+
+ // bind event listener
+ var $insertQuoteButtons = this._dialog.find('.jsInsertQuote');
+ if (this._supportPaste) {
+ $insertQuoteButtons.click($.proxy(this._insertQuote, this));
+ }
+ else {
+ $insertQuoteButtons.hide();
+ }
+
+ this._dialog.find('input.jsCheckbox').change($.proxy(this._changeButtons, this));
+
+ // mark quotes for removal
+ // TODO: is this still supported?
+ if (this._removeOnSubmit.length) {
+ var self = this;
+ this._dialog.find('input.jsRemoveQuote').each(function(index, input) {
+ var $input = $(input).change($.proxy(this._change, this));
+
+ // mark for deletion
+ if (WCF.inArray($input.parent('li').attr('data-quote-id'), self._removeOnSubmit)) {
+ $input.attr('checked', 'checked');
+ }
+ });
+ }
+ },
+
+ /**
+ * Updates button labels if a checkbox is checked or unchecked.
+ */
+ _changeButtons: function() {
+ // selection
+ if (this._dialog.find('input.jsCheckbox:checked').length) {
+ if (this._supportPaste) this._buttons.insert.html(WCF.Language.get('wcf.message.quote.insertSelectedQuotes'));
+ this._buttons.remove.html(WCF.Language.get('wcf.message.quote.removeSelectedQuotes'));
+ }
+ else {
+ // no selection, pick all
+ if (this._supportPaste) this._buttons.insert.html(WCF.Language.get('wcf.message.quote.insertAllQuotes'));
+ this._buttons.remove.html(WCF.Language.get('wcf.message.quote.removeAllQuotes'));
+ }
+ },
+
+ /**
+ * Checks for change event on delete-checkboxes.
+ *
+ * @param object event
+ */
+ _change: function(event) {
+ var $input = $(event.currentTarget);
+ var $quoteID = $input.parent('li').attr('data-quote-id');
+
+ if ($input.prop('checked')) {
+ this._removeOnSubmit.push($quoteID);
+ }
+ else {
+ for (var $index in this._removeOnSubmit) {
+ if (this._removeOnSubmit[$index] == $quoteID) {
+ delete this._removeOnSubmit[$index];
+ break;
+ }
+ }
+ }
+ },
+
+ /**
+ * Inserts the selected quotes.
+ */
+ _insertSelected: function() {
+ var $api = $('.jsQuickReply:eq(0)').data('__api');
+ if ($api && !$api.getContainer().is(':visible')) {
+ this._insertQuotes = false;
+ $api.click(null);
+ }
+
+ if (!this._dialog.find('input.jsCheckbox:checked').length) {
+ this._dialog.find('input.jsCheckbox').prop('checked', 'checked');
+ }
+
+ // insert all quotes
+ this._dialog.find('input.jsCheckbox:checked').each($.proxy(function(index, input) {
+ this._insertQuote(null, input);
+ }, this));
+
+ // close dialog
+ this._dialog.wcfDialog('close');
+ },
+
+ /**
+ * Inserts a quote.
+ *
+ * @param object event
+ * @param object inputElement
+ */
+ _insertQuote: function(event, inputElement) {
+ if (event !== null) {
+ var $api = $('.jsQuickReply:eq(0)').data('__api');
+ if ($api && !$api.getContainer().is(':visible')) {
+ this._insertQuotes = false;
+ $api.click(null);
+ }
+ }
+
+ var $listItem = (event === null) ? $(inputElement).parents('li') : $(event.currentTarget).parents('li');
+ var $quote = $.trim($listItem.children('div.jsFullQuote').text());
+ var $message = $listItem.parents('article.message');
+
+ // build quote tag
+ $quote = "[quote='" + $message.attr('data-username') + "','" + $message.data('link') + "']" + $quote + "[/quote]";
+
+ // insert into ckEditor
+ var $ckEditor = ($.browser.mobile) ? null : this._ckEditor.ckeditorGet();
+ if ($ckEditor !== null && $ckEditor.mode === 'wysiwyg') {
+ // in design mode
+ $ckEditor.insertText($quote + "\n\n");
+ }
+ else {
+ // in source mode
+ var $textarea = ($.browser.mobile) ? this._ckEditor : this._ckEditor.next('.cke_editor_text').find('textarea');
+ var $value = $textarea.val();
+ $quote += "\n\n";
+ if ($value.length == 0) {
+ $textarea.val($quote);
+ }
+ else {
+ var $position = $textarea.getCaret();
+ $textarea.val( $value.substr(0, $position) + $quote + $value.substr($position) );
+ }
+ }
+
+ // remove quote upon submit or upon request
+ this._removeOnSubmit.push($listItem.attr('data-quote-id'));
+
+ // close dialog
+ if (event !== null) {
+ this._dialog.wcfDialog('close');
+ }
+ },
+
+ /**
+ * Removes selected quotes.
+ */
+ _removeSelected: function() {
+ if (!this._dialog.find('input.jsCheckbox:checked').length) {
+ this._dialog.find('input.jsCheckbox').prop('checked', 'checked');
+ }
+
+ var $quoteIDs = [ ];
+ this._dialog.find('input.jsCheckbox:checked').each(function(index, input) {
+ $quoteIDs.push($(input).parents('li').attr('data-quote-id'));
+ });
+
+ if ($quoteIDs.length) {
+ // get object types
+ var $objectTypes = [ ];
+ for (var $objectType in this._handlers) {
+ $objectTypes.push($objectType);
+ }
+
+ this._proxy.setOption('data', {
+ actionName: 'remove',
+ objectTypes: $objectTypes,
+ quoteIDs: $quoteIDs
+ });
+ this._proxy.sendRequest();
+
+ this._dialog.wcfDialog('close');
+ }
+ },
+
+ /**
+ * Appends list of quote ids to remove after successful submit.
+ */
+ _submit: function() {
+ if (this._supportPaste && this._removeOnSubmit.length > 0) {
+ var $formSubmit = this._form.find('.formSubmit');
+ for (var $i in this._removeOnSubmit) {
+ $('<input type="hidden" name="__removeQuoteIDs[]" value="' + this._removeOnSubmit[$i] + '" />').appendTo($formSubmit);
+ }
+ }
+ },
+
+ /**
+ * Returns a list of quote ids marked for removal.
+ *
+ * @return array<integer>
+ */
+ getQuotesMarkedForRemoval: function() {
+ return this._removeOnSubmit;
+ },
+
+ /**
+ * Marks quote ids for removal.
+ */
+ markQuotesForRemoval: function() {
+ if (this._removeOnSubmit.length) {
+ this._proxy.setOption('data', {
+ actionName: 'markForRemoval',
+ quoteIDs: this._removeOnSubmit
+ });
+ this._proxy.sendRequest();
+ }
+ },
+
+ /**
+ * Remoes all marked quote ids.
+ */
+ removeMarkedQuotes: function() {
+ if (this._removeOnSubmit.length) {
+ this._proxy.setOption('data', {
+ actionName: 'removeMarkedQuotes'
+ });
+ this._proxy.sendRequest();
+ }
+ },
+
+ /**
+ * Counts stored quotes.
+ */
+ countQuotes: function() {
+ var $objectTypes = [ ];
+ for (var $objectType in this._handlers) {
+ $objectTypes.push($objectType);
+ }
+
+ this._proxy.setOption('data', {
+ actionName: 'count',
+ objectTypes: $objectTypes
+ });
+ this._proxy.sendRequest();
+ },
+
+ /**
+ * Handles successful AJAX requests.
+ *
+ * @param object data
+ * @param string textStatus
+ * @param jQuery jqXHR
+ */
+ _success: function(data, textStatus, jqXHR) {
+ if (data === null) {
+ return;
+ }
+
+ if (data.count !== undefined) {
+ var $fullQuoteObjectIDs = (data.fullQuoteObjectIDs !== undefined) ? data.fullQuoteObjectIDs : { };
+ this.updateCount(data.count, $fullQuoteObjectIDs);
+ }
+
+ if (data.template !== undefined) {
+ if ($.trim(data.template) == '') {
+ this.updateCount(0, { });
+ }
+ else {
+ this.renderDialog(data.template);
+ }
+ }
+ }
+});
+
+/**
+ * Namespace for message sharing related classes.
+ */
+WCF.Message.Share = { };
+
+/**
+ * Displays a dialog overlay for permalinks.
+ */
+WCF.Message.Share.Content = Class.extend({
+ /**
+ * list of cached templates
+ * @var object
+ */
+ _cache: { },
+
+ /**
+ * dialog overlay
+ * @var jQuery
+ */
+ _dialog: null,
+
+ /**
+ * Initializes the WCF.Message.Share.Content class.
+ */
+ init: function() {
+ this._cache = { };
+ this._dialog = null;
+
+ this._initLinks();
+
+ WCF.DOMNodeInsertedHandler.addCallback('WCF.Message.Share.Content', $.proxy(this._initLinks, this));
+ },
+
+ /**
+ * Initializes share links.
+ */
+ _initLinks: function() {
+ $('a.jsButtonShare').removeClass('jsButtonShare').click($.proxy(this._click, this));
+ },
+
+ /**
+ * Displays links to share this content.
+ *
+ * @param object event
+ */
+ _click: function(event) {
+ event.preventDefault();
+
+ var $target = $(event.currentTarget);
+ var $link = $target.prop('href');
+ var $title = ($target.data('linkTitle') ? $target.data('linkTitle') : $link);
+ var $key = $link.hashCode();
+ if (this._cache[$key] === undefined) {
+
+ // remove dialog contents
+ var $dialogInitialized = false;
+ if (this._dialog === null) {
+ this._dialog = $('<div />').hide().appendTo(document.body);
+ $dialogInitialized = true;
+ }
+ else {
+ this._dialog.empty();
+ }
+
+ // permalink (plain text)
+ var $fieldset = $('<fieldset><legend><label for="__sharePermalink">' + WCF.Language.get('wcf.message.share.permalink') + '</label></legend></fieldset>').appendTo(this._dialog);
+ $('<input type="text" id="__sharePermalink" class="long" readonly="readonly" />').attr('value', $link).appendTo($fieldset);
+
+ // permalink (BBCode)
+ var $fieldset = $('<fieldset><legend><label for="__sharePermalinkBBCode">' + WCF.Language.get('wcf.message.share.permalink.bbcode') + '</label></legend></fieldset>').appendTo(this._dialog);
+ $('<input type="text" id="__sharePermalinkBBCode" class="long" readonly="readonly" />').attr('value', '[url=\'' + $link + '\']' + $title + '[/url]').appendTo($fieldset);
+
+ // permalink (HTML)
+ var $fieldset = $('<fieldset><legend><label for="__sharePermalinkHTML">' + WCF.Language.get('wcf.message.share.permalink.html') + '</label></legend></fieldset>').appendTo(this._dialog);
+ $('<input type="text" id="__sharePermalinkHTML" class="long" readonly="readonly" />').attr('value', '<a href="' + $link + '">' + WCF.String.escapeHTML($title) + '</a>').appendTo($fieldset);
+
+ this._cache[$key] = this._dialog.html();
+
+ if ($dialogInitialized) {
+ this._dialog.wcfDialog({
+ title: WCF.Language.get('wcf.message.share')
+ });
+ }
+ else {
+ this._dialog.wcfDialog('open');
+ }
+ }
+ else {
+
+ this._dialog.html(this._cache[$key]).wcfDialog('open');
+ }
+
+ this._dialog.find('input').click(function() { $(this).select(); });
+ }
+});
+
+/**
+ * Provides buttons to share a page through multiple social community sites.
+ *
+ * @param boolean fetchObjectCount
+ */
+WCF.Message.Share.Page = Class.extend({
+ /**
+ * list of share buttons
+ * @var object
+ */
+ _ui: { },
+
+ /**
+ * page description
+ * @var string
+ */
+ _pageDescription: '',
+
+ /**
+ * canonical page URL
+ * @var string
+ */
+ _pageURL: '',
+
+ /**
+ * Initializes the WCF.Message.Share.Page class.
+ *
+ * @param boolean fetchObjectCount
+ */
+ init: function(fetchObjectCount) {
+ this._pageDescription = encodeURIComponent($('meta[property="og:description"]').prop('content'));
+ this._pageURL = encodeURIComponent($('meta[property="og:url"]').prop('content'));
+
+ var $container = $('.messageShareButtons');
+ this._ui = {
+ facebook: $container.find('.jsShareFacebook'),
+ google: $container.find('.jsShareGoogle'),
+ reddit: $container.find('.jsShareReddit'),
+ twitter: $container.find('.jsShareTwitter')
+ };
+
+ this._ui.facebook.children('a').click($.proxy(this._shareFacebook, this));
+ this._ui.google.children('a').click($.proxy(this._shareGoogle, this));
+ this._ui.reddit.children('a').click($.proxy(this._shareReddit, this));
+ this._ui.twitter.children('a').click($.proxy(this._shareTwitter, this));
+
+ if (fetchObjectCount === true) {
+ this._fetchFacebook();
+ this._fetchTwitter();
+ this._fetchReddit();
+ }
+ },
+
+ /**
+ * Shares current page to selected social community site.
+ *
+ * @param string objectName
+ * @param string url
+ */
+ _share: function(objectName, url) {
+ window.open(url.replace(/{pageURL}/, this._pageURL).replace(/{text}/, this._pageDescription), 'height=600,width=600');
+ },
+
+ /**
+ * Shares current page with Facebook.
+ */
+ _shareFacebook: function() {
+ this._share('facebook', 'https://www.facebook.com/sharer.php?u={pageURL}&t={text}');
+ },
+
+ /**
+ * Shares current page with Google Plus.
+ */
+ _shareGoogle: function() {
+ this._share('google', 'https://plus.google.com/share?url={pageURL}');
+ },
+
+ /**
+ * Shares current page with Reddit.
+ */
+ _shareReddit: function() {
+ this._share('reddit', 'https://ssl.reddit.com/submit?url={pageURL}');
+ },
+
+ /**
+ * Shares current page with Twitter.
+ */
+ _shareTwitter: function() {
+ this._share('twitter', 'https://twitter.com/share?url={pageURL}&text={text}');
+ },
+
+ /**
+ * Fetches share count from a social community site.
+ *
+ * @param string url
+ * @param object callback
+ * @param string callbackName
+ */
+ _fetchCount: function(url, callback, callbackName) {
+ var $options = {
+ autoSend: true,
+ dataType: 'jsonp',
+ showLoadingOverlay: false,
+ success: callback,
+ suppressErrors: true,
+ type: 'GET',
+ url: url.replace(/{pageURL}/, this._pageURL)
+ };
+ if (callbackName) {
+ $options.jsonp = callbackName;
+ }
+
+ new WCF.Action.Proxy($options);
+ },
+
+ /**
+ * Fetches number of Facebook likes.
+ */
+ _fetchFacebook: function() {
+ this._fetchCount('https://graph.facebook.com/?id={pageURL}', $.proxy(function(data) {
+ if (data.shares) {
+ this._ui.facebook.children('span.badge').show().text(data.shares);
+ }
+ }, this));
+ },
+
+ /**
+ * Fetches tweet count from Twitter.
+ */
+ _fetchTwitter: function() {
+ this._fetchCount('http://urls.api.twitter.com/1/urls/count.json?url={pageURL}', $.proxy(function(data) {
+ if (data.count) {
+ this._ui.twitter.children('span.badge').show().text(data.count);
+ }
+ }, this));
+ },
+
+ /**
+ * Fetches cumulative vote sum from Reddit.
+ */
+ _fetchReddit: function() {
+ this._fetchCount('http://www.reddit.com/api/info.json?url={pageURL}', $.proxy(function(data) {
+ if (data.data.children.length) {
+ this._ui.reddit.children('span.badge').show().text(data.data.children[0].data.score);
+ }
+ }, this), 'jsonp');
+ }
+});
--- /dev/null
+WCF.Message={};WCF.Message.BBCode={};WCF.Message.BBCode.CodeViewer=Class.extend({_dialog:null,init:function(){this._dialog=null;this._initCodeBoxes();WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.BBCode.CodeViewer",$.proxy(this._initCodeBoxes,this));WCF.DOMNodeInsertedHandler.forceExecution()},_initCodeBoxes:function(){$(".codeBox:not(.jsCodeViewer)").each($.proxy(function(a,c){var b=$(c).addClass("jsCodeViewer");$('<span class="icon icon16 icon-copy pointer jsTooltip" title="'+WCF.Language.get("wcf.message.bbcode.code.copy")+'" />').appendTo(b.find("div > h3")).click($.proxy(this._click,this))},this))},_click:function(b){var a="";$(b.currentTarget).parents("div").next("ol").children("li").each(function(c,d){if(a){a+="\n"}a+=$(d).text().replace(/\n+$/,"")});if(this._dialog===null){this._dialog=$('<div><textarea cols="60" rows="12" readonly="readonly" /></div>').hide().appendTo(document.body);this._dialog.children("textarea").val(a);this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.bbcode.code.copy")})}else{this._dialog.children("textarea").val(a);this._dialog.wcfDialog("open")}this._dialog.children("textarea").select()}});WCF.Message.FormGuard=Class.extend({init:function(){var a=$("form.jsFormGuard").removeClass("jsFormGuard").submit(function(){$(this).find(".formSubmit input[type=submit]").disable()});$(window).unload(function(){a.find(".formSubmit input[type=submit]").enable()})}});WCF.Message.Preview=Class.extend({_className:"",_messageFieldID:"",_messageField:null,_proxy:null,_previewButton:null,_previewButtonLabel:"",init:function(b,a,c){this._className=b;this._messageFieldID=$.wcfEscapeID(a);this._messageField=$("#"+this._messageFieldID);if(!this._messageField.length){console.debug("[WCF.Message.Preview] Unable to find message field identified by '"+this._messageFieldID+"'");return}c=$.wcfEscapeID(c);this._previewButton=$("#"+c);if(!this._previewButton.length){console.debug("[WCF.Message.Preview] Unable to find preview button identified by '"+c+"'");return}this._previewButton.click($.proxy(this._click,this));this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)})},_click:function(b){var a=this._getMessage();if(a===null){console.debug("[WCF.Message.Preview] Unable to access ckEditor instance of '"+this._messageFieldID+"'");return}this._proxy.setOption("data",{actionName:"getMessagePreview",className:this._className,parameters:this._getParameters(a)});this._proxy.sendRequest();this._previewButtonLabel=this._previewButton.html();this._previewButton.html(WCF.Language.get("wcf.global.loading")).disable();b.stopPropagation();return false},_getParameters:function(b){var a={};$("#settings").find("input[type=checkbox]").each(function(c,e){var d=$(e);if(d.is(":checked")){a[d.prop("name")]=d.prop("value")}});return{data:{message:b},options:a}},_getMessage:function(){if($.browser.mobile){return this._messageField.val()}else{if(this._messageField.data("ckeditorInstance")){var a=this._messageField.ckeditorGet();return a.getData()}}return null},_success:function(b,c,a){this._previewButton.html(this._previewButtonLabel).enable();this._handleResponse(b)},_handleResponse:function(a){}});WCF.Message.DefaultPreview=WCF.Message.Preview.extend({_attachmentObjectType:null,_attachmentObjectID:null,_tmpHash:null,init:function(b,a,c){this._super("wcf\\data\\bbcode\\MessagePreviewAction","text","previewButton");this._attachmentObjectType=b||null;this._attachmentObjectID=a||null;this._tmpHash=c||null},_handleResponse:function(b){var a=$("#previewContainer");if(!a.length){a=$('<div class="container containerPadding marginTop" id="previewContainer"><fieldset><legend>'+WCF.Language.get("wcf.global.preview")+"</legend><div></div></fieldset>").prependTo($("#messageContainer")).wcfFadeIn()}a.find("div:eq(0)").html(b.returnValues.message)},_getParameters:function(b){var a=this._super(b);if(this._attachmentObjectType!=null){a.attachmentObjectType=this._attachmentObjectType;a.attachmentObjectID=this._attachmentObjectID;a.tmpHash=this._tmpHash}return a}});WCF.Message.Multilingualism=Class.extend({_availableLanguages:{},_languageID:0,_languageInput:null,init:function(c,d,a){this._availableLanguages=d;this._languageID=c||0;this._languageInput=$("#languageID");this._updateLabel();this._languageInput.find(".dropdownMenu > li").click($.proxy(this._click,this));if(!a){var b=this._languageInput.find(".dropdownMenu");$('<li class="dropdownDivider" />').appendTo(b);$('<li><span><span class="badge">'+this._availableLanguages[0]+"</span></span></li>").click($.proxy(this._disable,this)).appendTo(b)}this._languageInput.parents("form").submit($.proxy(this._submit,this))},_click:function(a){this._languageID=$(a.currentTarget).data("languageID");this._updateLabel()},_disable:function(){this._languageID=0;this._updateLabel()},_updateLabel:function(){this._languageInput.find(".dropdownToggle > span").text(this._availableLanguages[this._languageID])},_submit:function(){this._languageInput.next("input[name=languageID]").prop("value",this._languageID)}});WCF.Message.SmileyCategories=Class.extend({_cache:[],_proxy:null,_ckEditor:null,init:function(){this._cache=[];this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});$("#smilies").on("wcftabsbeforeactivate",$.proxy(this._click,this));var a=this;new WCF.PeriodicalExecuter(function(b){b.stop();a._click({},{newTab:$("#smilies > .menu li.ui-state-active")})},100)},_click:function(b,c){var a=parseInt($(c.newTab).children("a").data("smileyCategoryID"));if(a&&!WCF.inArray(a,this._cache)){this._proxy.setOption("data",{actionName:"getSmilies",className:"wcf\\data\\smiley\\category\\SmileyCategoryAction",objectIDs:[a]});this._proxy.sendRequest()}},_success:function(c,d,b){var a=parseInt(c.returnValues.smileyCategoryID);this._cache.push(a);$("#smilies-"+a).html(c.returnValues.template)}});WCF.Message.Smilies=Class.extend({_ckEditor:null,init:function(a){if(a){this._ckEditor=$("#"+a);$(document).on("click",".jsSmiley",$.proxy(this._smileyClick,this))}},_smileyClick:function(c){var e=$(c.currentTarget);var h=e.data("smileyCode");var i=this._ckEditor.ckeditorGet();var f=e.find("img").attr("src");if(!WCF.inArray(h,i.config.smiley_descriptions)){i.config.smiley_descriptions.push(h);i.config.smiley_images.push(f)}if(i.mode==="wysiwyg"){var a=i.document.createElement("img",{attributes:{src:f,"class":"smiley",alt:h}});i.insertText(" ");i.insertElement(a);i.insertText(" ")}else{var g=this._ckEditor.next(".cke_editor_text").find("textarea");var j=g.val();if(j.length==0){g.val(h);g.setCaret(h.length)}else{var d=g.getCaret();var b=((j.substr(d-1,1)!==" ")?" ":"")+h+" ";g.val(j.substr(0,d)+b+j.substr(d));g.setCaret(d+b.length)}}}});WCF.Message.QuickReply=Class.extend({_container:null,_messageField:null,_notification:null,_proxy:null,_quoteManager:null,_scrollHandler:null,_successMessageNonVisible:"",init:function(c,b){this._container=$("#messageQuickReply");this._messageField=$("#text");if(!this._container||!this._messageField){return}var a=this._container.find(".formSubmit");a.find("button[data-type=save]").click($.proxy(this._save,this));if(c){a.find("button[data-type=extended]").click($.proxy(this._prepareExtended,this))}a.find("button[data-type=cancel]").click($.proxy(this._cancel,this));if(b){this._quoteManager=b}$(".jsQuickReply").data("__api",this).click($.proxy(this.click,this));this._proxy=new WCF.Action.Proxy({failure:$.proxy(this._failure,this),showLoadingOverlay:false,success:$.proxy(this._success,this)});this._scroll=new WCF.Effect.Scroll();this._notification=new WCF.System.Notification(WCF.Language.get("wcf.global.success.add"));this._successMessageNonVisible=""},click:function(b){this._container.toggle();if(this._container.is(":visible")){this._scroll.scrollTo(this._container,true);WCF.Message.Submit.registerButton("text",this._container.find(".formSubmit button[data-type=save]"));if(this._quoteManager){var a=true;if($.browser.touch){a=(!this._messageField.val().length)}else{a=(!this._messageField.ckeditorGet().getData().length)}if(a){this._quoteManager.insertQuotes(this._getClassName(),this._getObjectID(),$.proxy(this._insertQuotes,this))}}new WCF.PeriodicalExecuter($.proxy(function(c){c.stop();if($.browser.mobile){this._messageField.focus()}else{this._messageField.ckeditorGet().ui.editor.focus()}},this),250)}if(b!==null){b.stopPropagation();return false}},getContainer:function(){return this._container},_insertQuotes:function(a){if(!a.returnValues.template){return}if($.browser.mobile){this._messageField.val(a.returnValues.template)}else{this._messageField.ckeditorGet().insertText(a.returnValues.template)}},_save:function(){var b="";if($.browser.mobile){b=$.trim(this._messageField.val())}else{var a=this._messageField.ckeditorGet();b=$.trim(a.getData())}var d=this._messageField.parent().find("small.innerError");if(b===""){if(!d.length){d=$('<small class="innerError" />').appendTo(this._messageField.parent())}d.html(WCF.Language.get("wcf.global.form.error.empty"));return}else{d.remove()}this._proxy.setOption("data",{actionName:"quickReply",className:this._getClassName(),interfaceName:"wcf\\data\\IMessageQuickReplyAction",parameters:this._getParameters(b)});this._proxy.sendRequest();var c=this._container.find(".messageQuickReplyContent .messageBody");$('<span class="icon icon48 icon-spinner" />').appendTo(c);c.children("#cke_text").hide().end().next().hide()},_getParameters:function(b){var a={objectID:this._getObjectID(),data:{message:b},lastPostTime:this._container.data("lastPostTime"),pageNo:this._container.data("pageNo"),removeQuoteIDs:(this._quoteManager===null?[]:this._quoteManager.getQuotesMarkedForRemoval())};if(this._container.data("anchor")){a.anchor=this._container.data("anchor")}return a},_cancel:function(){this._revertQuickReply(true);if($.browser.mobile){this._messageField.val("")}else{this._messageField.ckeditorGet().setData("")}},_revertQuickReply:function(b){var a=this._container.find(".messageQuickReplyContent .messageBody");if(b){this._container.hide();a.children("small.innerError").remove()}a.children(".icon-spinner").remove();a.children("#cke_text").show();a.next().show()},_prepareExtended:function(){if(this._quoteManager!==null){this._quoteManager.markQuotesForRemoval()}var b="";if($.browser.mobile){b=this._messageField.val()}else{var a=this._messageField.ckeditorGet();b=a.getData()}new WCF.Action.Proxy({autoSend:true,data:{actionName:"jumpToExtended",className:this._getClassName(),interfaceName:"wcf\\data\\IExtendedMessageQuickReplyAction",parameters:{containerID:this._getObjectID(),message:b}},success:function(d,e,c){window.location=d.returnValues.url}})},_success:function(c,d,b){if(c.returnValues.url){window.location=c.returnValues.url}else{if(c.returnValues.template){var a=$(""+c.returnValues.template);a.insertBefore(this._container);this._container.data("lastPostTime",c.returnValues.lastPostTime);this._notification.show(undefined,undefined,WCF.Language.get("wcf.global.success.add"));this._updateHistory(a.wcfIdentify())}else{var a=(this._successMessageNonVisible)?this._successMessageNonVisible:"wcf.global.success.add";this._notification.show(undefined,5000,WCF.Language.get(a))}if($.browser.mobile){this._messageField.val("")}else{this._messageField.ckeditorGet().setData("")}this._revertQuickReply(true);if(this._quoteManager!==null){this._quoteManager.countQuotes()}}},_failure:function(b){this._revertQuickReply(false);if(b===null||b.returnValues===undefined||b.returnValues.errorType===undefined){return true}var a=this._container.find(".messageQuickReplyContent .messageBody");var c=a.children("small.innerError").empty();if(!c.length){c=$('<small class="innerError" />').appendTo(a)}c.html(b.returnValues.errorType);return false},_getClassName:function(){return""},_getObjectID:function(){return 0},_updateHistory:function(a){window.location.hash=a}});WCF.Message.InlineEditor=Class.extend({_activeElementID:"",_cache:"",_container:{},_containerID:0,_dropdowns:{},_messageContainerSelector:".jsMessage",_messageEditorIDPrefix:"messageEditor",_notification:null,_proxy:null,_supportExtendedForm:false,init:function(a,b){this._activeElementID="";this._cache="";this._container={};this._containerID=parseInt(a);this._dropdowns={};this._supportExtendedForm=(b)?true:false;this._proxy=new WCF.Action.Proxy({failure:$.proxy(this._failure,this),showLoadingOverlay:false,success:$.proxy(this._success,this)});this._notification=new WCF.System.Notification(WCF.Language.get("wcf.global.success.edit"));this.initContainers();WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.InlineEditor",$.proxy(this.initContainers,this))},initContainers:function(){$(this._messageContainerSelector).each($.proxy(function(b,a){var d=$(a);var c=d.wcfIdentify();if(!this._container[c]){this._container[c]=d;if(d.data("canEditInline")){d.find(".jsMessageEditButton:eq(0)").data("containerID",c).click($.proxy(this._clickInline,this)).dblclick($.proxy(this._click,this))}else{if(d.data("canEdit")){d.find(".jsMessageEditButton:eq(0)").data("containerID",c).click($.proxy(this._click,this))}}}},this))},_click:function(c,a){var b=(c===null)?a:$(c.currentTarget).data("containerID");if(this._activeElementID===""){this._activeElementID=b;this._prepare();this._proxy.setOption("data",{actionName:"beginEdit",className:this._getClassName(),interfaceName:"wcf\\data\\IMessageInlineEditorAction",parameters:{containerID:this._containerID,objectID:this._container[b].data("objectID")}});this._proxy.sendRequest()}else{var d=new WCF.System.Notification(WCF.Language.get("wcf.message.error.editorAlreadyInUse"),"warning");d.show()}if(c!==null){c.stopPropagation();return false}},_clickInline:function(c){var d=$(c.currentTarget);if(!d.hasClass("dropdownToggle")){var b=d.data("containerID");WCF.DOMNodeInsertedHandler.enable();d.addClass("dropdownToggle").parent().addClass("dropdown");var a=$('<ul class="dropdownMenu" />').insertAfter(d);this._initDropdownMenu(b,a);WCF.DOMNodeInsertedHandler.disable();this._dropdowns[this._container[b].data("objectID")]=a;WCF.Dropdown.registerCallback(d.parent().wcfIdentify(),$.proxy(this._toggleDropdown,this));d.trigger("click")}c.stopPropagation();return false},_failure:function(b){this._revertEditor();if(b===null||b.returnValues===undefined||b.returnValues.errorType===undefined){return true}var a=this._container[this._activeElementID].find(".messageBody .messageInlineEditor");var c=a.children("small.innerError").empty();if(!c.length){c=$('<small class="innerError" />').insertBefore(a.children(".formSubmit"))}c.html(b.returnValues.errorType);return false},_toggleDropdown:function(b,a){b.parents(".messageOptions").toggleClass("forceOpen")},_initDropdownMenu:function(a,b){},_prepare:function(){var b=this._container[this._activeElementID].find(".messageBody");$('<span class="icon icon48 icon-spinner" />').appendTo(b);var a=b.find(".messageText");this._cache=a.html();a.empty()},_cancel:function(){var d=this._container[this._activeElementID];try{var a=$("#"+this._messageEditorIDPrefix+d.data("objectID")).ckeditorGet();a.destroy()}catch(c){}var b=d.find(".messageBody");b.children(".icon-spinner").remove();b.find(".messageText").html(this._cache);this._container[this._activeElementID].find(".messageOptions").removeClass("forceHidden");this._activeElementID=""},_success:function(b,c,a){switch(b.returnValues.actionName){case"beginEdit":this._showEditor(b);break;case"save":this._showMessage(b);break}},_showEditor:function(e){var c=this._container[this._activeElementID].find(".messageBody");c.children(".icon-spinner").remove();var b=c.find(".messageText");$(""+e.returnValues.template).appendTo(b);var a=b.find(".formSubmit");var d=a.find("button[data-type=save]").click($.proxy(this._save,this));if(this._supportExtendedForm){a.find("button[data-type=extended]").click($.proxy(this._prepareExtended,this))}a.find("button[data-type=cancel]").click($.proxy(this._cancel,this));WCF.Message.Submit.registerButton(this._messageEditorIDPrefix+this._container[this._activeElementID].data("objectID"),d);this._container[this._activeElementID].find(".messageOptions").addClass("forceHidden");new WCF.PeriodicalExecuter($.proxy(function(f){f.stop();$("#"+this._messageEditorIDPrefix+this._container[this._activeElementID].data("objectID")).ckeditorGet().ui.editor.focus()},this),250)},_revertEditor:function(){var a=this._container[this._activeElementID].find(".messageBody");a.children("span.icon-spinner").remove();a.find(".messageText").children().show()},_save:function(){var d=this._container[this._activeElementID];var c=d.data("objectID");var b="";if($.browser.mobile){b=$("#"+this._messageEditorIDPrefix+c).val()}else{var a=$("#"+this._messageEditorIDPrefix+c).ckeditorGet();b=a.getData()}this._proxy.setOption("data",{actionName:"save",className:this._getClassName(),interfaceName:"wcf\\data\\IMessageInlineEditorAction",parameters:{containerID:this._containerID,data:{message:b},objectID:c}});this._proxy.sendRequest();this._hideEditor()},_prepareExtended:function(){var d=this._container[this._activeElementID];var c=d.data("objectID");var b="";if($.browser.mobile){b=$("#"+this._messageEditorIDPrefix+c).val()}else{var a=$("#"+this._messageEditorIDPrefix+c).ckeditorGet();b=a.getData()}new WCF.Action.Proxy({autoSend:true,data:{actionName:"jumpToExtended",className:this._getClassName(),parameters:{containerID:this._containerID,message:b,messageID:c}},success:function(f,g,e){window.location=f.returnValues.url}})},_hideEditor:function(){var a=this._container[this._activeElementID].find(".messageBody");$('<span class="icon icon48 icon-spinner" />').appendTo(a);a.find(".messageText").children().hide()},_showMessage:function(d){var e=this._container[this._activeElementID];var c=e.find(".messageBody");c.children(".icon-spinner").remove();var b=c.find(".messageText");this._container[this._activeElementID].find(".messageOptions").removeClass("forceHidden");if(!$.browser.mobile){var a=$("#"+this._messageEditorIDPrefix+e.data("objectID")).ckeditorGet();a.destroy()}b.empty();b.html(d.returnValues.message);this._activeElementID="";this._updateHistory(this._getHash(e.data("objectID")));this._notification.show()},_getClassName:function(){return""},_getHash:function(a){return"#message"+a},_updateHistory:function(a){window.location.hash=a}});WCF.Message.Submit={_buttons:{},registerButton:function(b,a){if(!WCF.Browser.isChrome()){return}this._buttons[b]=$(a)},execute:function(a){if(!this._buttons[a]){return}this._buttons[a].trigger("click")}};WCF.Message.Quote={};WCF.Message.Quote.Handler=Class.extend({_activeContainerID:"",_className:"",_containers:{},_containerSelector:"",_copyQuote:null,_message:"",_messageBodySelector:"",_objectID:0,_objectType:"",_proxy:null,_quoteManager:null,init:function(e,d,b,a,c,f){this._className=d;if(this._className==""){console.debug("[WCF.Message.QuoteManager] Empty class name given, aborting.");return}this._objectType=b;if(this._objectType==""){console.debug("[WCF.Message.QuoteManager] Empty object type name given, aborting.");return}this._containerSelector=a;this._message="";this._messageBodySelector=c;this._messageContentSelector=f;this._objectID=0;this._proxy=new WCF.Action.Proxy({success:$.proxy(this._success,this)});this._initContainers();this._initCopyQuote();$(document).mouseup($.proxy(this._mouseUp,this));this._quoteManager=e;this._quoteManager.register(this._objectType,this);WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.Quote.Handler"+b.hashCode(),$.proxy(this._initContainers,this))},_initContainers:function(){var a=this;$(this._containerSelector).each(function(c,b){var e=$(b);var d=e.wcfIdentify();if(!a._containers[d]){a._containers[d]=e;if(e.hasClass("jsInvalidQuoteTarget")){return true}if(a._messageBodySelector!==null){e=e.find(a._messageBodySelector).data("containerID",d)}e.mousedown($.proxy(a._mouseDown,a));a._containers[d].find(".jsQuoteMessage").click($.proxy(a._saveFullQuote,a))}})},_mouseDown:function(a){this._copyQuote.hide();var b=$(a.currentTarget);if(this._messageBodySelector){b=this._containers[b.data("containerID")]}this._activeContainerID=b.wcfIdentify();if($.browser.mozilla){b.find("img").each(function(){var c=$(this);c.data("__alt",c.attr("alt")).removeAttr("alt")})}},_getNodeText:function(d){var c="";for(var b=0;b<d.childNodes.length;b++){if(d.childNodes[b].nodeType==3){c+=d.childNodes[b].nodeValue}else{var a=d.childNodes[b].tagName.toLowerCase();if(a==="li"){c+="\r\n"}c+=this._getNodeText(d.childNodes[b]);if(a==="ul"){c+="\n"}}}return c},_mouseUp:function(a){if(this._activeContainerID==""){this._copyQuote.hide();return}var i=this._containers[this._activeContainerID];var c=this._getSelectedText();var f=$.trim(c);if(f==""){this._copyQuote.hide();return}var d=null;if(this._messageBodySelector){d=this._getNodeText(i.find(this._messageContentSelector).get(0))}else{d=this._getNodeText(i.get(0))}if(this._normalize(d).indexOf(this._normalize(f))===-1){return}this._copyQuote.show();var g=this._getBoundingRectangle(c);var e=this._copyQuote.getDimensions("outer");var b=(g.right-g.left)/2-(e.width/2)+g.left;this._copyQuote.css({top:g.top-e.height-7+"px",left:b+"px"});this._copyQuote.hide();this._activeContainerID="";var h=this;new WCF.PeriodicalExecuter(function(j){j.stop();var k=$.trim(h._getSelectedText());if(k!=""){h._copyQuote.show();h._message=k;h._objectID=i.data("objectID");if($.browser.mozilla){i.find("img").each(function(){var l=$(this);l.attr("alt",l.data("__alt"))})}}},10)},_normalize:function(a){return a.replace(/\r?\n|\r/g,"\n").replace(/\s{2,}/g," ")},_getOffset:function(c,e){c.collapse(e);var g=WCF.getRandomID();var a=document.createElement("span");a.innerHTML='<span id="'+g+'"></span>';var h=document.createDocumentFragment(),b,d;while(b=a.firstChild){d=h.appendChild(b)}c.insertNode(h);a=$("#"+g);var f=a.offset();f.top=f.top-$(window).scrollTop();a.remove();return f},_getBoundingRectangle:function(g){var i=null;if(document.createRange&&typeof document.createRange().getBoundingClientRect!="undefined"){if(g.rangeCount>0){var h=g.getRangeAt(0).getClientRects();if(!$.browser.mozilla&&h.length>1){var d=g.getRangeAt(0);var b=this._getOffset(d,true);var d=g.getRangeAt(0);var a=this._getOffset(d,false);var e={left:(b.left>a.left)?a.left:b.left,right:(b.left>a.left)?b.left:a.left,top:(b.top>a.top)?a.top:b.top}}else{var e=g.getRangeAt(0).getBoundingClientRect()}var c=$(document);var f=c.scrollTop();i={left:e.left,right:e.right,top:e.top+f}}}else{if(document.selection&&document.selection.type!="Control"){var d=document.selection.createRange();i={left:d.boundingLeft,right:d.boundingRight,top:d.boundingTop}}}return i},_initCopyQuote:function(){this._copyQuote=$("#quoteManagerCopy");if(!this._copyQuote.length){this._copyQuote=$('<div id="quoteManagerCopy" class="balloonTooltip"><span>'+WCF.Language.get("wcf.message.quote.quoteSelected")+'</span><span class="pointer"><span></span></span></div>').hide().appendTo(document.body);this._copyQuote.click($.proxy(this._saveQuote,this))}},_getSelectedText:function(){if(window.getSelection){return window.getSelection()}else{if(document.getSelection){return document.getSelection()}else{if(document.selection){return document.selection.createRange().text}}}return""},_saveFullQuote:function(b){var a=$(b.currentTarget);this._proxy.setOption("data",{actionName:"saveFullQuote",className:this._className,interfaceName:"wcf\\data\\IMessageQuoteAction",objectIDs:[a.data("objectID")]});this._proxy.sendRequest();if(a.data("isQuoted")){a.data("isQuoted",false).children("a").removeClass("active")}else{a.data("isQuoted",true).children("a").addClass("active")}b.stopPropagation();return false},_saveQuote:function(){this._proxy.setOption("data",{actionName:"saveQuote",className:this._className,interfaceName:"wcf\\data\\IMessageQuoteAction",objectIDs:[this._objectID],parameters:{message:this._message}});this._proxy.sendRequest()},_success:function(c,d,b){if(c.returnValues.count!==undefined){var a=(c.fullQuoteObjectIDs!==undefined)?c.fullQuoteObjectIDs:{};this._quoteManager.updateCount(c.returnValues.count,a)}},updateFullQuoteObjectIDs:function(b){for(var a in this._containers){this._containers[a].find(".jsQuoteMessage").each(function(c,d){var e=$(d).data("isQuoted",0);e.children("a").removeClass("active");if(WCF.inArray(e.data("objectID"),b)){e.data("isQuoted",1).children("a").addClass("active")}})}}});WCF.Message.Quote.Manager=Class.extend({_buttons:{},_ckEditor:null,_count:0,_dialog:null,_form:null,_handlers:{},_hasTemplate:false,_insertQuotes:true,_proxy:null,_removeOnSubmit:[],_showQuotes:null,_supportPaste:false,init:function(c,b,a,d){this._buttons={insert:null,remove:null};this._ckEditor=null;this._count=parseInt(c)||0;this._dialog=null;this._form=null;this._handlers={};this._hasTemplate=false;this._insertQuotes=true;this._removeOnSubmit=[];this._showQuotes=null;this._supportPaste=false;if(b){this._ckEditor=$("#"+b);if(this._ckEditor.length){this._supportPaste=true;this._form=this._ckEditor.parents("form:eq(0)");if(this._form.length){this._form.submit($.proxy(this._submit,this));this._removeOnSubmit=d||[]}else{this._form=null;this._supportPaste=(a===true)?true:false}}}this._proxy=new WCF.Action.Proxy({showLoadingOverlay:false,success:$.proxy(this._success,this),url:"index.php/MessageQuote/?t="+SECURITY_TOKEN+SID_ARG_2ND});this._toggleShowQuotes()},register:function(a,b){this._handlers[a]=b},updateCount:function(c,b){this._count=parseInt(c)||0;this._toggleShowQuotes();for(var a in this._handlers){if(b[a]){this._handlers[a].updateFullQuoteObjectIDs(b[a])}}},insertQuotes:function(a,b,c){if(!this._insertQuotes){this._insertQuotes=true;return}new WCF.Action.Proxy({autoSend:true,data:{actionName:"getRenderedQuotes",className:a,interfaceName:"wcf\\data\\IMessageQuoteAction",parameters:{parentObjectID:b}},success:c})},_toggleShowQuotes:function(){if(!this._count){if(this._showQuotes!==null){this._showQuotes.hide()}}else{if(this._showQuotes===null){this._showQuotes=$("#showQuotes");if(!this._showQuotes.length){this._showQuotes=$('<div id="showQuotes" class="balloonTooltip" />').click($.proxy(this._click,this)).appendTo(document.body)}}var a=WCF.Language.get("wcf.message.quote.showQuotes").replace(/#count#/,this._count);this._showQuotes.text(a).show()}this._hasTemplate=false},_click:function(){if(this._hasTemplate){this._dialog.wcfDialog("open")}else{this._proxy.showLoadingOverlayOnce();this._proxy.setOption("data",{actionName:"getQuotes",supportPaste:this._supportPaste});this._proxy.sendRequest()}},renderDialog:function(c){if(this._dialog===null){this._dialog=$("#messageQuoteList");if(!this._dialog.length){this._dialog=$('<div id="messageQuoteList" />').hide().appendTo(document.body)}}this._dialog.html(c);var a=$('<div class="formSubmit" />').appendTo(this._dialog);if(this._supportPaste){this._buttons.insert=$("<button>"+WCF.Language.get("wcf.message.quote.insertAllQuotes")+"</button>").click($.proxy(this._insertSelected,this)).appendTo(a)}this._buttons.remove=$("<button>"+WCF.Language.get("wcf.message.quote.removeAllQuotes")+"</button>").click($.proxy(this._removeSelected,this)).appendTo(a);this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.quote.manageQuotes")});this._dialog.wcfDialog("render");this._hasTemplate=true;var d=this._dialog.find(".jsInsertQuote");if(this._supportPaste){d.click($.proxy(this._insertQuote,this))}else{d.hide()}this._dialog.find("input.jsCheckbox").change($.proxy(this._changeButtons,this));if(this._removeOnSubmit.length){var b=this;this._dialog.find("input.jsRemoveQuote").each(function(f,e){var g=$(e).change($.proxy(this._change,this));if(WCF.inArray(g.parent("li").attr("data-quote-id"),b._removeOnSubmit)){g.attr("checked","checked")}})}},_changeButtons:function(){if(this._dialog.find("input.jsCheckbox:checked").length){if(this._supportPaste){this._buttons.insert.html(WCF.Language.get("wcf.message.quote.insertSelectedQuotes"))}this._buttons.remove.html(WCF.Language.get("wcf.message.quote.removeSelectedQuotes"))}else{if(this._supportPaste){this._buttons.insert.html(WCF.Language.get("wcf.message.quote.insertAllQuotes"))}this._buttons.remove.html(WCF.Language.get("wcf.message.quote.removeAllQuotes"))}},_change:function(c){var d=$(c.currentTarget);var b=d.parent("li").attr("data-quote-id");if(d.prop("checked")){this._removeOnSubmit.push(b)}else{for(var a in this._removeOnSubmit){if(this._removeOnSubmit[a]==b){delete this._removeOnSubmit[a];break}}}},_insertSelected:function(){var a=$(".jsQuickReply:eq(0)").data("__api");if(a&&!a.getContainer().is(":visible")){this._insertQuotes=false;a.click(null)}if(!this._dialog.find("input.jsCheckbox:checked").length){this._dialog.find("input.jsCheckbox").prop("checked","checked")}this._dialog.find("input.jsCheckbox:checked").each($.proxy(function(c,b){this._insertQuote(null,b)},this));this._dialog.wcfDialog("close")},_insertQuote:function(a,i){if(a!==null){var c=$(".jsQuickReply:eq(0)").data("__api");if(c&&!c.getContainer().is(":visible")){this._insertQuotes=false;c.click(null)}}var g=(a===null)?$(i).parents("li"):$(a.currentTarget).parents("li");var j=$.trim(g.children("div.jsFullQuote").text());var e=g.parents("article.message");j="[quote='"+e.attr("data-username")+"','"+e.data("link")+"']"+j+"[/quote]";var f=($.browser.mobile)?null:this._ckEditor.ckeditorGet();if(f!==null&&f.mode==="wysiwyg"){f.insertText(j+"\n\n")}else{var d=($.browser.mobile)?this._ckEditor:this._ckEditor.next(".cke_editor_text").find("textarea");var h=d.val();j+="\n\n";if(h.length==0){d.val(j)}else{var b=d.getCaret();d.val(h.substr(0,b)+j+h.substr(b))}}this._removeOnSubmit.push(g.attr("data-quote-id"));if(a!==null){this._dialog.wcfDialog("close")}},_removeSelected:function(){if(!this._dialog.find("input.jsCheckbox:checked").length){this._dialog.find("input.jsCheckbox").prop("checked","checked")}var b=[];this._dialog.find("input.jsCheckbox:checked").each(function(e,d){b.push($(d).parents("li").attr("data-quote-id"))});if(b.length){var c=[];for(var a in this._handlers){c.push(a)}this._proxy.setOption("data",{actionName:"remove",objectTypes:c,quoteIDs:b});this._proxy.sendRequest();this._dialog.wcfDialog("close")}},_submit:function(){if(this._supportPaste&&this._removeOnSubmit.length>0){var a=this._form.find(".formSubmit");for(var b in this._removeOnSubmit){$('<input type="hidden" name="__removeQuoteIDs[]" value="'+this._removeOnSubmit[b]+'" />').appendTo(a)}}},getQuotesMarkedForRemoval:function(){return this._removeOnSubmit},markQuotesForRemoval:function(){if(this._removeOnSubmit.length){this._proxy.setOption("data",{actionName:"markForRemoval",quoteIDs:this._removeOnSubmit});this._proxy.sendRequest()}},removeMarkedQuotes:function(){if(this._removeOnSubmit.length){this._proxy.setOption("data",{actionName:"removeMarkedQuotes"});this._proxy.sendRequest()}},countQuotes:function(){var b=[];for(var a in this._handlers){b.push(a)}this._proxy.setOption("data",{actionName:"count",objectTypes:b});this._proxy.sendRequest()},_success:function(c,d,b){if(c===null){return}if(c.count!==undefined){var a=(c.fullQuoteObjectIDs!==undefined)?c.fullQuoteObjectIDs:{};this.updateCount(c.count,a)}if(c.template!==undefined){if($.trim(c.template)==""){this.updateCount(0,{})}else{this.renderDialog(c.template)}}}});WCF.Message.Share={};WCF.Message.Share.Content=Class.extend({_cache:{},_dialog:null,init:function(){this._cache={};this._dialog=null;this._initLinks();WCF.DOMNodeInsertedHandler.addCallback("WCF.Message.Share.Content",$.proxy(this._initLinks,this))},_initLinks:function(){$("a.jsButtonShare").removeClass("jsButtonShare").click($.proxy(this._click,this))},_click:function(e){e.preventDefault();var a=$(e.currentTarget);var b=a.prop("href");var d=(a.data("linkTitle")?a.data("linkTitle"):b);var c=b.hashCode();if(this._cache[c]===undefined){var g=false;if(this._dialog===null){this._dialog=$("<div />").hide().appendTo(document.body);g=true}else{this._dialog.empty()}var f=$('<fieldset><legend><label for="__sharePermalink">'+WCF.Language.get("wcf.message.share.permalink")+"</label></legend></fieldset>").appendTo(this._dialog);$('<input type="text" id="__sharePermalink" class="long" readonly="readonly" />').attr("value",b).appendTo(f);var f=$('<fieldset><legend><label for="__sharePermalinkBBCode">'+WCF.Language.get("wcf.message.share.permalink.bbcode")+"</label></legend></fieldset>").appendTo(this._dialog);$('<input type="text" id="__sharePermalinkBBCode" class="long" readonly="readonly" />').attr("value","[url='"+b+"']"+d+"[/url]").appendTo(f);var f=$('<fieldset><legend><label for="__sharePermalinkHTML">'+WCF.Language.get("wcf.message.share.permalink.html")+"</label></legend></fieldset>").appendTo(this._dialog);$('<input type="text" id="__sharePermalinkHTML" class="long" readonly="readonly" />').attr("value",'<a href="'+b+'">'+WCF.String.escapeHTML(d)+"</a>").appendTo(f);this._cache[c]=this._dialog.html();if(g){this._dialog.wcfDialog({title:WCF.Language.get("wcf.message.share")})}else{this._dialog.wcfDialog("open")}}else{this._dialog.html(this._cache[c]).wcfDialog("open")}this._dialog.find("input").click(function(){$(this).select()})}});WCF.Message.Share.Page=Class.extend({_ui:{},_pageDescription:"",_pageURL:"",init:function(a){this._pageDescription=encodeURIComponent($('meta[property="og:description"]').prop("content"));this._pageURL=encodeURIComponent($('meta[property="og:url"]').prop("content"));var b=$(".messageShareButtons");this._ui={facebook:b.find(".jsShareFacebook"),google:b.find(".jsShareGoogle"),reddit:b.find(".jsShareReddit"),twitter:b.find(".jsShareTwitter")};this._ui.facebook.children("a").click($.proxy(this._shareFacebook,this));this._ui.google.children("a").click($.proxy(this._shareGoogle,this));this._ui.reddit.children("a").click($.proxy(this._shareReddit,this));this._ui.twitter.children("a").click($.proxy(this._shareTwitter,this));if(a===true){this._fetchFacebook();this._fetchTwitter();this._fetchReddit()}},_share:function(b,a){window.open(a.replace(/{pageURL}/,this._pageURL).replace(/{text}/,this._pageDescription),"height=600,width=600")},_shareFacebook:function(){this._share("facebook","https://www.facebook.com/sharer.php?u={pageURL}&t={text}")},_shareGoogle:function(){this._share("google","https://plus.google.com/share?url={pageURL}")},_shareReddit:function(){this._share("reddit","https://ssl.reddit.com/submit?url={pageURL}")},_shareTwitter:function(){this._share("twitter","https://twitter.com/share?url={pageURL}&text={text}")},_fetchCount:function(b,d,c){var a={autoSend:true,dataType:"jsonp",showLoadingOverlay:false,success:d,suppressErrors:true,type:"GET",url:b.replace(/{pageURL}/,this._pageURL)};if(c){a.jsonp=c}new WCF.Action.Proxy(a)},_fetchFacebook:function(){this._fetchCount("https://graph.facebook.com/?id={pageURL}",$.proxy(function(a){if(a.shares){this._ui.facebook.children("span.badge").show().text(a.shares)}},this))},_fetchTwitter:function(){this._fetchCount("http://urls.api.twitter.com/1/urls/count.json?url={pageURL}",$.proxy(function(a){if(a.count){this._ui.twitter.children("span.badge").show().text(a.count)}},this))},_fetchReddit:function(){this._fetchCount("http://www.reddit.com/api/info.json?url={pageURL}",$.proxy(function(a){if(a.data.children.length){this._ui.reddit.children("span.badge").show().text(a.data.children[0].data.score)}},this),"jsonp")}});
\ No newline at end of file
--- /dev/null
+<?php
+namespace wcf\action;
+use wcf\system\exception\SystemException;
+use wcf\system\exception\UserInputException;
+use wcf\system\message\quote\MessageQuoteManager;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+use wcf\util\JSON;
+use wcf\util\StringUtil;
+
+/**
+ * Handles message quotes.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage action
+ * @category Community Framework
+ */
+class MessageQuoteAction extends AJAXProxyAction {
+ /**
+ * list of quote ids
+ * @var array<string>
+ */
+ public $quoteIDs = array();
+
+ /**
+ * list of object types
+ * @var array<string>
+ */
+ public $objectTypes = array();
+
+ /**
+ * @see wcf\action\IAction::readParameters()
+ */
+ public function readParameters() {
+ AbstractSecureAction::readParameters();
+
+ if (isset($_POST['actionName'])) $this->actionName = StringUtil::trim($_POST['actionName']);
+ if (isset($_POST['objectTypes']) && is_array($_POST['objectTypes'])) $this->objectTypes = ArrayUtil::trim($_POST['objectTypes']);
+ if (isset($_POST['quoteIDs'])) {
+ $this->quoteIDs = ArrayUtil::trim($_POST['quoteIDs']);
+
+ // validate quote ids
+ foreach ($this->quoteIDs as $key => $quoteID) {
+ if (MessageQuoteManager::getInstance()->getQuote($quoteID) === null) {
+ unset($this->quoteIDs[$key]);
+ }
+ }
+ }
+ }
+
+ /**
+ * @see wcf\action\IAction::execute()
+ */
+ public function execute() {
+ AbstractAction::execute();
+
+ $returnValues = null;
+ switch ($this->actionName) {
+ case 'count':
+ $returnValues = array(
+ 'count' => $this->count(),
+ 'fullQuoteObjectIDs' => $this->getFullQuoteObjectIDs()
+ );
+ break;
+
+ case 'getQuotes':
+ $returnValues = array('template' => $this->getQuotes());
+ break;
+
+ case 'markForRemoval':
+ $this->markForRemoval();
+ break;
+
+ case 'remove':
+ $returnValues = array(
+ 'count' => $this->remove(),
+ 'fullQuoteObjectIDs' => $this->getFullQuoteObjectIDs()
+ );
+ break;
+
+ case 'removeMarkedQuotes':
+ $returnValues = array(
+ 'count' => $this->removeMarkedQuotes(),
+ 'fullQuoteObjectIDs' => $this->getFullQuoteObjectIDs()
+ );
+ break;
+
+ default:
+ throw new SystemException("Unknown action '".$this->actionName."'");
+ break;
+ }
+
+ $this->executed();
+
+ // force session update
+ WCF::getSession()->update();
+ WCF::getSession()->disableUpdate();
+
+ if ($returnValues !== null) {
+ // send JSON-encoded response
+ header('Content-type: application/json');
+ echo JSON::encode($returnValues);
+ }
+
+ exit;
+ }
+
+ /**
+ * Returns the count of stored quotes.
+ *
+ * @return integer
+ */
+ protected function count() {
+ return MessageQuoteManager::getInstance()->countQuotes();
+ }
+
+ /**
+ * Returns the quote list template.
+ *
+ * @return string
+ */
+ protected function getQuotes() {
+ $supportPaste = (isset($_POST['supportPaste'])) ? (bool)$_POST['supportPaste'] : false;
+
+ return MessageQuoteManager::getInstance()->getQuotes($supportPaste);
+ }
+
+ /**
+ * Marks quotes for removal.
+ */
+ protected function markForRemoval() {
+ if (!empty($this->quoteIDs)) {
+ MessageQuoteManager::getInstance()->markQuotesForRemoval($this->quoteIDs);
+ }
+ }
+
+ /**
+ * Removes a list of quotes from storage and returns the remaining count.
+ *
+ * @return integer
+ */
+ protected function remove() {
+ if (empty($this->quoteIDs)) {
+ throw new UserInputException('quoteIDs');
+ }
+
+ foreach ($this->quoteIDs as $quoteID) {
+ if (!MessageQuoteManager::getInstance()->removeQuote($quoteID)) {
+ throw new SystemException("Unable to remove quote identified by '".$quoteID."'");
+ }
+ }
+
+ return $this->count();
+ }
+
+ /**
+ * Removes all quotes marked for removal and returns the remaining count.
+ *
+ * @return integer
+ */
+ protected function removeMarkedQuotes() {
+ MessageQuoteManager::getInstance()->removeMarkedQuotes();
+
+ return $this->count();
+ }
+
+ /**
+ * Returns a list of full quotes by object ids for given object types.
+ *
+ * @return array<array>
+ */
+ protected function getFullQuoteObjectIDs() {
+ if (empty($this->objectTypes)) {
+ throw new UserInputException('objectTypes');
+ }
+
+ try {
+ return MessageQuoteManager::getInstance()->getFullQuoteObjectIDs($this->objectTypes);
+ }
+ catch (SystemException $e) {
+ throw new UserInputException('objectTypes');
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Default interface for actions implementing quick reply with extended mode.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage data
+ * @category Community Framework
+ */
+interface IExtendedMessageQuickReplyAction extends IMessageQuickReplyAction {
+ /**
+ * Saves message and jumps to extended mode.
+ *
+ * @return array
+ */
+ public function jumpToExtended();
+
+ /**
+ * Validates parameters to jump to extended mode.
+ */
+ public function validateJumpToExtended();
+}
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Every feed entry should implement this interface.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage data
+ * @category Community Framework
+ */
+interface IFeedEntry extends IMessage {
+ /**
+ * Returns the number of comments.
+ *
+ * @return integer
+ */
+ public function getComments();
+
+ /**
+ * Returns a list of category names.
+ *
+ * @return array<string>
+ */
+ public function getCategories();
+}
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Default interface for message database objects.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage data
+ * @category Community Framework
+ */
+interface IMessage extends IUserContent {
+ /**
+ * Returns a simplified message (only inline codes), truncated to 255 characters by default.
+ *
+ * @param integer $maxLength
+ * @return string
+ */
+ public function getExcerpt($maxLength = 255);
+
+ /**
+ * Returns formatted message text.
+ *
+ * @return string
+ */
+ public function getFormattedMessage();
+
+ /**
+ * Returns message text.
+ *
+ * @return string
+ */
+ public function getMessage();
+
+ /**
+ * Returns true, if message is visible for current user.
+ *
+ * @return boolean
+ */
+ public function isVisible();
+
+ /**
+ * Returns formatted message text.
+ *
+ * @see wcf\data\IMessage::getFormattedMessage()
+ */
+ public function __toString();
+}
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Default interface for actions implementing message inline editing.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage data
+ * @category Community Framework
+ */
+interface IMessageInlineEditorAction {
+ /**
+ * Provides WYSIWYG editor for message inline editing.
+ *
+ * @return array
+ */
+ public function beginEdit();
+
+ /**
+ * Saves changes made to a message.
+ *
+ * @return array
+ */
+ public function save();
+
+ /**
+ * Validates parameters to begin message inline editing.
+ */
+ public function validateBeginEdit();
+
+ /**
+ * Validates parameters to save changes made to a message.
+ */
+ public function validateSave();
+}
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Default interface for actions implementing quick reply.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage data
+ * @category Community Framework
+ */
+interface IMessageQuickReplyAction {
+ /**
+ * Creates a new message object.
+ *
+ * @return wcf\data\DatabaseObject
+ */
+ public function create();
+
+ /**
+ * Returns a message list object.
+ *
+ * @param wcf\data\DatabaseObject $container
+ * @param integer $lastMessageTime
+ * @return wcf\data\DatabaseObjectList
+ */
+ public function getMessageList(DatabaseObject $container, $lastMessageTime);
+
+ /**
+ * Returns page no for given container object.
+ *
+ * @param wcf\data\DatabaseObject $container
+ * @return array
+ */
+ public function getPageNo(DatabaseObject $container);
+
+ /**
+ * Returns the redirect url.
+ *
+ * @param wcf\data\DatabaseObject $container
+ * @param wcf\data\DatabaseObject $message
+ * @return string
+ */
+ public function getRedirectUrl(DatabaseObject $container, DatabaseObject $message);
+
+ /**
+ * Validates the message.
+ *
+ * @param wcf\data\DatabaseObject $container
+ * @param string $message
+ */
+ public function validateMessage(DatabaseObject $container, $message);
+
+ /**
+ * Creates a new message and returns it.
+ *
+ * @return array
+ */
+ public function quickReply();
+
+ /**
+ * Validates the container object for quick reply.
+ *
+ * @param wcf\data\DatabaseObject $container
+ */
+ public function validateContainer(DatabaseObject $container);
+
+ /**
+ * Validates parameters for quick reply.
+ */
+ public function validateQuickReply();
+}
--- /dev/null
+<?php
+namespace wcf\data;
+
+/**
+ * Default interface for message action classes supporting quotes.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage data
+ * @category Community Framework
+ */
+interface IMessageQuoteAction {
+ /**
+ * Validates parameters to return a parsed template of all associated quotes.
+ */
+ public function validateGetRenderedQuotes();
+
+ /**
+ * Returns the parsed template for all associated quotes.
+ *
+ * @return array
+ */
+ public function getRenderedQuotes();
+
+ /**
+ * Validates parameters to quote an entire message.
+ */
+ public function validateSaveFullQuote();
+
+ /**
+ * Quotes an entire message.
+ *
+ * @return array
+ */
+ public function saveFullQuote();
+
+ /**
+ * Validates parameters to save a quote.
+ */
+ public function validateSaveQuote();
+
+ /**
+ * Saves the quote message and returns the number of stored quotes.
+ *
+ * @return array
+ */
+ public function saveQuote();
+}
--- /dev/null
+<?php
+namespace wcf\data\bbcode;
+use wcf\data\attachment\GroupedAttachmentList;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\bbcode\AttachmentBBCode;
+use wcf\system\bbcode\MessageParser;
+use wcf\system\bbcode\PreParser;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Provides a default message preview action.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage data.message
+ * @category Community Framework
+ */
+class MessagePreviewAction extends BBCodeAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess
+ */
+ protected $allowGuestAccess = array('getMessagePreview');
+
+ /**
+ * Validates parameters for message preview.
+ */
+ public function validateGetMessagePreview() {
+ if (!isset($this->parameters['data']['message'])) {
+ throw new UserInputException('message');
+ }
+
+ if (!isset($this->parameters['options'])) {
+ throw new UserInputException('options');
+ }
+ }
+
+ /**
+ * Returns a rendered message preview.
+ *
+ * @return array
+ */
+ public function getMessagePreview() {
+ // get options
+ $enableBBCodes = (isset($this->parameters['options']['enableBBCodes'])) ? 1 : 0;
+ $enableHtml = (isset($this->parameters['options']['enableHtml'])) ? 1 : 0;
+ $enableSmilies = (isset($this->parameters['options']['enableSmilies'])) ? 1 : 0;
+ $preParse = (isset($this->parameters['options']['preParse'])) ? 1 : 0;
+
+ // validate permissions for options
+ if ($enableBBCodes && !WCF::getSession()->getPermission('user.message.canUseBBCodes')) $enableBBCodes = 0;
+ if ($enableHtml && !WCF::getSession()->getPermission('user.message.canUseHtml')) $enableHtml = 0;
+ if ($enableSmilies && !WCF::getSession()->getPermission('user.message.canUseSmilies')) $enableSmilies = 0;
+
+ // get attachments
+ if (!empty($this->parameters['attachmentObjectType'])) {
+ $attachmentList = new GroupedAttachmentList($this->parameters['attachmentObjectType']);
+ if (!empty($this->parameters['attachmentObjectID'])) {
+ $attachmentList->getConditionBuilder()->add('attachment.objectID = ?', array($this->parameters['attachmentObjectID']));
+ AttachmentBBCode::setObjectID($this->parameters['attachmentObjectID']);
+
+ $objectType = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.attachment.objectType', $this->parameters['attachmentObjectType']);
+ $processor = $objectType->getProcessor();
+ if (!$processor->canDownload($this->parameters['attachmentObjectID']) && !$processor->canViewPreview($this->parameters['attachmentObjectID'])) {
+ if (WCF::getUser()->userID) {
+ $attachmentList->getConditionBuilder()->add('attachment.userID = ?', array(WCF::getUser()->userID));
+ }
+ else {
+ $attachmentList->getConditionBuilder()->add('attachment.userID IS NULL');
+ }
+ }
+ }
+ else {
+ $attachmentList->getConditionBuilder()->add('attachment.tmpHash = ?', array($this->parameters['tmpHash']));
+
+ if (WCF::getUser()->userID) {
+ $attachmentList->getConditionBuilder()->add('attachment.userID = ?', array(WCF::getUser()->userID));
+ }
+ else {
+ $attachmentList->getConditionBuilder()->add('attachment.userID IS NULL');
+ }
+ }
+
+ $attachmentList->readObjects();
+ AttachmentBBCode::setAttachmentList($attachmentList);
+ }
+
+ // get message
+ $message = StringUtil::trim($this->parameters['data']['message']);
+
+ // parse URLs
+ if ($preParse && $enableBBCodes) {
+ $message = PreParser::getInstance()->parse($message);
+ }
+
+ // parse message
+ $preview = MessageParser::getInstance()->parse($message, $enableSmilies, $enableHtml, $enableBBCodes, false);
+
+ return array(
+ 'message' => $preview
+ );
+ }
+}
--- /dev/null
+<?php
+namespace wcf\data\smiley\category;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+
+/**
+ * Executes smiley category-related actions.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.bbcode
+ * @subpackage data.smiley.category
+ * @category Community Framework
+ */
+class SmileyCategoryAction extends AbstractDatabaseObjectAction {
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$className
+ */
+ protected $className = 'wcf\data\category\CategoryEditor';
+
+ /**
+ * @see wcf\data\AbstractDatabaseObjectAction::$allowGuestAccess
+ */
+ protected $allowGuestAccess = array('getSmilies');
+
+ /**
+ * active smiley category
+ * @var wcf\data\smiley\category\SmileyCategory
+ */
+ public $smileyCategory = null;
+
+ /**
+ * Validates smiley category id.
+ */
+ public function validateGetSmilies() {
+ $this->smileyCategory = new SmileyCategory($this->getSingleObject()->getDecoratedObject());
+
+ if ($this->smileyCategory->isDisabled) throw new IllegalLinkException();
+ }
+
+ /**
+ * Returns parsed template for smiley category's smilies.
+ *
+ * @return array
+ */
+ public function getSmilies() {
+ $this->smileyCategory->loadSmilies();
+
+ WCF::getTPL()->assign(array(
+ 'smilies' => $this->smileyCategory
+ ));
+
+ return array(
+ 'smileyCategoryID' => $this->smileyCategory->categoryID,
+ 'template' => WCF::getTPL()->fetch('__messageFormSmilies')
+ );
+ }
+}
--- /dev/null
+<?php
+namespace wcf\form;
+use wcf\data\smiley\SmileyCache;
+use wcf\system\attachment\AttachmentHandler;
+use wcf\system\bbcode\BBCodeParser;
+use wcf\system\bbcode\PreParser;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\LanguageFactory;
+use wcf\system\message\censorship\Censorship;
+use wcf\system\WCF;
+use wcf\util\MessageUtil;
+use wcf\util\StringUtil;
+
+/**
+ * MessageForm is an abstract form implementation for a message with optional captcha suppport.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage form
+ * @category Community Framework
+ */
+abstract class MessageForm extends RecaptchaForm {
+ /**
+ * name of the permission which contains the allowed BBCodes
+ * @var string
+ */
+ public $allowedBBCodesPermission = 'user.message.allowedBBCodes';
+
+ /**
+ * attachment handler
+ * @var wcf\system\attachment\AttachmentHandler
+ */
+ public $attachmentHandler = null;
+
+ /**
+ * object id for attachments
+ * @var integer
+ */
+ public $attachmentObjectID = 0;
+
+ /**
+ * object type for attachments, if left blank, attachment support is disabled
+ * @var integer
+ */
+ public $attachmentObjectType = '';
+
+ /**
+ * parent object id for attachments
+ * @var integer
+ */
+ public $attachmentParentObjectID = 0;
+
+ /**
+ * list of available content languages
+ * @var array<wcf\data\language\Language>
+ */
+ public $availableContentLanguages = array();
+
+ /**
+ * list of default smilies
+ * @var array<wcf\data\smiley\Smiley>
+ */
+ public $defaultSmilies = array();
+
+ /**
+ * enables bbcodes
+ * @var boolean
+ */
+ public $enableBBCodes = 1;
+
+ /**
+ * enables html
+ * @var boolean
+ */
+ public $enableHtml = 0;
+
+ /**
+ * enables multilingualism
+ * @var boolean
+ */
+ public $enableMultilingualism = false;
+
+ /**
+ * enables smilies
+ * @var boolean
+ */
+ public $enableSmilies = 1;
+
+ /**
+ * content language id
+ * @var integer
+ */
+ public $languageID = null;
+
+ /**
+ * maximum text length
+ * @var integer
+ */
+ public $maxTextLength = 0;
+
+ /**
+ * pre parses the message
+ * @var boolean
+ */
+ public $preParse = 1;
+
+ /**
+ * required permission to use BBCodes
+ * @var boolean
+ */
+ public $permissionCanUseBBCodes = 'user.message.canUseBBCodes';
+
+ /**
+ * required permission to use HTML
+ * @var boolean
+ */
+ public $permissionCanUseHtml = 'user.message.canUseHtml';
+
+ /**
+ * required permission to use smilies
+ * @var boolean
+ */
+ public $permissionCanUseSmilies = 'user.message.canUseSmilies';
+
+ /**
+ * shows the signature
+ * @var boolean
+ */
+ public $showSignature = 0;
+
+ /**
+ * enables the 'showSignature' setting
+ * @var boolean
+ */
+ public $showSignatureSetting = 1;
+
+ /**
+ * list of smiley categories
+ * @var array<wcf\data\smiley\category\SmileyCategory>
+ */
+ public $smileyCategories = array();
+
+ /**
+ * message subject
+ * @var string
+ */
+ public $subject = '';
+
+ /**
+ * message text
+ * @var string
+ */
+ public $text = '';
+
+ /**
+ * temp hash
+ * @var string
+ */
+ public $tmpHash = '';
+
+ /**
+ * @see wcf\form\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['tmpHash'])) {
+ $this->tmpHash = $_REQUEST['tmpHash'];
+ }
+ if (empty($this->tmpHash)) {
+ $this->tmpHash = StringUtil::getRandomID();
+ }
+
+ if ($this->enableMultilingualism) {
+ $this->availableContentLanguages = LanguageFactory::getInstance()->getContentLanguages();
+ if (WCF::getUser()->userID) {
+ foreach ($this->availableContentLanguages as $key => $value) {
+ if (!in_array($key, WCF::getUser()->getLanguageIDs())) unset($this->availableContentLanguages[$key]);
+ }
+ }
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::readFormParameters()
+ */
+ public function readFormParameters() {
+ parent::readFormParameters();
+
+ if (isset($_POST['subject'])) $this->subject = StringUtil::trim($_POST['subject']);
+ if (isset($_POST['text'])) $this->text = MessageUtil::stripCrap(StringUtil::trim($_POST['text']));
+
+ // settings
+ $this->enableSmilies = $this->enableHtml = $this->enableBBCodes = $this->preParse = $this->showSignature = 0;
+ if (isset($_POST['preParse'])) $this->preParse = intval($_POST['preParse']);
+ if (isset($_POST['enableSmilies']) && WCF::getSession()->getPermission($this->permissionCanUseSmilies)) $this->enableSmilies = intval($_POST['enableSmilies']);
+ if (isset($_POST['enableHtml']) && WCF::getSession()->getPermission($this->permissionCanUseHtml)) $this->enableHtml = intval($_POST['enableHtml']);
+ if (isset($_POST['enableBBCodes']) && WCF::getSession()->getPermission($this->permissionCanUseBBCodes)) $this->enableBBCodes = intval($_POST['enableBBCodes']);
+ if (isset($_POST['showSignature'])) $this->showSignature = intval($_POST['showSignature']);
+
+ // multilingualism
+ if (isset($_POST['languageID'])) $this->languageID = intval($_POST['languageID']);
+ }
+
+ /**
+ * @see wcf\form\IForm::validate()
+ */
+ public function validate() {
+ // subject
+ $this->validateSubject();
+
+ // text
+ $this->validateText();
+
+ // multilingualism
+ $this->validateContentLanguage();
+
+ parent::validate();
+ }
+
+ /**
+ * Validates the message subject.
+ */
+ protected function validateSubject() {
+ if (empty($this->subject)) {
+ throw new UserInputException('subject');
+ }
+
+ if (StringUtil::length($this->subject) > 255) {
+ throw new UserInputException('subject', 'tooLong');
+ }
+
+ // search for censored words
+ if (ENABLE_CENSORSHIP) {
+ $result = Censorship::getInstance()->test($this->subject);
+ if ($result) {
+ WCF::getTPL()->assign('censoredWords', $result);
+ throw new UserInputException('subject', 'censoredWordsFound');
+ }
+ }
+ }
+
+ /**
+ * Validates the message text.
+ */
+ protected function validateText() {
+ if (empty($this->text)) {
+ throw new UserInputException('text');
+ }
+
+ // check text length
+ if ($this->maxTextLength != 0 && StringUtil::length($this->text) > $this->maxTextLength) {
+ throw new UserInputException('text', 'tooLong');
+ }
+
+ if ($this->enableBBCodes && $this->allowedBBCodesPermission) {
+ $disallowedBBCodes = BBCodeParser::getInstance()->validateBBCodes($this->text, explode(',', WCF::getSession()->getPermission($this->allowedBBCodesPermission)));
+ if (!empty($disallowedBBCodes)) {
+ WCF::getTPL()->assign('disallowedBBCodes', $disallowedBBCodes);
+ throw new UserInputException('text', 'disallowedBBCodes');
+ }
+ }
+
+ // search for censored words
+ if (ENABLE_CENSORSHIP) {
+ $result = Censorship::getInstance()->test($this->text);
+ if ($result) {
+ WCF::getTPL()->assign('censoredWords', $result);
+ throw new UserInputException('text', 'censoredWordsFound');
+ }
+ }
+ }
+
+ /**
+ * Validates content language id.
+ */
+ protected function validateContentLanguage() {
+ if (!$this->languageID || !$this->enableMultilingualism || empty($this->availableContentLanguages)) {
+ $this->languageID = null;
+ return;
+ }
+
+ if (!isset($this->availableContentLanguages[$this->languageID])) {
+ throw new UserInputException('languageID', 'notValid');
+ }
+ }
+
+ /**
+ * @see wcf\form\IForm::save()
+ */
+ public function save() {
+ parent::save();
+
+ // parse URLs
+ if ($this->preParse == 1) {
+ // BBCodes are enabled
+ if ($this->enableBBCodes) {
+ if ($this->allowedBBCodesPermission) {
+ $this->text = PreParser::getInstance()->parse($this->text, explode(',', WCF::getSession()->getPermission($this->allowedBBCodesPermission)));
+ }
+ else {
+ $this->text = PreParser::getInstance()->parse($this->text);
+ }
+ }
+ // BBCodes are disabled, thus no allowed BBCodes
+ else {
+ $this->text = PreParser::getInstance()->parse($this->text, array());
+ }
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::readData()
+ */
+ public function readData() {
+ // get attachments
+ if (MODULE_ATTACHMENT && $this->attachmentObjectType) {
+ $this->attachmentHandler = new AttachmentHandler($this->attachmentObjectType, $this->attachmentObjectID, $this->tmpHash, $this->attachmentParentObjectID);
+ }
+
+ if (empty($_POST)) {
+ $this->enableBBCodes = (ENABLE_BBCODES_DEFAULT_VALUE && WCF::getSession()->getPermission($this->permissionCanUseBBCodes)) ? 1 : 0;
+ $this->enableHtml = (ENABLE_HTML_DEFAULT_VALUE && WCF::getSession()->getPermission($this->permissionCanUseHtml)) ? 1 : 0;
+ $this->enableSmilies = (ENABLE_SMILIES_DEFAULT_VALUE && WCF::getSession()->getPermission($this->permissionCanUseSmilies)) ? 1 : 0;
+ $this->preParse = PRE_PARSE_DEFAULT_VALUE;
+ $this->showSignature = SHOW_SIGNATURE_DEFAULT_VALUE;
+ $this->languageID = WCF::getLanguage()->languageID;
+ }
+
+ parent::readData();
+
+ // get default smilies
+ if (MODULE_SMILEY) {
+ $this->smileyCategories = SmileyCache::getInstance()->getCategories();
+ foreach ($this->smileyCategories as $index => $category) {
+ $category->loadSmilies();
+
+ // remove empty categories
+ if (!count($category) || $category->isDisabled) {
+ unset($this->smileyCategories[$index]);
+ }
+ }
+
+ $firstCategory = reset($this->smileyCategories);
+ if ($firstCategory) {
+ $this->defaultSmilies = SmileyCache::getInstance()->getCategorySmilies($firstCategory->categoryID ?: null);
+ }
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::assignVariables();
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'attachmentHandler' => $this->attachmentHandler,
+ 'attachmentObjectID' => $this->attachmentObjectID,
+ 'attachmentObjectType' => $this->attachmentObjectType,
+ 'attachmentParentObjectID' => $this->attachmentParentObjectID,
+ 'availableContentLanguages' => $this->availableContentLanguages,
+ 'defaultSmilies' => $this->defaultSmilies,
+ 'enableBBCodes' => $this->enableBBCodes,
+ 'enableHtml' => $this->enableHtml,
+ 'enableSmilies' => $this->enableSmilies,
+ 'languageID' => ($this->languageID ?: 0),
+ 'maxTextLength' => $this->maxTextLength,
+ 'preParse' => $this->preParse,
+ 'showSignature' => $this->showSignature,
+ 'showSignatureSetting' => $this->showSignatureSetting,
+ 'smileyCategories' => $this->smileyCategories,
+ 'subject' => $this->subject,
+ 'text' => $this->text,
+ 'tmpHash' => $this->tmpHash
+ ));
+
+ if ($this->allowedBBCodesPermission) {
+ WCF::getTPL()->assign('allowedBBCodes', explode(',', WCF::getSession()->getPermission($this->allowedBBCodesPermission)));
+ }
+ }
+}
--- /dev/null
+<?php
+namespace wcf\page;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+
+/**
+ * Generates RSS 2-Feeds.
+ *
+ * @author Tim Duesterhus
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage page
+ * @category Community Framework
+ */
+abstract class AbstractFeedPage extends AbstractAuthedPage {
+ /**
+ * @see wcf\page\AbstractPage::$templateName
+ */
+ public $templateName = 'rssFeed';
+
+ /**
+ * application name
+ * @var string
+ */
+ public $application = 'wcf';
+
+ /**
+ * @see wcf\page\AbstractPage::$useTemplate
+ */
+ public $useTemplate = false;
+
+ /**
+ * parsed contents of $_REQUEST['id']
+ * @var array<integer>
+ */
+ public $objectIDs = array();
+
+ /**
+ * list of feed-entries for the current page
+ * @var wcf\data\DatabaseObjectList
+ */
+ public $items = null;
+
+ /**
+ * feed title
+ * @var string
+ */
+ public $title = '';
+
+ /**
+ * @see wcf\page\IPage::assignVariables()
+ */
+ public function assignVariables() {
+ parent::assignVariables();
+
+ WCF::getTPL()->assign(array(
+ 'items' => $this->items,
+ 'title' => $this->title
+ ));
+ }
+
+ /**
+ * @see wcf\page\IPage::readParameters()
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (isset($_REQUEST['id'])) {
+ if (is_array($_REQUEST['id'])) {
+ // ?id[]=1337&id[]=9001
+ $this->objectIDs = ArrayUtil::toIntegerArray($_REQUEST['id']);
+ }
+ else {
+ // ?id=1337 or ?id=1337,9001
+ $this->objectIDs = ArrayUtil::toIntegerArray(explode(',', $_REQUEST['id']));
+ }
+ }
+ }
+
+ /**
+ * @see wcf\page\IPage::show()
+ */
+ public function show() {
+ parent::show();
+
+ // set correct content-type
+ @header('Content-Type: application/rss+xml');
+
+ // show template
+ WCF::getTPL()->display($this->templateName, $this->application, false);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\bbcode;
+use wcf\data\attachment\GroupedAttachmentList;
+use wcf\system\request\LinkHandler;
+use wcf\util\StringUtil;
+
+/**
+ * Parses the [attach] bbcode tag.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage system.bbcode
+ * @category Community Framework
+ */
+class AttachmentBBCode extends AbstractBBCode {
+ /**
+ * list of attachments
+ * @var wcf\data\attachment\GroupedAttachmentList
+ */
+ protected static $attachmentList = null;
+
+ /**
+ * active object id
+ * @var integer
+ */
+ protected static $objectID = 0;
+
+ /**
+ * @see wcf\system\bbcode\IBBCode::getParsedTag()
+ */
+ public function getParsedTag(array $openingTag, $content, array $closingTag, BBCodeParser $parser) {
+ // get attachment id
+ $attachmentID = 0;
+ if (isset($openingTag['attributes'][0])) {
+ $attachmentID = $openingTag['attributes'][0];
+ }
+
+ // get attachment for active object
+ $attachments = array();
+ if (self::$attachmentList !== null) {
+ $attachments = self::$attachmentList->getGroupedObjects(self::$objectID);
+ }
+
+ if (isset($attachments[$attachmentID])) {
+ $attachment = $attachments[$attachmentID];
+
+ // mark attachment as embedded
+ $attachment->markAsEmbedded();
+
+ if ($attachment->showAsImage() && $parser->getOutputType() == 'text/html') {
+ // image
+ $linkParameters = array(
+ 'object' => $attachment
+ );
+ if ($attachment->hasThumbnail()) {
+ $linkParameters['thumbnail'] = 1;
+ }
+
+ // get alignment
+ $alignment = (isset($openingTag['attributes'][1]) ? $openingTag['attributes'][1] : '');
+ $result = '<img src="'.StringUtil::encodeHTML(LinkHandler::getInstance()->getLink('Attachment', $linkParameters)).'"'.(!$attachment->hasThumbnail() ? ' class="jsResizeImage"' : '').' style="width: '.($attachment->hasThumbnail() ? $attachment->thumbnailWidth : $attachment->width).'px; height: '.($attachment->hasThumbnail() ? $attachment->thumbnailHeight: $attachment->height).'px;'.(!empty($alignment) ? ' float:' . ($alignment == 'left' ? 'left' : 'right') . '; margin: ' . ($alignment == 'left' ? '0 15px 7px 0' : '0 0 7px 15px' ) : '').'" alt="" />';
+ if ($attachment->hasThumbnail() && $attachment->canDownload()) {
+ $result = '<a href="'.StringUtil::encodeHTML(LinkHandler::getInstance()->getLink('Attachment', array('object' => $attachment))).'" title="'.StringUtil::encodeHTML($attachment->filename).'" class="jsImageViewer">'.$result.'</a>';
+ }
+ return $result;
+ }
+ else {
+ // file
+ return StringUtil::getAnchorTag(LinkHandler::getInstance()->getLink('Attachment', array(
+ 'object' => $attachment
+ )), ((!empty($content) && $content != $attachmentID) ? $content : $attachment->filename));
+ }
+ }
+
+ // fallback
+ return StringUtil::getAnchorTag(LinkHandler::getInstance()->getLink('Attachment', array(
+ 'id' => $attachmentID
+ )));
+ }
+
+ /**
+ * Sets the attachment list.
+ *
+ * @param wcf\data\attachment\GroupedAttachmentList $attachments
+ */
+ public static function setAttachmentList(GroupedAttachmentList $attachmentList) {
+ self::$attachmentList = $attachmentList;
+ }
+
+ /**
+ * Sets the active object id.
+ *
+ * @param integer $objectID
+ */
+ public static function setObjectID($objectID) {
+ self::$objectID = $objectID;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\message;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\data\IMessage;
+use wcf\data\IMessageQuickReplyAction;
+use wcf\system\bbcode\PreParser;
+use wcf\system\event\EventHandler;
+use wcf\system\exception\SystemException;
+use wcf\system\exception\UserInputException;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+use wcf\util\ClassUtil;
+
+/**
+ * Manages quick replies and stored messages.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage system.message
+ * @category Community Framework
+ */
+class QuickReplyManager extends SingletonFactory {
+ /**
+ * list of allowed bbcodes
+ * @var array<string>
+ */
+ public $allowedBBodes = null;
+
+ /**
+ * container object
+ * @var wcf\data\DatabaseObject
+ */
+ public $container = null;
+
+ /**
+ * object id
+ * @var integer
+ */
+ public $objectID = 0;
+
+ /**
+ * object type
+ * @var string
+ */
+ public $type = '';
+
+ /**
+ * Returns a stored message from session.
+ *
+ * @param string $type
+ * @param integer $objectID
+ * @return string
+ */
+ public function getMessage($type, $objectID) {
+ $this->type = $type;
+ $this->objectID = $objectID;
+
+ // allow manipulation before fetching data
+ EventHandler::getInstance()->fireAction($this, 'getMessage');
+
+ $message = WCF::getSession()->getVar('quickReply-'.$this->type.'-'.$this->objectID);
+ return ($message === null ? '' : $message);
+ }
+
+ /**
+ * Stores a message in session.
+ *
+ * @param string $type
+ * @param integer $objectID
+ * @param string $message
+ */
+ public function setMessage($type, $objectID, $message) {
+ WCF::getSession()->register('quickReply-'.$type.'-'.$objectID, $message);
+ }
+
+ /**
+ * Removes a stored message from session.
+ *
+ * @param string $type
+ * @param integer $objectID
+ */
+ public function removeMessage($type, $objectID) {
+ WCF::getSession()->unregister('quickReply-'.$this->type.'-'.$objectID);
+ }
+
+ /**
+ * Sets the allowed bbcodes.
+ *
+ * @param array<string> $allowedBBCodes
+ */
+ public function setAllowedBBCodes(array $allowedBBCodes = null) {
+ $this->allowedBBodes = $allowedBBCodes;
+ }
+
+ /**
+ * Validates parameters for current request.
+ *
+ * @param wcf\system\message\IMessageQuickReplyAction $object
+ * @param array<array> $parameters
+ * @param string $containerClassName
+ * @param string $containerDecoratorClassName
+ */
+ public function validateParameters(IMessageQuickReplyAction $object, array &$parameters, $containerClassName, $containerDecoratorClassName = '') {
+ if (!isset($parameters['data']['message']) || empty($parameters['data']['message'])) {
+ throw new UserInputException('message');
+ }
+
+ $parameters['lastPostTime'] = (isset($parameters['lastPostTime'])) ? intval($parameters['lastPostTime']) : 0;
+ if (!$parameters['lastPostTime']) {
+ throw new UserInputException('lastPostTime');
+ }
+
+ $parameters['pageNo'] = (isset($parameters['pageNo'])) ? intval($parameters['pageNo']) : 0;
+ if (!$parameters['pageNo']) {
+ throw new UserInputException('pageNo');
+ }
+
+ $parameters['objectID'] = (isset($parameters['objectID'])) ? intval($parameters['objectID']) : 0;
+ if (!$parameters['objectID']) {
+ throw new UserInputException('objectID');
+ }
+
+ $this->container = new $containerClassName($parameters['objectID']);
+ if (!empty($containerDecoratorClassName)) {
+ if (!ClassUtil::isInstanceOf($containerDecoratorClassName, 'wcf\data\DatabaseObjectDecorator')) {
+ throw new SystemException("'".$containerDecoratorClassName."' does not extend 'wcf\data\DatabaseObjectDecorator'");
+ }
+
+ $this->container = new $containerDecoratorClassName($this->container);
+ }
+ $object->validateContainer($this->container);
+
+ // validate message
+ $object->validateMessage($this->container, $parameters['data']['message']);
+
+ // check for message quote ids
+ $parameters['removeQuoteIDs'] = (isset($parameters['removeQuoteIDs']) && is_array($parameters['removeQuoteIDs'])) ? ArrayUtil::trim($parameters['removeQuoteIDs']) : array();
+ }
+
+ /**
+ * Creates a new message and returns the parsed template.
+ *
+ * @param wcf\data\IMessageQuickReplyAction $object
+ * @param array<array> $parameters
+ * @param string $containerActionClassName
+ * @param string $sortOrder
+ * @param string $templateName
+ * @param string $application
+ * @return array
+ */
+ public function createMessage(IMessageQuickReplyAction $object, array &$parameters, $containerActionClassName, $sortOrder, $templateName, $application = 'wcf') {
+ $tableIndexName = call_user_func(array($this->container, 'getDatabaseTableIndexName'));
+ $parameters['data'][$tableIndexName] = $parameters['objectID'];
+ $parameters['data']['enableSmilies'] = WCF::getSession()->getPermission('user.message.canUseSmilies');
+ $parameters['data']['enableHtml'] = 0;
+ $parameters['data']['enableBBCodes'] = WCF::getSession()->getPermission('user.message.canUseBBCodes');
+ $parameters['data']['showSignature'] = (WCF::getUser()->userID ? WCF::getUser()->showSignature : 0);
+ $parameters['data']['time'] = TIME_NOW;
+ $parameters['data']['userID'] = WCF::getUser()->userID;
+ $parameters['data']['username'] = WCF::getUser()->username;
+
+ // pre-parse message text
+ $parameters['data']['message'] = PreParser::getInstance()->parse($parameters['data']['message'], $this->allowedBBodes);
+
+ $message = $object->create();
+ if ($message instanceof IMessage && !$message->isVisible()) {
+ return array(
+ 'isVisible' => false
+ );
+ }
+
+ // resolve the page no
+ list($pageNo, $count) = $object->getPageNo($this->container);
+
+ // we're still on current page
+ if ($pageNo == $parameters['pageNo']) {
+ // check for additional messages
+ $messageList = $object->getMessageList($this->container, $parameters['lastPostTime']);
+
+ // calculate start index
+ $startIndex = $count - (count($messageList) - 1);
+
+ WCF::getTPL()->assign(array(
+ 'attachmentList' => $messageList->getAttachmentList(),
+ 'container' => $this->container,
+ 'objects' => $messageList,
+ 'startIndex' => $startIndex,
+ 'sortOrder' => $sortOrder,
+ ));
+
+ // assign 'to top' link
+ if (isset($parameters['anchor'])) {
+ WCF::getTPL()->assign('anchor', $parameters['anchor']);
+ }
+
+ // update visit time (messages shouldn't occur as new upon next visit)
+ if (ClassUtil::isInstanceOf($containerActionClassName, 'wcf\data\IVisitableObjectAction')) {
+ $containerAction = new $containerActionClassName(array(($this->container instanceof DatabaseObjectDecorator ? $this->container->getDecoratedObject() : $this->container)), 'markAsRead');
+ $containerAction->executeAction();
+ }
+
+ return array(
+ 'lastPostTime' => $message->time,
+ 'template' => WCF::getTPL()->fetch($templateName, $application)
+ );
+ }
+ else {
+ // redirect
+ return array(
+ 'url' => $object->getRedirectUrl($this->container, $message)
+ );
+ }
+ }
+
+ /**
+ * Returns the container object.
+ *
+ * @return wcf\data\DatabaseObject
+ */
+ public function getContainer() {
+ return $this->container;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\message\censorship;
+use wcf\system\SingletonFactory;
+use wcf\util\ArrayUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Finds censored words.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage system.message.censorship
+ * @category Community Framework
+ */
+class Censorship extends SingletonFactory {
+ /**
+ * censored words
+ * @var array<string>
+ */
+ protected $censoredWords = array();
+
+ /**
+ * word delimiters
+ * @var string
+ */
+ protected $delimiters = '[\s\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]';
+
+ /**
+ * list of words
+ * @var array<string>
+ */
+ protected $words = array();
+
+ /**
+ * list of matches
+ * @var array
+ */
+ protected $matches = array();
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ // get words which should be censored
+ $censoredWords = explode("\n", StringUtil::unifyNewlines(StringUtil::toLowerCase(CENSORED_WORDS)));
+
+ // format censored words
+ $this->censoredWords = ArrayUtil::trim($censoredWords);
+ }
+
+ /**
+ * Returns censored words from a text.
+ *
+ * @param string $text
+ * @return mixed $matches / false
+ */
+ public function test($text) {
+ // reset values
+ $this->matches = $this->words = array();
+
+ // string to lower case
+ $text = StringUtil::toLowerCase($text);
+
+ // ignore bbcode tags
+ $text = preg_replace('~\[/?[a-z]+[^\]]*\]~i', '', $text);
+
+ // split the text in single words
+ $this->words = preg_split("!".$this->delimiters."+!", $text, -1, PREG_SPLIT_NO_EMPTY);
+
+ // check each word if it's censored.
+ for ($i = 0, $count = count($this->words); $i < $count; $i++) {
+ $word = $this->words[$i];
+ foreach ($this->censoredWords as $censoredWord) {
+ // check for direct matches ("badword" == "badword")
+ if ($censoredWord == $word) {
+ // store censored word
+ if (isset($this->matches[$word])) {
+ $this->matches[$word]++;
+ }
+ else {
+ $this->matches[$word] = 1;
+ }
+
+ continue 2;
+ }
+ // check for asterisk matches ("*badword*" == "FooBadwordBar")
+ else if (StringUtil::indexOf($censoredWord, '*') !== false) {
+ $censoredWord = StringUtil::replace('\*', '.*', preg_quote($censoredWord));
+ if (preg_match('!^'.$censoredWord.'$!', $word)) {
+ // store censored word
+ if (isset($this->matches[$word])) {
+ $this->matches[$word]++;
+ }
+ else {
+ $this->matches[$word] = 1;
+ }
+
+ continue 2;
+ }
+ }
+ // check for partial matches ("~badword~" == "bad-word")
+ else if (StringUtil::indexOf($censoredWord, '~') !== false) {
+ $censoredWord = StringUtil::replace('~', '', $censoredWord);
+ if (($position = StringUtil::indexOf($censoredWord, $word)) !== false) {
+ if ($position > 0) {
+ // look behind
+ if (!$this->lookBehind($i - 1, StringUtil::substring($censoredWord, 0, $position))) {
+ continue;
+ }
+ }
+
+ if ($position + StringUtil::length($word) < StringUtil::length($censoredWord)) {
+ // look ahead
+ if (($newIndex = $this->lookAhead($i + 1, StringUtil::substring($censoredWord, $position + StringUtil::length($word))))) {
+ $i = $newIndex;
+ }
+ else {
+ continue;
+ }
+ }
+
+ // store censored word
+ if (isset($this->matches[$censoredWord])) {
+ $this->matches[$censoredWord]++;
+ }
+ else {
+ $this->matches[$censoredWord] = 1;
+ }
+
+ continue 2;
+ }
+ }
+ }
+ }
+
+ // at least one censored word was found
+ if (count($this->matches) > 0) {
+ return $this->matches;
+ }
+ // text is clean
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * Looks behind in the word list.
+ *
+ * @param integer $index
+ * @param string $search
+ * @return boolean
+ */
+ protected function lookBehind($index, $search) {
+ if (isset($this->words[$index])) {
+ if (StringUtil::indexOf($this->words[$index], $search) === (StringUtil::length($this->words[$index]) - StringUtil::length($search))) {
+ return true;
+ }
+ else if (StringUtil::indexOf($search, $this->words[$index]) === (StringUtil::length($search) - StringUtil::length($this->words[$index]))) {
+ return $this->lookBehind($index - 1, 0, (StringUtil::length($search) - StringUtil::length($this->words[$index])));
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Looks ahead in the word list.
+ *
+ * @param integer $index
+ * @param string $search
+ * @return mixed
+ */
+ protected function lookAhead($index, $search) {
+ if (isset($this->words[$index])) {
+ if (StringUtil::indexOf($this->words[$index], $search) === 0) {
+ return $index;
+ }
+ else if (StringUtil::indexOf($search, $this->words[$index]) === 0) {
+ return $this->lookAhead($index + 1, StringUtil::substring($search, StringUtil::length($this->words[$index])));
+ }
+ }
+
+ return false;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\message\quote;
+use wcf\data\user\UserProfile;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Default implementation for quote handlers.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage system.message.quote
+ * @category Community Framework
+ */
+abstract class AbstractMessageQuoteHandler extends SingletonFactory implements IMessageQuoteHandler {
+ /**
+ * template name
+ * @var string
+ */
+ public $templateName = 'messageQuoteList';
+
+ /**
+ * list of quoted message
+ * @var array<wcf\system\message\quote\QuotedMessage>
+ */
+ public $quotedMessages = array();
+
+ /**
+ * @see wcf\system\message\quote\IMessageQuoteHandler::render()
+ */
+ public function render(array $data, $supportPaste = false) {
+ $messages = $this->getMessages($data);
+ $userIDs = $userProfiles = array();
+ foreach ($messages as $message) {
+ $userID = $message->getUserID();
+ if ($userID) {
+ $userIDs[] = $userID;
+ }
+ }
+
+ if (!empty($userIDs)) {
+ $userIDs = array_unique($userIDs);
+ $userProfiles = UserProfile::getUserProfiles($userIDs);
+ }
+
+ WCF::getTPL()->assign(array(
+ 'messages' => $this->getMessages($data),
+ 'supportPaste' => $supportPaste,
+ 'userProfiles' => $userProfiles
+ ));
+
+ return WCF::getTPL()->fetch($this->templateName);
+ }
+
+ /**
+ * @see wcf\system\message\quote\IMessageQuoteHandler::renderQuotes()
+ */
+ public function renderQuotes(array $data, $render = true) {
+ $messages = $this->getMessages($data);
+
+ $renderedQuotes = array();
+ foreach ($messages as $message) {
+ foreach ($message as $quoteID => $quote) {
+ if ($render) {
+ $renderedQuotes[] = MessageQuoteManager::getInstance()->renderQuote($message->object, $quote);
+ }
+ else {
+ $quotedMessage = $message->getFullQuote($quoteID);
+ $renderedQuotes[] = MessageQuoteManager::getInstance()->renderQuote($message->object, ($quotedMessage === null ? $quote : $quotedMessage));
+ }
+ }
+ }
+
+ return $renderedQuotes;
+ }
+
+ /**
+ * Returns a list of QuotedMessage objects.
+ *
+ * @param array<array> $data
+ * @return array<wcf\system\message\quote\QuotedMessage>
+ */
+ abstract protected function getMessages(array $data);
+}
--- /dev/null
+<?php
+namespace wcf\system\message\quote;
+
+/**
+ * Default interface for quote handlers.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage system.message.quote
+ * @category Community Framework
+ */
+interface IMessageQuoteHandler {
+ /**
+ * Renders a template for given quotes.
+ *
+ * @param array $data
+ * @param boolean $supportPaste
+ * @return string
+ */
+ public function render(array $data, $supportPaste = false);
+
+ /**
+ * Renders a list of quotes for insertation.
+ *
+ * @param array<array> $data
+ * @param boolean $render
+ * @return array<string>
+ */
+ public function renderQuotes(array $data, $render = true);
+}
--- /dev/null
+<?php
+namespace wcf\system\message\quote;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\IMessage;
+use wcf\system\application\ApplicationHandler;
+use wcf\system\exception\SystemException;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Manages message quotes.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage system.message.quote
+ * @category Community Framework
+ */
+class MessageQuoteManager extends SingletonFactory {
+ /**
+ * current object ids
+ * @var array<integer>
+ */
+ protected $objectIDs = array();
+
+ /**
+ * current object type name
+ * @var string
+ */
+ protected $objectType = '';
+
+ /**
+ * list of object types
+ * @var array<wcf\data\object\type\ObjectType>
+ */
+ protected $objectTypes = array();
+
+ /**
+ * primary application's package id
+ * @var integer
+ */
+ protected $packageID = 0;
+
+ /**
+ * list of stored quotes
+ * @var array<array>
+ */
+ protected $quotes = array();
+
+ /**
+ * list of quote messages by quote id
+ * @var array<string>
+ */
+ protected $quoteData = array();
+
+ /**
+ * message id for quoting
+ * @var integer
+ */
+ protected $quoteMessageID = 0;
+
+ /**
+ * list of quote ids to be removed
+ * @var array<string>
+ */
+ protected $removeQuoteIDs = array();
+
+ /**
+ * @see wcf\system\SingletonFactory::init()
+ */
+ protected function init() {
+ $this->packageID = ApplicationHandler::getInstance()->getPrimaryApplication()->packageID;
+
+ // load stored quotes from session
+ $messageQuotes = WCF::getSession()->getVar('__messageQuotes'.$this->packageID);
+ if (is_array($messageQuotes)) {
+ $this->quotes = (isset($messageQuotes['quotes'])) ? $messageQuotes['quotes'] : array();
+ $this->quoteData = (isset($messageQuotes['quoteData'])) ? $messageQuotes['quoteData'] : array();
+ $this->removeQuoteIDs = (isset($messageQuotes['removeQuoteIDs'])) ? $messageQuotes['removeQuoteIDs'] : array();
+ }
+
+ // load object types
+ $objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.message.quote');
+ foreach ($objectTypes as $objectType) {
+ $this->objectTypes[$objectType->objectType] = $objectType;
+ }
+ }
+
+ /**
+ * Adds a quote unless it is already stored. If you want to quote a whole
+ * message while maintaing the original markup, pass $obj->getExcerpt() for
+ * $message and $obj->getMessage() for $fullQuote.
+ *
+ * @param string $objectType
+ * @param integer $parentObjectID
+ * @param integer $objectID
+ * @param string $message
+ * @param string $fullQuote
+ * @param boolean
+ */
+ public function addQuote($objectType, $parentObjectID, $objectID, $message, $fullQuote = '') {
+ if (!isset($this->objectTypes[$objectType])) {
+ throw new SystemException("Object type '".$objectType."' is unknown");
+ }
+
+ if (!isset($this->quotes[$objectType])) {
+ $this->quotes[$objectType] = array();
+ }
+
+ if (!isset($this->quotes[$objectType][$objectID])) {
+ $this->quotes[$objectType][$objectID] = array();
+ }
+
+ $quoteID = $this->getQuoteID($objectType, $objectID, $message, $fullQuote);
+ if (!isset($this->quotes[$objectType][$objectID][$quoteID])) {
+ $this->quotes[$objectType][$objectID][$quoteID] = 0;
+ $this->quoteData[$quoteID] = $message;
+
+ // save parent object id
+ if ($parentObjectID) {
+ if (!isset($this->quoteData['parents'])) {
+ $this->quoteData['parents'] = array();
+ }
+
+ if (!isset($this->quoteData['parents'][$objectType])) {
+ $this->quoteData['parents'][$objectType] = array();
+ }
+
+ if (!isset($this->quoteData['parents'][$objectType][$parentObjectID])) {
+ $this->quoteData['parents'][$objectType][$parentObjectID] = array();
+ }
+
+ $this->quoteData['parents'][$objectType][$parentObjectID][] = $objectID;
+ $this->quoteData[$quoteID.'_pID'] = $parentObjectID;
+ }
+
+ if (!empty($fullQuote)) {
+ $this->quotes[$objectType][$objectID][$quoteID] = 1;
+ $this->quoteData[$quoteID.'_fq'] = $fullQuote;
+ }
+
+ $this->updateSession();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the quote id for given quote.
+ *
+ * @param string $objectType
+ * @param integer $objectID
+ * @param string $message
+ * @param string $fullQuote
+ * @return string
+ */
+ public function getQuoteID($objectType, $objectID, $message, $fullQuote = '') {
+ return substr(sha1($objectType.'|'.$objectID.'|'.$message.'|'.$fullQuote), 0, 8);
+ }
+
+ /**
+ * Removes a quote from storage.
+ *
+ * @param string $quoteID
+ */
+ public function removeQuote($quoteID) {
+ if (!isset($this->quoteData[$quoteID])) {
+ return false;
+ }
+
+ foreach ($this->quotes as $objectType => $objectIDs) {
+ foreach ($objectIDs as $objectID => $quoteIDs) {
+ foreach ($quoteIDs as $qID => $isFullQuote) {
+ if ($qID == $quoteID) {
+ unset($this->quotes[$objectType][$objectID][$qID]);
+
+ // clean-up structure
+ if (empty($this->quotes[$objectType][$objectID])) {
+ unset($this->quotes[$objectType][$objectID]);
+
+ if (empty($this->quotes[$objectType])) {
+ unset($this->quotes[$objectType]);
+ }
+ }
+
+ unset($this->quoteData[$quoteID]);
+ if ($isFullQuote) {
+ unset($this->quoteData[$quoteID.'_fq']);
+ }
+
+ // remove parent object id reference
+ if (isset($this->quoteData[$quoteID.'_pID'])) {
+ $parentObjectID = $this->quoteData[$quoteID.'_pID'];
+ if (!isset($this->quotes[$objectType][$objectID])) {
+ if (isset($this->quoteData['parents'][$objectType][$parentObjectID][$objectID])) {
+ unset($this->quoteData['parents'][$objectType][$parentObjectID][$objectID]);
+
+ // cleanup
+ if (empty($this->quoteData['parents'][$objectType][$parentObjectID])) {
+ unset($this->quoteData['parents'][$objectType][$parentObjectID]);
+
+ if (empty($this->quoteData['parents'][$objectType])) {
+ unset($this->quoteData['parents'][$objectType]);
+
+ if (empty($this->quoteData['parents'])) {
+ unset($this->quoteData['parents']);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ $this->updateSession();
+
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a list of quotes.
+ *
+ * @param boolean supportPaste
+ */
+ public function getQuotes($supportPaste = false) {
+ $template = '';
+
+ foreach ($this->quotes as $objectType => $objectData) {
+ $quoteHandler = call_user_func(array($this->objectTypes[$objectType]->className, 'getInstance'));
+ $template .= $quoteHandler->render($objectData, $supportPaste);
+ }
+
+ return $template;
+ }
+
+ /**
+ * Returns a list of quotes by object type and id.
+ *
+ * @param string $objectType
+ * @param array<integer> $objectIDs
+ * @param boolean $markForRemoval
+ * @return array<string>
+ */
+ public function getQuotesByObjectIDs($objectType, array $objectIDs, $markForRemoval = true) {
+ if (!isset($this->quotes[$objectType])) {
+ return array();
+ }
+
+ $data = array();
+ $removeQuoteIDs = array();
+ foreach ($this->quotes[$objectType] as $objectID => $quoteIDs) {
+ if (in_array($objectID, $objectIDs)) {
+ $data[$objectID] = $quoteIDs;
+
+ // mark quotes for removal
+ if ($markForRemoval) {
+ $removeQuoteIDs = array_merge($removeQuoteIDs, array_keys($quoteIDs));
+ }
+ }
+ }
+
+ // no quotes found
+ if (empty($data)) {
+ return array();
+ }
+
+ // mark quotes for removal
+ if (!empty($removeQuoteIDs)) {
+ $this->markQuotesForRemoval($removeQuoteIDs);
+ }
+
+ $quoteHandler = call_user_func(array($this->objectTypes[$objectType]->className, 'getInstance'));
+ return $quoteHandler->renderQuotes($data);
+ }
+
+ /**
+ * Returns a list of quotes by object type and parent object id.
+ *
+ * @param string $objectType
+ * @param integer $parentObjectID
+ * @param boolean $markForRemoval
+ * @return array<string>
+ */
+ public function getQuotesByParentObjectID($objectType, $parentObjectID, $markForRemoval = true) {
+ if (!isset($this->quoteData['parents'][$objectType][$parentObjectID])) {
+ return array();
+ }
+
+ $data = array();
+ $removeQuoteIDs = array();
+ foreach ($this->quoteData['parents'][$objectType][$parentObjectID] as $objectID) {
+ if (isset($this->quotes[$objectType][$objectID])) {
+ $data[$objectID] = $this->quotes[$objectType][$objectID];
+
+ // mark quotes for removal
+ if ($markForRemoval) {
+ $removeQuoteIDs = array_merge($removeQuoteIDs, array_keys($data[$objectID]));
+ }
+ }
+ }
+
+ // no quotes found
+ if (empty($data)) {
+ return array();
+ }
+
+ // mark quotes for removal
+ if (!empty($removeQuoteIDs)) {
+ $this->markQuotesForRemoval($removeQuoteIDs);
+ }
+
+ $quoteHandler = call_user_func(array($this->objectTypes[$objectType]->className, 'getInstance'));
+ return $quoteHandler->renderQuotes($data, false);
+ }
+
+ /**
+ * Returns a quote by id.
+ *
+ * @param string $quoteID
+ * @param boolean $useFullQuote
+ * @return string
+ */
+ public function getQuote($quoteID, $useFullQuote = true) {
+ if ($useFullQuote && isset($this->quoteData[$quoteID.'_fq'])) {
+ return $this->quoteData[$quoteID.'_fq'];
+ }
+ else if (isset($this->quoteData[$quoteID])) {
+ return $this->quoteData[$quoteID];
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the object id by quote id.
+ *
+ * @param string $quoteID
+ * @return integer
+ */
+ public function getObjectID($quoteID) {
+ if (isset($this->quoteData[$quoteID])) {
+ foreach ($this->quotes as $objectIDs) {
+ foreach ($objectIDs as $objectID => $quoteIDs) {
+ if (isset($quoteIDs[$quoteID])) {
+ return $objectID;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Marks quote ids for removal.
+ *
+ * @param array<string> $quoteIDs
+ */
+ public function markQuotesForRemoval(array $quoteIDs) {
+ foreach ($quoteIDs as $index => $quoteID) {
+ if (!isset($this->quoteData[$quoteID]) || in_array($quoteID, $this->removeQuoteIDs)) {
+ unset($quoteIDs[$index]);
+ }
+ }
+
+ if (!empty($quoteIDs)) {
+ $this->removeQuoteIDs = array_merge($this->removeQuoteIDs, $quoteIDs);
+ $this->updateSession();
+ }
+ }
+
+ /**
+ * Renders a quote for given message.
+ *
+ * @param wcf\data\IMessage $message
+ * @param string $text
+ * @return string
+ */
+ public function renderQuote(IMessage $message, $text) {
+ $escapedUsername = StringUtil::replace(array("\\", "'"), array("\\\\", "\'"), $message->getUsername());
+ $escapedLink = StringUtil::replace(array("\\", "'"), array("\\\\", "\'"), $message->getLink());
+
+ return "[quote='".$escapedUsername."','".$escapedLink."']".$text."[/quote]";
+ }
+
+ /**
+ * Removes quotes marked for removal.
+ */
+ public function removeMarkedQuotes() {
+ if (!empty($this->removeQuoteIDs)) {
+ foreach ($this->removeQuoteIDs as $quoteID) {
+ $this->removeQuote($quoteID);
+ }
+
+ // reset list of quote ids marked for removal
+ $this->removeQuoteIDs = array();
+
+ $this->updateSession();
+ }
+ }
+
+ /**
+ * Returns the number of stored quotes.
+ *
+ * @return integer
+ */
+ public function countQuotes() {
+ $count = 0;
+ foreach ($this->quoteData as $quoteID => $quote) {
+ if (strlen($quoteID) == 8) {
+ $count++;
+ }
+ }
+
+ return $count;
+ }
+
+ /**
+ * Returns a list of full quotes by object id for given object types.
+ *
+ * @param array<string> $objectTypes
+ * @return array<array>
+ */
+ public function getFullQuoteObjectIDs(array $objectTypes) {
+ $objectIDs = array();
+
+ foreach ($objectTypes as $objectType) {
+ if (!isset($this->objectTypes[$objectType])) {
+ throw new SystemException("Object type '".$objectType."' is unknown");
+ }
+
+ $objectIDs[$objectType] = array();
+ if (isset($this->quotes[$objectType])) {
+ foreach ($this->quotes[$objectType] as $objectID => $quotes) {
+ foreach ($quotes as $quoteID => $isFullQuote) {
+ if ($isFullQuote) {
+ $objectIDs[$objectType][] = $objectID;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return $objectIDs;
+ }
+
+ /**
+ * Sets object type and object ids.
+ *
+ * @param string $objectType
+ * @param array<integer> $objectIDs
+ */
+ public function initObjects($objectType, array $objectIDs) {
+ if (!isset($this->objectTypes[$objectType])) {
+ throw new SystemException("Object type '".$objectType."' is unknown");
+ }
+
+ $this->objectIDs = ArrayUtil::toIntegerArray($objectIDs);
+ $this->objectType = $objectType;
+ }
+
+ /**
+ * Reads the quote message id.
+ */
+ public function readParameters() {
+ if (isset($_REQUEST['quoteMessageID'])) $this->quoteMessageID = intval($_REQUEST['quoteMessageID']);
+ }
+
+ /**
+ * Reads a list of quote ids to remove.
+ */
+ public function readFormParameters() {
+ if (isset($_REQUEST['__removeQuoteIDs']) && is_array($_REQUEST['__removeQuoteIDs'])) {
+ $quoteIDs = ArrayUtil::trim($_REQUEST['__removeQuoteIDs']);
+ foreach ($quoteIDs as $index => $quoteID) {
+ if (!isset($this->quoteData[$quoteID])) {
+ unset($quoteIDs[$index]);
+ }
+ }
+
+ if (!empty($quoteIDs)) {
+ $this->removeQuoteIDs = array_merge($this->removeQuoteIDs, $quoteIDs);
+ }
+ }
+ }
+
+ /**
+ * Removes quotes after saving current message.
+ */
+ public function saved() {
+ $this->removeMarkedQuotes();
+ }
+
+ /**
+ * Assigns variables on page load.
+ */
+ public function assignVariables() {
+ $fullQuoteObjectIDs = array();
+ if (!empty($this->objectType) && !empty($this->objectIDs) && isset($this->quotes[$this->objectType])) {
+ foreach ($this->quotes[$this->objectType] as $objectID => $quotes) {
+ if (!in_array($objectID, $this->objectIDs)) {
+ continue;
+ }
+
+ foreach ($quotes as $isFullQuote) {
+ if ($isFullQuote) {
+ $fullQuoteObjectIDs[] = $objectID;
+ break;
+ }
+ }
+ }
+ }
+
+ WCF::getTPL()->assign(array(
+ '__quoteCount' => $this->countQuotes(),
+ '__quoteFullQuote' => $fullQuoteObjectIDs,
+ '__quoteRemove' => $this->removeQuoteIDs
+ ));
+ }
+
+ /**
+ * Returns quote message id.
+ *
+ * @return integer
+ */
+ public function getQuoteMessageID() {
+ return $this->quoteMessageID;
+ }
+
+ /**
+ * Removes orphaned quote ids
+ *
+ * @param array<integer> $quoteIDs
+ */
+ public function removeOrphanedQuotes(array $quoteIDs) {
+ foreach ($quoteIDs as $quoteID) {
+ $this->removeQuote($quoteID);
+ }
+
+ $this->updateSession();
+ }
+
+ /**
+ * Updates data stored in session,
+ */
+ protected function updateSession() {
+ WCF::getSession()->register('__messageQuotes'.$this->packageID, array(
+ 'quotes' => $this->quotes,
+ 'quoteData' => $this->quoteData,
+ 'removeQuoteIDs' => $this->removeQuoteIDs
+ ));
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\message\quote;
+use wcf\data\IMessage;
+
+/**
+ * Wrapper class for quoted messages.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2013 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage system.message.quote
+ * @category Community Framework
+ */
+class QuotedMessage implements \Countable, \Iterator {
+ /**
+ * list of full quotes for insertation
+ * @var array<string>
+ */
+ public $fullQuotes = array();
+
+ /**
+ * quotable database object
+ * @var wcf\data\IQuotableDatabaseObject
+ */
+ public $object = null;
+
+ /**
+ * list of quotes (shortend)
+ * @var array<string>
+ */
+ public $quotes = array();
+
+ /**
+ * current iterator index
+ * @var integer
+ */
+ protected $index = 0;
+
+ /**
+ * list of index to object relation
+ * @var array<integer>
+ */
+ protected $indexToObject = null;
+
+ /**
+ * Creates a new QuotedMessage object.
+ *
+ * @param wcf\data\IMessage $object
+ */
+ public function __construct(IMessage $object) {
+ $this->object = $object;
+ }
+
+ /**
+ * Adds a quote for this message.
+ *
+ * @param string $quoteID
+ * @param string $quote
+ * @param string $fullQuote
+ */
+ public function addQuote($quoteID, $quote, $fullQuote) {
+ $this->fullQuotes[$quoteID] = $fullQuote;
+ $this->quotes[$quoteID] = $quote;
+ $this->indexToObject[] = $quoteID;
+ }
+
+ /**
+ * @see wcf\data\ITitledObject::getTitle()
+ */
+ public function __toString() {
+ return $this->object->getTitle();
+ }
+
+ /**
+ * Forwards calls to the decorated object.
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return mixed
+ */
+ public function __call($name, $value) {
+ return $this->object->$name();
+ }
+
+ /**
+ * Returns the full quote by quote id.
+ *
+ * @param string $quoteID
+ * @return string
+ */
+ public function getFullQuote($quoteID) {
+ if (isset($this->fullQuotes[$quoteID])) {
+ return $this->fullQuotes[$quoteID];
+ }
+
+ return null;
+ }
+
+ /**
+ * @see \Countable::count()
+ */
+ public function count() {
+ return count($this->quotes);
+ }
+
+ /**
+ * @see \Iterator::current()
+ */
+ public function current() {
+ $objectID = $this->indexToObject[$this->index];
+ return $this->quotes[$objectID];
+ }
+
+ /**
+ * CAUTION: This methods does not return the current iterator index,
+ * rather than the object key which maps to that index.
+ *
+ * @see \Iterator::key()
+ */
+ public function key() {
+ return $this->indexToObject[$this->index];
+ }
+
+ /**
+ * @see \Iterator::next()
+ */
+ public function next() {
+ ++$this->index;
+ }
+
+ /**
+ * @see \Iterator::rewind()
+ */
+ public function rewind() {
+ $this->index = 0;
+ }
+
+ /**
+ * @see \Iterator::valid()
+ */
+ public function valid() {
+ return isset($this->indexToObject[$this->index]);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\util;
+use wcf\system\Callback;
+use wcf\system\Regex;
+
+/**
+ * Contains message-related functions.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf.message
+ * @subpackage util
+ * @category Community Framework
+ */
+class MessageUtil {
+ /**
+ * Strips session links, html entities and \r\n from the given text.
+ *
+ * @param string $text
+ * @return string
+ */
+ public static function stripCrap($text) {
+ // strip session links, security tokens and access tokens
+ $text = Regex::compile('(?<=\?|&)([st]=[a-f0-9]{40}|at=\d+-[a-f0-9]{40})')->replace($text, '');
+
+ // convert html entities (utf-8)
+ $text = Regex::compile('&#(3[2-9]|[4-9][0-9]|\d{3,5});')->replace($text, new Callback(function ($matches) {
+ return StringUtil::getCharacter(intval($matches[1]));
+ }));
+
+ // unify new lines
+ $text = StringUtil::unifyNewlines($text);
+
+ return $text;
+ }
+}
--- /dev/null
+/* ### message groups ### */
+.messageGroupList {
+ .columnSubject {
+ > .labelList {
+ float: right;
+ padding-left: 7px;
+ }
+
+ > h3 {
+ > .messageGroupLink {
+ font-size: @wcfTitleFontSize;
+ }
+
+ > .badge.label {
+ top: -2px;
+ }
+ }
+
+ > small {
+ display: block;
+ }
+
+ > nav {
+ font-size: @wcfSmallFontSize;
+
+ > ul > li {
+ display: inline;
+ }
+ }
+ }
+
+ tr {
+ &.new .columnSubject > h3 > .messageGroupLink {
+ font-weight: bold;
+ }
+
+ &.new .columnAvatar div > p > img,
+ &:hover .columnAvatar div > p > img {
+ opacity: 1;
+ }
+
+ &.messageDisabled {
+ color: @wcfDisabledColor;
+
+ > td {
+ background-color: @wcfDisabledBackgroundColor !important;
+ }
+
+ a:not(.badge) {
+ color: @wcfDisabledColor;
+ }
+ }
+
+ &.messageDeleted {
+ color: @wcfDeletedColor;
+
+ > td {
+ background-color: @wcfDeletedBackgroundColor !important;
+ }
+
+ a:not(.badge) {
+ color: @wcfDeletedColor;
+ }
+ }
+
+ .columnSubject .statusDisplay .pageNavigation {
+ opacity: 0;
+
+ .transition(opacity, .2s);
+ }
+
+ &:hover .columnSubject .statusDisplay .pageNavigation {
+ opacity: 1;
+ }
+
+ &.new .columnAvatar > div {
+ &:after {
+ color: @wcfLinkColor;
+ content: "\f069";
+ font-family: FontAwesome;
+ font-weight: normal !important;
+ font-style: normal !important;
+ font-size: 14px;
+ position: absolute;
+ text-decoration: none !important;
+ top: -4px;
+ right: -2px;
+
+ .textShadow(@wcfContainerBackgroundColor);
+ }
+ }
+ }
+
+ .columnAvatar {
+ div {
+ position: relative;
+ width: 40px;
+ height: 38px;
+
+ > p > img {
+ opacity: .6;
+
+ .transition(opacity, .2s);
+ }
+ }
+
+ .myAvatar {
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ bottom: -2px;
+ left: 24px;
+ opacity: 1;
+
+ .boxShadow(0, 0, rgba(0, 0, 0, .3), 3px);
+ }
+ }
+
+ .columnLastPost {
+ white-space: nowrap;
+
+ > div > div > small {
+ color: @wcfDimmedColor;
+ }
+ }
+}
+
+/* ### messages ### */
+@media only screen and (min-width: 801px) {
+ .messageList {
+ .messageGroupStarter {
+ position: relative;
+
+ > .message:after {
+ content: "\f005";
+ font-family: FontAwesome;
+ font-size: 14px;
+ display: block;
+ left: 4px;
+ position: absolute;
+ top: 2px;
+
+ .textShadow(@wcfSidebarBackgroundColor);
+ }
+
+ > .message.messageSidebarOrientationRight:after {
+ left: auto;
+ right: 4px;
+ }
+ }
+ }
+}
+
+.message {
+ background-color: @wcfContainerHoverBackgroundColor;
+ border: 1px solid @wcfContainerBorderColor;
+ //overflow: hidden; /* todo: fixes floating issues when using message on pages with a sidebar */
+ position: relative;
+
+ &:hover {
+ .messageHeader .messageQuickOptions > li > a {
+ opacity: 1;
+ }
+
+ .messageOptions nav {
+ opacity: 1;
+ }
+ }
+
+ &.messageDisabled {
+ background-color: @wcfDisabledBackgroundColor;
+
+ .messageSidebar {
+ color: @wcfDisabledColor;
+
+ a {
+ color: @wcfDisabledColor;
+ }
+ }
+ }
+
+ &.messageDeleted {
+ background-color: @wcfDeletedBackgroundColor;
+
+ .messageSidebar {
+ color: @wcfDeletedColor;
+
+ a {
+ color: @wcfDeletedColor;
+ }
+ }
+ }
+
+ &.jsMarked {
+ background-color: @wcfSelectedBackgroundColor;
+
+ .messageSidebar {
+ color: @wcfSelectedColor;
+
+ a {
+ color: @wcfSelectedColor;
+ }
+ }
+ }
+
+ .messageOptions {
+ font-size: @wcfSmallFontSize;
+ position: relative;
+
+ &.forceHidden nav {
+ opacity: 0 !important;
+ }
+
+ &.forceOpen nav {
+ opacity: 1;
+ }
+
+ nav {
+ bottom: -2px;
+ opacity: 0;
+ position: absolute;
+ right: -22px;
+ text-align: right;
+
+ .transition(opacity, .1s);
+
+ ul.smallButtons {
+ > li {
+ a.button {
+ .borderRadius(0);
+ }
+ }
+ }
+ }
+ }
+
+ .messageHeader {
+ .messageQuickOptions {
+ float: right;
+
+ > li {
+ display: inline-block;
+
+ > a {
+ opacity: .6;
+
+ .transition(opacity, .2s);
+
+ > span.icon {
+ color: @wcfDimmedColor;
+
+ .transition(color, .2s);
+ }
+
+ &:hover {
+ > span.icon {
+ color: @wcfLinkColor;
+ }
+ }
+ }
+ }
+
+ input[type=checkbox] {
+ position: relative;
+ top: 1px;
+ }
+ }
+
+ .permalink {
+ color: @wcfDimmedColor;
+ }
+ }
+
+ &.dividers {
+ .userCredits {
+ border-top: 1px solid @wcfContainerBorderColor;
+ }
+ }
+}
+
+.touch .message .messageOptions nav {
+ opacity: 1;
+}
+
+@media only screen and (max-width: 800px) {
+ .message {
+ border-width: 1px 0;
+ }
+}
+
+/* sidebars orientations */
+.message.messageSidebarOrientationLeft {
+ .messageContent {
+ border-left: 1px solid @wcfContainerBorderColor;
+ margin: 0 0 0 211px;
+ }
+
+ .messageSidebar {
+ float: left;
+ }
+}
+
+.message.messageSidebarOrientationRight {
+ .messageContent {
+ border-right: 1px solid @wcfContainerBorderColor;
+ margin: 0 211px 0 0;
+ }
+
+ .messageSidebar {
+ float: right;
+ }
+}
+
+/* pointer */
+.message.messageSidebarOrientationLeft,
+.message.messageSidebarOrientationRight {
+ .messageHeader {
+ &:before,
+ &:after {
+ border-width: 20px;
+ content: "";
+ display: block;
+ height: 0;
+ position: absolute;
+ top: 35px;
+ width: 0;
+ }
+
+ &:before {
+ z-index: 100;
+ }
+
+ &:after {
+ z-index: 101;
+ }
+ }
+}
+
+.message.messageSidebarOrientationLeft {
+ .messageHeader {
+ &:before,
+ &:after {
+ border-style: inset solid inset none;
+ }
+
+ &:before {
+ border-color: transparent @wcfContainerBorderColor transparent transparent;
+ left: -20px;
+ }
+
+ &:after {
+ border-color: transparent @wcfContainerBackgroundColor transparent transparent;
+ left: -19px;
+ }
+ }
+}
+
+.message.messageSidebarOrientationRight {
+ .messageHeader {
+ &:before,
+ &:after {
+ border-style: inset none inset solid;
+ }
+
+ &:before {
+ border-color: transparent transparent transparent @wcfContainerBorderColor;
+ right: -20px;
+ }
+
+ &:after {
+ border-color: transparent transparent transparent @wcfContainerBackgroundColor;
+ right: -19px;
+ }
+ }
+}
+
+/* new message badge */
+.message .newMessageBadge {
+ color: @wcfTabularBoxColor;
+ display: block;
+ font-size: @wcfSmallFontSize;
+ font-weight: bold;
+ padding: 6px 10px;
+ position: absolute;
+ text-transform: uppercase;
+ top: 24px;
+
+ .boxShadow(1px, 1px, rgba(0, 0, 0, .2), 3px);
+ .linearGradient(darken(@wcfTabularBoxBackgroundColor, 10%), @wcfTabularBoxBackgroundColor, darken(@wcfTabularBoxBackgroundColor, 10%));
+ .textShadow(darken(@wcfTabularBoxBackgroundColor, 10%));
+
+ &:before {
+ border-bottom: 4px solid darken(@wcfTabularBoxBackgroundColor, 20%);
+ content: "";
+ display: block;
+ position: absolute;
+ top: -4px;
+ }
+}
+
+.message.messageSidebarOrientationLeft .newMessageBadge {
+ left: -219px;
+
+ .borderRadius(0, 5px, 5px, 0);
+
+ &:before {
+ border-left: 6px solid transparent;
+ left: 0;
+ }
+}
+
+.message.messageSidebarOrientationRight .newMessageBadge {
+ right: -219px;
+}
+
+.message.messageReduced .newMessageBadge {
+ right: -7px;
+ top: 21px;
+}
+
+.message.messageSidebarOrientationRight,
+.message.messageReduced {
+ .newMessageBadge {
+ .borderRadius(5px, 0, 0, 5px);
+
+ &:before {
+ border-right: 6px solid transparent;
+ right: 0;
+ }
+ }
+}
+
+.message .messageBody {
+ color: @wcfColor;
+ display: block;
+ line-height: 1.5;
+ padding: @wcfGapMedium @wcfGapLarge 1px;
+ /*position: relative;*/
+
+ > div:not(.messageFooter) {
+ border-top: 1px dotted @wcfContainerBorderColor;
+ overflow: hidden;
+ padding: @wcfGapMedium 0;
+ }
+
+ > footer {
+ padding-bottom: @wcfGapMedium;
+ }
+
+ > .messageSignature {
+ color: @wcfDimmedColor;
+ }
+
+ .messageFooter {
+ > *:not(:first-child) {
+ margin-top: @wcfGapSmall;
+ }
+
+ > .messageFooterNote {
+ border-left: 2px solid @wcfContainerBorderColor;
+ color: @wcfDimmedColor;
+ font-size: @wcfSmallFontSize;
+ padding: @wcfGapTiny @wcfGapSmall;
+
+ @messageFooterNoteGradientColor: fade(@wcfContainerBorderColor, 20%);
+ .linearGradientNative(~"left top, @{messageFooterNoteGradientColor} 0%, transparent 40%");
+ }
+ }
+}
+
+.message .messageContent {
+ background-color: @wcfContainerBackgroundColor;
+
+ .messageHeader {
+ padding: @wcfGapMedium @wcfGapLarge 0;
+ position: relative;
+
+ .messageHeadline {
+ > h1 {
+ color: @wcfColor;
+ font-size: @wcfSubHeadlineFontSize;
+ font-weight: bold;
+ overflow: hidden;
+ padding-right: 21px; // reserved space for new badge
+ text-overflow: ellipsis;
+
+ .textShadow(@wcfContainerBackgroundColor);
+
+ + p {
+ margin-top: 2px;
+ }
+ }
+
+ > p {
+ font-size: @wcfSmallFontSize;
+
+ > .likesBadge {
+ font-size: 100%;
+ margin: -2px 0 -1px 4px;
+ }
+
+ > .username:after {
+ content: " - ";
+ }
+ }
+ }
+
+ .box32 > .messageHeadline > p:first-child {
+ font-size: 100%;
+
+ > .username {
+ font-size: @wcfTitleFontSize;
+ font-weight: bold;
+
+ .textShadow(@wcfContainerBackgroundColor);
+ }
+
+ > .username {
+ display: block;
+ }
+
+ > .username:after {
+ content: "";
+ }
+
+ > .likesBadge {
+ font-size: @wcfSmallFontSize;
+ top: -1px;
+ }
+ }
+ }
+}
+
+.message .messageSidebar {
+ line-height: 1.3;
+ margin-bottom: -1px;
+ padding: @wcfGapMedium @wcfGapLarge @wcfGapLarge;
+ position: relative;
+ text-align: center;
+ width: 170px; /* Width toggle */
+
+ &:after {
+ clear: both;
+ content: '';
+ display: block;
+ }
+
+ header .username {
+ color: @wcfLinkColor;
+ font-size: @wcfTitleFontSize;
+ font-weight: bold;
+ padding: 0 3px 1px;
+
+ .textShadow(@wcfContainerHoverBackgroundColor);
+
+ a {
+ text-decoration: none;
+ }
+ }
+
+ .userTitle {
+ margin: 7px 0 0;
+ }
+
+ .userRank {
+ margin: 2px 0 0;
+ }
+
+ .userAvatar {
+ display: inline-block;
+ margin: @wcfGapSmall 0 0;
+ position: relative;
+ text-align: left;
+
+ > .badgeOnline {
+ color: rgba(238, 255, 238, 1);
+ bottom: 7px;
+ left: -5px;
+ position: absolute;
+ text-transform: uppercase;
+
+ .borderRadius(0, 5px, 5px, 0);
+ .boxShadow(1px, 1px, rgba(0, 0, 0, .2), 3px);
+ .linearGradient(darken(rgba(0, 153, 0, 1), 10%), rgba(0, 153, 0, 1), darken(rgba(0, 153, 0, 1), 10%));
+ .textShadow(darken(rgba(0, 153, 0, 1), 10%));
+
+ &:before {
+ border-bottom: 4px solid darken(rgba(0, 153, 0, 1), 20%);
+ border-left: 6px solid transparent;
+ content: "";
+ display: block;
+ left: 0;
+ position: absolute;
+ top: -4px;
+ }
+ }
+ }
+
+ .userCredits {
+ font-size: @wcfSmallFontSize;
+ margin: @wcfGapSmall 0 0;
+ overflow: hidden;
+ padding: @wcfGapSmall 0 0;
+
+ .dataList {
+ > dt {
+ width: 50%;
+ }
+
+ > dd {
+ margin-left: 53%;
+ }
+ }
+ }
+}
+
+.message:not(.messageReduced) .messageOptions {
+ .clearfix();
+}
+
+.message:not(.messageReduced) .messageBody {
+ .clearfix();
+}
+
+li:nth-child(2n+1) .message {
+ &.messageSidebarOrientationLeft .messageHeader:after {
+ border-right-color: @wcfContainerAccentBackgroundColor;
+ }
+
+ &.messageSidebarOrientationRight .messageHeader:after {
+ border-left-color: @wcfContainerAccentBackgroundColor;
+ }
+
+ .messageContent {
+ background-color: @wcfContainerAccentBackgroundColor;
+ }
+}
+
+.messageReduced {
+ .messageOptions > .breadcrumbs {
+ bottom: 10px;
+ left: 0;
+ opacity: 1;
+ position: relative;
+ }
+}
+
+.messageCollapsed {
+ color: @wcfDimmedColor;
+ opacity: .8;
+ padding: @wcfGapMedium @wcfGapLarge;
+
+ .transition(opacity, .1s);
+
+ &:hover {
+ opacity: 1;
+ }
+
+ &.messageCollapsedExpandable {
+ cursor: pointer;
+ }
+
+ h1 {
+ font-size: @wcfSmallFontSize;
+ }
+
+ .messageCounter {
+ padding-top: 3px;
+ }
+
+ &.jsMarked {
+ background-color: @wcfSelectedBackgroundColor !important;
+ color: @wcfColor;
+
+ a {
+ color: @wcfColor;
+ }
+ }
+}
+
+@media only screen and (max-width: 800px) {
+ .messageCollapsed {
+ padding: @wcfGapSmall;
+ }
+}
+
+@media only screen and (min-width: 641px) and (max-width: 800px) {
+ .messageCollapsed {
+ padding: @wcfGapSmall @wcfGapMedium;
+ }
+}
+
+/* quick reply and inline editor */
+.messageBody > span.icon-spinner {
+ left: 50%;
+ margin: -21px -21px 0 0;
+ position: absolute;
+ top: 50%;
+}
+
+#messageQuickReply {
+ .formSubmit {
+ padding-bottom: @wcfGapMedium;
+ }
+}
+
+/* message quotes */
+#showQuotes {
+ bottom: @wcfGapLarge + @wcfGapTiny;
+ cursor: pointer;
+ opacity: .7;
+ position: fixed;
+ right: @wcfGapLarge + @wcfGapTiny;
+
+ .transition(opacity, .2s);
+
+ &:hover {
+ opacity: 1;
+ }
+}
+
+#messageQuoteList {
+ max-width: 800px !important;
+
+ li {
+ &:not(:first-child) {
+ margin-top: @wcfGapSmall;
+ }
+
+ > span {
+ float: left;
+ width: 40px;
+
+ > input {
+ vertical-align: bottom;
+ }
+
+ > span {
+ cursor: pointer;
+ vertical-align: middle;
+ }
+ }
+
+ div.jsQuote {
+ margin-left: 60px;
+ }
+
+ div.jsFullQuote {
+ display: none;
+ }
+ }
+}
+
+#quoteManagerCopy {
+ cursor: pointer;
+
+ .pointer {
+ border-width: 5px 5px 0;
+ bottom: -5px;
+ margin-left: -5px;
+ top: auto;
+ }
+}
+
+/* share buttons */
+.messageShareButtons {
+ > ul > li {
+ display: inline-block;
+
+ > a {
+ text-decoration: none;
+
+ > .icon {
+ height: 28px;
+ }
+ }
+
+ > .badge {
+ background-color: @wcfContainerBackgroundColor;
+ border: 1px solid @wcfContainerBorderColor;
+ color: @wcfColor;
+ line-height: 23px;
+ padding: 0 7px;
+ position: relative;
+ vertical-align: 1px;
+
+ .borderRadius(3px);
+
+ &:before {
+ border: 6px solid @wcfContainerBorderColor;
+ border-color: transparent @wcfContainerBorderColor transparent transparent;
+ content: "";
+ display: block;
+ height: 0;
+ margin-top: -6px;
+ position: absolute;
+ right: 100%;
+ top: 50%;
+ width: 0;
+ }
+
+ &:after {
+ border: 6px solid @wcfContainerBackgroundColor;
+ border-color: transparent @wcfContainerBackgroundColor transparent transparent;
+ content: "";
+ display: block;
+ height: 0;
+ margin-right: -1px;
+ margin-top: -6px;
+ position: absolute;
+ right: 100%;
+ top: 50%;
+ width: 0;
+ }
+ }
+ }
+
+ .jsShareFacebook {
+ > a > .icon {
+ color: rgb(59, 89, 152);
+ }
+ }
+
+ .jsShareTwitter {
+ > a > .icon {
+ color: rgb(64, 153, 255);
+ }
+ }
+
+ .jsShareGoogle {
+ > a > .icon {
+ color: rgb(211, 72, 54);
+ }
+ }
+
+ .jsShareReddit {
+ > a > img {
+ vertical-align: -7px;
+ padding: 0 4px;
+ }
+ }
+}
+
+.contentNavigation > .messageShareButtons {
+ float: right;
+ margin-right: @wcfGapMedium;
+ margin-top: 0;
+}
+
+/* ckeditor fixes */
+.cke_editor_text {
+ border-style: solid !important;
+ padding: 0 !important;
+}
+
+.cke_source {
+ padding: 8px !important;
+}
+
+.cke_combo__fontsize .cke_combo_text {
+ width: auto !important;
+}
+
+@media only screen and (max-width: 800px) {
+ .message.messageSidebarOrientationLeft,
+ .message.messageSidebarOrientationRight {
+ .messageContent {
+ border: 0;
+ margin: 0;
+ }
+
+ .messageSidebar {
+ float: none;
+ }
+
+ .messageHeader {
+ &:before,
+ &:after {
+ display: none;
+ }
+ }
+ }
+
+ .message .messageHeader .messageQuickOptions,
+ .message .messageBody .messageSignature,
+ .message .messageSidebar .userCredits {
+ display: none;
+ }
+
+ .message .messageSidebar {
+ padding: 7px;
+ text-align: left;
+ width: auto;
+
+ > div {
+ margin-left: 40px;
+ }
+
+ .userAvatar {
+ left: 7px;
+ position: absolute;
+ top: 0;
+
+ img {
+ height: 32px !important;
+ width: 32px !important;
+ }
+
+ > .badgeOnline {
+ display: none;
+ }
+ }
+
+ .userTitle {
+ margin-top: -2px;
+ }
+ }
+
+ /* reduce paddings */
+ .message .messageContent .messageHeader {
+ padding: 7px 7px 0;
+ }
+
+ .message .messageBody {
+ padding: 7px;
+ }
+
+ .message .messageBody > div:not(.messageFooter) {
+ padding: 7px 0;
+ }
+
+ .message .messageBody > footer {
+ padding: 0;
+ position: absolute;
+ right: 7px;
+ top: 7px;
+ }
+
+ .message .messageOptions nav {
+ opacity: 1;
+ position: static;
+ text-align: left;
+ }
+
+ .message.messageReduced .messageOptions {
+ display: none;
+ }
+
+ .message .newMessageBadge {
+ display: none;
+ }
+}
+
+@media only screen and (min-width: 641px) and (max-width: 800px) {
+ .message .messageSidebar,
+ .message .messageContent .messageHeader,
+ .message .messageBody {
+ padding-left: @wcfGapMedium;
+ padding-right: @wcfGapMedium;
+ }
+
+ .message .messageSidebar {
+ .userAvatar {
+ left: @wcfGapMedium;
+ }
+ }
+
+ .message .messageBody > footer {
+ right: @wcfGapMedium;
+ }
+}
--- /dev/null
+#pollOptionContainer .sortableList {
+ padding: @wcfGapSmall 0;
+
+ .sortableNode {
+ margin-top: @wcfGapSmall;
+
+ .sortableButtonContainer > img {
+ cursor: pointer;
+ margin-right: @wcfGapMedium;
+ }
+ }
+}
+
+.pollContainer {
+ float: left;
+ margin: 0 @wcfGapMedium @wcfGapSmall 0;
+ max-width: 50%;
+ min-width: 300px;
+
+ > .formSubmit {
+ background-color: @wcfContainerAccentBackgroundColor;
+ border-top: 1px solid @wcfContainerBorderColor;
+ margin: @wcfGapMedium -@wcfGapLarge -@wcfGapMedium -@wcfGapLarge;
+ padding: 10px 0;
+ }
+}
+
+.pollResultList {
+ li {
+ margin-bottom: 8px;
+ padding: 1px 0;
+ position: relative;
+ z-index: 0;
+
+ .transition(background-color, .1s);
+
+ &:last-child {
+ margin-bottom: 0px;
+ }
+
+ &:hover {
+ background-color: @wcfContainerAccentBackgroundColor;
+
+ .borderRadius(0, 5px, 5px, 0);
+ }
+
+ .pollMeter {
+ background-color: @wcfContainerHoverBackgroundColor;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ z-index: -1;
+
+ .borderRadius(0, 5px, 5px, 0);
+ }
+
+ .caption {
+ color: @wcfLinkColor;
+ padding: 2px 0;
+
+ .optionName {
+ display: inline-block;
+ padding: 0 2.5em 0 @wcfGapSmall;
+ }
+
+ .relativeVotes {
+ position: absolute;
+ right: 7px;
+ top: 3px;
+ }
+ }
+ }
+}
\ No newline at end of file
<item name="wcf.acp.group.option.admin.content.bbcode.canManageBBCode"><![CDATA[Kann BBCodes verwalten]]></item>
<item name="wcf.acp.group.option.category.admin.content.smiley"><![CDATA[Smileys]]></item>
<item name="wcf.acp.group.option.admin.content.smiley.canManageSmiley"><![CDATA[Kann Smileys verwalten]]></item>
+ <item name="wcf.acp.group.option.user.message.canUseSmilies"><![CDATA[Kann Smileys benutzen]]></item>
+ <item name="wcf.acp.group.option.user.message.canUseHtml"><![CDATA[Kann HTML benutzen]]></item>
+ <item name="wcf.acp.group.option.user.message.canUseBBCodes"><![CDATA[Kann BBCodes benutzen]]></item>
+ <item name="wcf.acp.group.option.user.message.allowedBBCodes"><![CDATA[Erlaubte BBCodes]]></item>
+ <item name="wcf.acp.group.option.user.message.allowedBBCodes.description"><![CDATA[Die hier ausgewählten BBCodes dürfen von Mitglieder dieser Benutzergruppe verwendet werden.]]></item>
</category>
<category name="wcf.acp.index">
<item name="wcf.acp.option.attachment_storage.description"><![CDATA[Sie können optional einen vom Standard abweichenden Speicherort für die Dateianhänge definieren. Bereits existierende Dateianhänge müssen bei einer Änderung des Speicherortes manuell in den neuen Ort übertragen werden.]]></item>
<item name="wcf.acp.option.module_attachment"><![CDATA[Dateianhänge]]></item>
<item name="wcf.acp.option.module_smiley"><![CDATA[Smileys]]></item>
+ <item name="wcf.acp.option.category.message.censorship"><![CDATA[Zensur-Funktion]]></item>
+ <item name="wcf.acp.option.censored_words"><![CDATA[Zu zensierende Wörter]]></item>
+ <item name="wcf.acp.option.censored_words.description"><![CDATA[Ein Wort pro Zeile. Sollte bei der Erstellung einer Nachricht eines dieser Wörter verwendet werden, so wird die Erstellung verweigert.<br />
+<em>Verwenden Sie „*“, um Wortteile zu finden: „wolt*“ findet auch „woltlab“</em><br />
+<em>Verwenden Sie „~“, um Worttrennungen zu finden: „wolt~“ findet auch „wolt-lab“</em>]]></item>
+ <item name="wcf.acp.option.enable_censorship"><![CDATA[Zensur aktivieren]]></item>
+ <item name="wcf.acp.option.enable_censorship.description"><![CDATA[Aktiviert die Zensur der unten angegebenen Wörter in allen von Benutzern geschriebenen Texten.]]></item>
+ <item name="wcf.acp.option.category.message.general.share"><![CDATA[Teilen]]></item>
+ <item name="wcf.acp.option.enable_bbcodes_default_value"><![CDATA[Darstellung von BBCodes aktivieren [Vorgabewert]]]></item>
+ <item name="wcf.acp.option.enable_html_default_value"><![CDATA[Darstellung von HTML aktivieren [Vorgabewert]]]></item>
+ <item name="wcf.acp.option.enable_smilies_default_value"><![CDATA[Darstellung von Smileys aktivieren [Vorgabewert]]]></item>
+ <item name="wcf.acp.option.pre_parse_default_value"><![CDATA[URLs automatisch erkennen [Vorgabewert]]]></item>
+ <item name="wcf.acp.option.show_signature_default_value"><![CDATA[Signatur anzeigen [Vorgabewert]]]></item>
+ <item name="wcf.acp.option.enable_share_buttons"><![CDATA[Buttons zum Teilen von Inhalten anzeigen]]></item>
+ <item name="wcf.acp.option.share_buttons_show_count"><![CDATA[Anzahl der Teilungen anzeigen]]></item>
</category>
<category name="wcf.acp.package">
<item name="wcf.imageViewer.next"><![CDATA[Nächstes Bild]]></item>
<item name="wcf.imageViewer.previous"><![CDATA[Vorheriges Bild]]></item>
</category>
+
+ <category name="wcf.message">
+ <item name="wcf.message.bbcode.code.copy"><![CDATA[Inhalt kopieren]]></item>
+ <item name="wcf.message.quote.insertAllQuotes"><![CDATA[Alle Zitate einfügen]]></item>
+ <item name="wcf.message.quote.insertQuote"><![CDATA[Zitat einfügen]]></item>
+ <item name="wcf.message.quote.insertSelectedQuotes"><![CDATA[Markierte Zitate einfügen]]></item>
+ <item name="wcf.message.quote.manageQuotes"><![CDATA[Zitate verwalten]]></item>
+ <item name="wcf.message.quote.quoteSelected"><![CDATA[Auswahl zitieren]]></item>
+ <item name="wcf.message.quote.showQuotes"><![CDATA[Zitate (#count#)]]></item>
+ <item name="wcf.message.quote.quoteMessage"><![CDATA[Zitieren]]></item>
+ <item name="wcf.message.quote.removeAllQuotes"><![CDATA[Alle Zitate entfernen]]></item>
+ <item name="wcf.message.quote.removeSelectedQuotes"><![CDATA[Markierte Zitate entfernen]]></item>
+ <item name="wcf.message.settings"><![CDATA[Einstellungen]]></item>
+ <item name="wcf.message.settings.enableBBCodes"><![CDATA[Darstellung von BBCodes aktivieren]]></item>
+ <item name="wcf.message.settings.enableBBCodes.description"><![CDATA[Sie können BBCodes zur Formatierung nutzen, sofern diese Option aktiviert ist.]]></item>
+ <item name="wcf.message.settings.enableHtml"><![CDATA[Darstellung von HTML aktivieren]]></item>
+ <item name="wcf.message.settings.enableHtml.description"><![CDATA[Wenn Sie diese Option aktiviert haben, können Sie HTML zur Formatierung verwenden.]]></item>
+ <item name="wcf.message.settings.enableSmilies"><![CDATA[Darstellung von Smileys aktivieren]]></item>
+ <item name="wcf.message.settings.enableSmilies.description"><![CDATA[Smiley-Code wird in Ihrem Beitrag automatisch als Smiley-Grafik dargestellt.]]></item>
+ <item name="wcf.message.settings.preParse"><![CDATA[URLs automatisch erkennen]]></item>
+ <item name="wcf.message.settings.preParse.description"><![CDATA[Internet-Adressen werden automatisch erkannt und umgewandelt.]]></item>
+ <item name="wcf.message.settings.showSignature"><![CDATA[Signatur anzeigen]]></item>
+ <item name="wcf.message.settings.showSignature.description"><![CDATA[Die im Profil eingestellte Signatur wird an diese Nachricht angehängt.]]></item>
+ <item name="wcf.message.share"><![CDATA[Teilen]]></item>
+ <item name="wcf.message.share.facebook"><![CDATA[Facebook]]></item>
+ <item name="wcf.message.share.google"><![CDATA[Google Plus]]></item>
+ <item name="wcf.message.share.permalink"><![CDATA[Permalink]]></item>
+ <item name="wcf.message.share.permalink.bbcode"><![CDATA[BBCode]]></item>
+ <item name="wcf.message.share.permalink.html"><![CDATA[HTML]]></item>
+ <item name="wcf.message.share.reddit"><![CDATA[Reddit]]></item>
+ <item name="wcf.message.share.twitter"><![CDATA[Twitter]]></item>
+ <item name="wcf.message.smilies"><![CDATA[Smileys]]></item>
+ <item name="wcf.message.button.extendedReply"><![CDATA[Erweiterte Antwort]]></item>
+ <item name="wcf.message.button.extendedEdit"><![CDATA[Erweiterte Bearbeitung]]></item>
+ <item name="wcf.message.new"><![CDATA[Neu]]></item>
+ <item name="wcf.message.error.censoredWordsFound"><![CDATA[Ihre Nachricht enthält folgende zensierte Wörter: {implode from=$censoredWords key=censoredWord item=number}{$censoredWord}{if $number > 1} ({#$number}×){/if}{/implode}]]></item>
+ <item name="wcf.message.error.disallowedBBCodes"><![CDATA[Ihre Nachricht enthält die folgenden BBCodes, die Sie nicht verwenden dürfen: {implode from=$disallowedBBCodes item=disallowedBBCode}{$disallowedBBCode}{/implode}]]></item>
+ <item name="wcf.message.error.editorAlreadyInUse"><![CDATA[Der Editor ist bereits aktiv, beenden Sie die Bearbeitung bevor Sie fortfahren.]]></item>
+ <item name="wcf.message.error.tooLong"><![CDATA[Ihre Nachricht ist zu lang. Es stehen maximal {#$maxTextLength} Zeichen zur Verfügung.]]></item>
+ </category>
<category name="wcf.page">
<item name="wcf.page.pageNo"><![CDATA[Seite {#$pageNo}]]></item>
<item name="wcf.acp.group.option.admin.content.bbcode.canManageBBCode"><![CDATA[Can manage BBCodes]]></item>
<item name="wcf.acp.group.option.category.admin.content.smiley"><![CDATA[Smilies]]></item>
<item name="wcf.acp.group.option.admin.content.smiley.canManageSmiley"><![CDATA[Can manage smilies]]></item>
+ <item name="wcf.acp.group.option.user.message.canUseSmilies"><![CDATA[Can use smilies]]></item>
+ <item name="wcf.acp.group.option.user.message.canUseHtml"><![CDATA[Can use HTML]]></item>
+ <item name="wcf.acp.group.option.user.message.canUseBBCodes"><![CDATA[Can use BBCodes]]></item>
+ <item name="wcf.acp.group.option.user.message.allowedBBCodes"><![CDATA[Allowed BBCodes]]></item>
+ <item name="wcf.acp.group.option.user.message.allowedBBCodes.description"><![CDATA[Selected BBCodes may be used by members of this group.]]></item>
</category>
<category name="wcf.acp.index">
<item name="wcf.acp.option.attachment_storage.description"><![CDATA[Changes storage location for attachments. Heads up! You are responsible to move already existing attachments to the new location.]]></item>
<item name="wcf.acp.option.module_attachment"><![CDATA[Attachments]]></item>
<item name="wcf.acp.option.module_smiley"><![CDATA[Smilies]]></item>
+ <item name="wcf.acp.option.category.message.censorship"><![CDATA[Censorship]]></item>
+ <item name="wcf.acp.option.censored_words"><![CDATA[Censored Words]]></item>
+ <item name="wcf.acp.option.censored_words.description"><![CDATA[One word per Line. Using at least one of these words within a message causes an immediate rejection.<br />
+<em>Use “*” to match parts: “wolt*” matches “woltlab”</em><br />
+<em>Use “~” to find splitted parts: “wolt~” matches “wolt-lab”</em>]]></item>
+ <item name="wcf.acp.option.enable_censorship"><![CDATA[Enable Censorship]]></item>
+ <item name="wcf.acp.option.enable_censorship.description"><![CDATA[Enables censorship for all messages containing the words below.]]></item>
+ <item name="wcf.acp.option.category.message.general.share"><![CDATA[Share]]></item>
+ <item name="wcf.acp.option.enable_bbcodes_default_value"><![CDATA[Enable BBCodes [default value]]]></item>
+ <item name="wcf.acp.option.enable_html_default_value"><![CDATA[Enable HTML [default value]]]></item>
+ <item name="wcf.acp.option.enable_smilies_default_value"><![CDATA[Enable smilies [default value]]]></item>
+ <item name="wcf.acp.option.pre_parse_default_value"><![CDATA[Detect URLs [default value]]]></item>
+ <item name="wcf.acp.option.show_signature_default_value"><![CDATA[Show signatures [default value]]]></item>
+ <item name="wcf.acp.option.enable_share_buttons"><![CDATA[Show content share button]]></item>
+ <item name="wcf.acp.option.share_buttons_show_count"><![CDATA[Show number of shares]]></item>
</category>
<category name="wcf.acp.package">
<item name="wcf.imageViewer.previous"><![CDATA[Previous Image]]></item>
</category>
+ <category name="wcf.message">
+ <item name="wcf.message.bbcode.code.copy"><![CDATA[Copy Contents]]></item>
+ <item name="wcf.message.quote.insertAllQuotes"><![CDATA[Insert All Quotes]]></item>
+ <item name="wcf.message.quote.insertQuote"><![CDATA[Insert Quote]]></item>
+ <item name="wcf.message.quote.insertSelectedQuotes"><![CDATA[Insert Marked Quotes]]></item>
+ <item name="wcf.message.quote.manageQuotes"><![CDATA[Manage Quotes]]></item>
+ <item name="wcf.message.quote.quoteSelected"><![CDATA[Quote Selection]]></item>
+ <item name="wcf.message.quote.showQuotes"><![CDATA[Quotes (#count#)]]></item>
+ <item name="wcf.message.quote.quoteMessage"><![CDATA[Quote]]></item>
+ <item name="wcf.message.quote.removeAllQuotes"><![CDATA[Remove All Quotes]]></item>
+ <item name="wcf.message.quote.removeSelectedQuotes"><![CDATA[Removed Marked Quotes]]></item>
+ <item name="wcf.message.settings"><![CDATA[Settings]]></item>
+ <item name="wcf.message.settings.enableBBCodes"><![CDATA[Enable BBCodes]]></item>
+ <item name="wcf.message.settings.enableBBCodes.description"><![CDATA[Allows BBCode usage once enabled.]]></item>
+ <item name="wcf.message.settings.enableHtml"><![CDATA[Enable HTML]]></item>
+ <item name="wcf.message.settings.enableHtml.description"><![CDATA[Allows HTML usage once enabled.]]></item>
+ <item name="wcf.message.settings.enableSmilies"><![CDATA[Enable smilies]]></item>
+ <item name="wcf.message.settings.enableSmilies.description"><![CDATA[Smilies will be displayed as a smiley-image.]]></item>
+ <item name="wcf.message.settings.preParse"><![CDATA[Detect links]]></item>
+ <item name="wcf.message.settings.preParse.description"><![CDATA[Links to webpages are automatically detected.]]></item>
+ <item name="wcf.message.settings.showSignature"><![CDATA[Show signature]]></item>
+ <item name="wcf.message.settings.showSignature.description"><![CDATA[Appends signature to this message, can be edited in your profile.]]></item>
+ <item name="wcf.message.share"><![CDATA[Share]]></item>
+ <item name="wcf.message.share.facebook"><![CDATA[Facebook]]></item>
+ <item name="wcf.message.share.google"><![CDATA[Google Plus]]></item>
+ <item name="wcf.message.share.permalink"><![CDATA[Permalink]]></item>
+ <item name="wcf.message.share.permalink.bbcode"><![CDATA[BBCode]]></item>
+ <item name="wcf.message.share.permalink.html"><![CDATA[HTML]]></item>
+ <item name="wcf.message.share.reddit"><![CDATA[Reddit]]></item>
+ <item name="wcf.message.share.twitter"><![CDATA[Twitter]]></item>
+ <item name="wcf.message.smilies"><![CDATA[Smilies]]></item>
+ <item name="wcf.message.button.extendedReply"><![CDATA[More Options]]></item>
+ <item name="wcf.message.button.extendedEdit"><![CDATA[More Options]]></item>
+ <item name="wcf.message.new"><![CDATA[New]]></item>
+ <item name="wcf.message.error.censoredWordsFound"><![CDATA[Message contains censored words: {implode from=$censoredWords key=censoredWord item=number}{$censoredWord}{if $number > 1} ({#$number}×){/if}{/implode}]]></item>
+ <item name="wcf.message.error.disallowedBBCodes"><![CDATA[Message contains disallowed BBCodes: {implode from=$disallowedBBCodes item=disallowedBBCode}{$disallowedBBCode}{/implode}]]></item>
+ <item name="wcf.message.error.editorAlreadyInUse"><![CDATA[Editor is already in use, please finish editing before continuing.]]></item>
+ <item name="wcf.message.error.tooLong"><![CDATA[Message is too long, must be below {#$maxTextLength} characters.]]></item>
+ </category>
+
<category name="wcf.page">
<item name="wcf.page.pageNo"><![CDATA[Page {#$pageNo}]]></item>
<item name="wcf.page.offline"><![CDATA[Page is currently in maintenance mode{if OFFLINE_MESSAGE != ''}:{else}.{/if}]]></item>