Merge branch '5.2'
authorMarcel Werk <burntime@woltlab.com>
Sat, 23 May 2020 17:49:57 +0000 (19:49 +0200)
committerMarcel Werk <burntime@woltlab.com>
Sat, 23 May 2020 17:49:57 +0000 (19:49 +0200)
135 files changed:
README.md
com.woltlab.wcf/mediaProvider.xml
com.woltlab.wcf/objectType.xml
com.woltlab.wcf/objectTypeDefinition.xml
com.woltlab.wcf/option.xml
com.woltlab.wcf/package.xml
com.woltlab.wcf/page.xml
com.woltlab.wcf/templates/__form.tpl
com.woltlab.wcf/templates/__formContainer.tpl
com.woltlab.wcf/templates/__formField.tpl
com.woltlab.wcf/templates/__labelSelection.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/__messageQuoteManager.tpl
com.woltlab.wcf/templates/ampFooter.tpl
com.woltlab.wcf/templates/article.tpl
com.woltlab.wcf/templates/articleList.tpl
com.woltlab.wcf/templates/categoryArticleList.tpl
com.woltlab.wcf/templates/headInclude.tpl
com.woltlab.wcf/templates/headIncludeRobotsMetaTag.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/mail.tpl [deleted file]
com.woltlab.wcf/templates/moderationList.tpl
com.woltlab.wcf/templates/moderationReport.tpl
com.woltlab.wcf/templates/pageMenuMobile.tpl
com.woltlab.wcf/templates/pollResult.tpl
com.woltlab.wcf/templates/unreadArticleList.tpl
com.woltlab.wcf/templates/user.tpl
com.woltlab.wcf/templates/userInformationButtons.tpl
com.woltlab.wcf/templates/userMenuSidebar.tpl
com.woltlab.wcf/templates/watchedArticleList.tpl
com.woltlab.wcf/userGroupOption.xml
com.woltlab.wcf/userNotificationEvent.xml
com.woltlab.wcf/userOption.xml
extra/package-lock.json
extra/package.json
spiderList/spiderList.xml
wcfsetup/install/files/acp/js/WCF.ACP.js
wcfsetup/install/files/acp/templates/__form.tpl
wcfsetup/install/files/acp/templates/__formContainer.tpl
wcfsetup/install/files/acp/templates/__formField.tpl
wcfsetup/install/files/acp/templates/__messageQuoteManager.tpl
wcfsetup/install/files/acp/templates/labelGroupAdd.tpl
wcfsetup/install/files/acp/templates/labelGroupList.tpl
wcfsetup/install/files/acp/templates/pageAdd.tpl
wcfsetup/install/files/acp/templates/styleAdd.tpl
wcfsetup/install/files/js/3rdParty/redactor2/redactor.combined.min.js
wcfsetup/install/files/js/WCF.Message.js
wcfsetup/install/files/js/WCF.User.js
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.js
wcfsetup/install/files/js/WoltLabSuite/Core/BootstrapFrontend.js
wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Popover.js
wcfsetup/install/files/js/WoltLabSuite/Core/I18n/Plural.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Template.grammar.jison
wcfsetup/install/files/js/WoltLabSuite/Core/Template.grammar.js
wcfsetup/install/files/js/WoltLabSuite/Core/Template.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/TwitterEmbed.js [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/StyleAddForm.class.php
wcfsetup/install/files/lib/acp/form/UserEditForm.class.php
wcfsetup/install/files/lib/acp/form/UserGroupOptionForm.class.php
wcfsetup/install/files/lib/acp/page/IndexPage.class.php
wcfsetup/install/files/lib/acp/page/LabelGroupListPage.class.php
wcfsetup/install/files/lib/acp/page/LabelListPage.class.php
wcfsetup/install/files/lib/core.functions.php
wcfsetup/install/files/lib/data/AbstractDatabaseObjectAction.class.php
wcfsetup/install/files/lib/data/DatabaseObjectList.class.php
wcfsetup/install/files/lib/data/IPopoverAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/IPopoverObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/article/LikeableArticle.class.php
wcfsetup/install/files/lib/data/article/LikeableArticleProvider.class.php
wcfsetup/install/files/lib/data/article/ViewableArticle.class.php
wcfsetup/install/files/lib/data/attachment/Attachment.class.php
wcfsetup/install/files/lib/data/attachment/AttachmentAction.class.php
wcfsetup/install/files/lib/data/comment/ViewableComment.class.php
wcfsetup/install/files/lib/data/comment/response/ViewableCommentResponse.class.php
wcfsetup/install/files/lib/data/language/Language.class.php
wcfsetup/install/files/lib/data/like/Like.class.php
wcfsetup/install/files/lib/data/moderation/queue/ModerationQueueAction.class.php
wcfsetup/install/files/lib/data/moderation/queue/ViewableModerationQueue.class.php
wcfsetup/install/files/lib/data/package/Package.class.php
wcfsetup/install/files/lib/data/package/installation/plugin/PackageInstallationPluginAction.class.php
wcfsetup/install/files/lib/data/trophy/Trophy.class.php
wcfsetup/install/files/lib/data/user/User.class.php
wcfsetup/install/files/lib/data/user/UserProfileAction.class.php
wcfsetup/install/files/lib/data/user/notification/event/UserNotificationEventAction.class.php
wcfsetup/install/files/lib/form/MailForm.class.php [deleted file]
wcfsetup/install/files/lib/form/SettingsForm.class.php
wcfsetup/install/files/lib/page/AbstractAuthedPage.class.php
wcfsetup/install/files/lib/page/ArticleAmpPage.class.php
wcfsetup/install/files/lib/page/ArticlePage.class.php
wcfsetup/install/files/lib/page/ModerationListPage.class.php
wcfsetup/install/files/lib/page/UserPage.class.php
wcfsetup/install/files/lib/system/bbcode/AttachmentBBCode.class.php
wcfsetup/install/files/lib/system/box/TagCloudBoxController.class.php
wcfsetup/install/files/lib/system/box/UserListBoxController.class.php
wcfsetup/install/files/lib/system/box/UserOnlineListBoxController.class.php
wcfsetup/install/files/lib/system/cli/command/PackageCLICommand.class.php
wcfsetup/install/files/lib/system/comment/manager/ArticleCommentManager.class.php
wcfsetup/install/files/lib/system/comment/manager/PageCommentManager.class.php
wcfsetup/install/files/lib/system/comment/manager/UserProfileCommentManager.class.php
wcfsetup/install/files/lib/system/condition/UserCoverPhotoCondition.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/condition/UserGroupCondition.class.php
wcfsetup/install/files/lib/system/condition/UserSignatureCondition.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/event/IEventListener.class.php
wcfsetup/install/files/lib/system/event/listener/AbstractEventListener.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/event/listener/IParameterizedEventListener.class.php
wcfsetup/install/files/lib/system/form/builder/FormDocument.class.php
wcfsetup/install/files/lib/system/form/builder/IFormDocument.class.php
wcfsetup/install/files/lib/system/form/builder/container/FormContainer.class.php
wcfsetup/install/files/lib/system/form/builder/container/IFormContainer.class.php
wcfsetup/install/files/lib/system/form/builder/container/wysiwyg/WysiwygFormContainer.class.php
wcfsetup/install/files/lib/system/io/RemoteFile.class.php
wcfsetup/install/files/lib/system/language/I18nPlural.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/menu/user/UserMenu.class.php
wcfsetup/install/files/lib/system/package/plugin/EventListenerPackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/ObjectTypePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/package/plugin/StylePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/reaction/ReactionHandler.class.php
wcfsetup/install/files/lib/system/search/acp/PackageACPSearchResultProvider.class.php
wcfsetup/install/files/lib/system/template/plugin/AnchorFunctionTemplatePlugin.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/template/plugin/PluralFunctionTemplatePlugin.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/template/plugin/UserFunctionTemplatePlugin.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/upload/DefaultUploadFileValidationStrategy.class.php
wcfsetup/install/files/lib/system/user/activity/event/ArticleCommentResponseUserActivityEvent.class.php
wcfsetup/install/files/lib/system/user/activity/event/PageCommentResponseUserActivityEvent.class.php
wcfsetup/install/files/lib/system/user/activity/event/ProfileCommentResponseUserActivityEvent.class.php
wcfsetup/install/files/lib/system/user/activity/event/TCommentResponseUserActivityEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/event/ArticleLikeUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/event/UserFollowFollowingUserNotificationEvent.class.php
wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentLikeUserNotificationEvent.class.php
wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentResponseLikeUserNotificationEvent.class.php
wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentResponseOwnerUserNotificationEvent.class.php
wcfsetup/install/files/lib/system/user/notification/event/UserProfileCommentUserNotificationEvent.class.php
wcfsetup/install/files/lib/system/user/notification/event/UserRegistrationUserNotificationEvent.class.php
wcfsetup/install/files/lib/util/MessageUtil.class.php
wcfsetup/install/files/style/layout/form.scss
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 523db126826835c869e142a65148fadab92080c6..6392766d10367c2598f38d07b01c78e60cd68575 100644 (file)
--- a/README.md
+++ b/README.md
@@ -6,7 +6,11 @@ _Formerly known as [WoltLab Community Framework](https://community.woltlab.com/t
 
 ## Version notes
 
-This is the stable version WoltLab Suite 5.2, please refer to the `master` branch to learn more about the development version.
+This is the **development** tree of WoltLab Suite Core representing the upcoming version of WoltLab Suite. Please use this branch for all pull requests, unless advised otherwise.
+
+- **Stable** version 5.2: [WSC 5.2](https://github.com/WoltLab/WCF/tree/5.2)
+- **Legacy** version 3.1: [WSC 3.1](https://github.com/WoltLab/WCF/tree/3.1)
+- **Legacy** version 3.0: [WSC 3.0](https://github.com/WoltLab/WCF/tree/3.0)
 
 ## About Redactor II
 
index 4f065ecaaa6a2b2d66e70253efc16b1bbcd382e9..71fa49551da3919991eba76ac91cba628b883a35 100644 (file)
@@ -78,9 +78,8 @@ https?://www.twitch.tv/[a-zA-Z0-9]+/v/(?<ID>[0-9]+)]]></regex>
                <provider name="twitter-tweet">
                        <title>Twitter Tweet</title>
                        <regex><![CDATA[https://twitter.com/(?<USERNAME>[0-9a-zA-Z_]+)/status/(?<ID>[0-9]+)]]></regex>
-                       <html><![CDATA[<script>window.twttr=function(t,e,r){var n,i=t.getElementsByTagName(e)[0],w=window.twttr||{};return t.getElementById(r)?w:((n=t.createElement(e)).id=r,n.src="https://platform.twitter.com/widgets.js",i.parentNode.insertBefore(n,i),w._e=[],w.ready=function(t){w._e.push(t)},w)}(document,"script","twitter-wjs");</script>
-<div data-wsc-twitter-tweet="{$ID}"></div>
-<script>twttr.ready(function(t){elBySelAll("[data-wsc-twitter-tweet]",void 0,function(t){elData(t,"wsc-twitter-tweet")&&(twttr.widgets.createTweet(elData(t,"wsc-twitter-tweet"),t,{dnt:!0}),elData(t,"wsc-twitter-tweet",""))})});</script>]]></html>
+                       <html><![CDATA[<div data-wsc-twitter-tweet="{$ID}"><a href="https://twitter.com/{$USERNAME}/status/{$ID}/">https://twitter.com/{$USERNAME}/status/{$ID}/</a></div>
+<script>require(["WoltLabSuite/Core/Ui/Message/TwitterEmbed"],function(t){t.embedAll()});</script>]]></html>
                </provider>
        </import>
        
index 6dddcb0c42c12d576213d4aa04e659de52b4b579..98963f777ecd672789c1ff741ef423d3c4839728 100644 (file)
                        <classname>wcf\system\user\notification\object\type\ArticleUserNotificationObjectType</classname>
                        <category>com.woltlab.wcf.page</category>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.likeableArticle.notification</name>
+                       <definitionname>com.woltlab.wcf.notification.objectType</definitionname>
+                       <classname>wcf\system\user\notification\object\type\LikeUserNotificationObjectType</classname>
+                       <category>com.woltlab.wcf.page</category>
+               </type>
                <type>
                        <name>com.woltlab.wcf.article.recentActivityEvent</name>
                        <definitionname>com.woltlab.wcf.user.recentActivityEvent</definitionname>
                        <classname>wcf\system\condition\UserAvatarCondition</classname>
                        <conditiongroup>general</conditiongroup>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.signature</name>
+                       <definitionname>com.woltlab.wcf.condition.userGroupAssignment</definitionname>
+                       <classname>wcf\system\condition\UserSignatureCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.coverPhoto</name>
+                       <definitionname>com.woltlab.wcf.condition.userGroupAssignment</definitionname>
+                       <classname>wcf\system\condition\UserCoverPhotoCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
                <type>
                        <name>com.woltlab.wcf.state</name>
                        <definitionname>com.woltlab.wcf.condition.userGroupAssignment</definitionname>
                        <classname>wcf\system\condition\UserAvatarCondition</classname>
                        <conditiongroup>general</conditiongroup>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.signature</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname>wcf\system\condition\UserSignatureCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.coverPhoto</name>
+                       <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
+                       <classname>wcf\system\condition\UserCoverPhotoCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
                <type>
                        <name>com.woltlab.wcf.state</name>
                        <definitionname>com.woltlab.wcf.condition.trophy</definitionname>
                        <classname>wcf\system\condition\UserAvatarCondition</classname>
                        <conditiongroup>general</conditiongroup>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.signature</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname>wcf\system\condition\UserSignatureCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.coverPhoto</name>
+                       <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
+                       <classname>wcf\system\condition\UserCoverPhotoCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
                <type>
                        <name>com.woltlab.wcf.state</name>
                        <definitionname>com.woltlab.wcf.bulkProcessing.user.condition</definitionname>
                        <classname>wcf\system\condition\UserRegistrationDateIntervalCondition</classname>
                        <conditiongroup>general</conditiongroup>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.lastActivityTime</name>
+                       <definitionname>com.woltlab.wcf.condition.userSearch</definitionname>
+                       <classname>wcf\system\condition\UserTimestampPropertyCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+                       <propertyname>lastActivityTime</propertyname>
+               </type>
                <type>
                        <name>com.woltlab.wcf.avatar</name>
                        <definitionname>com.woltlab.wcf.condition.userSearch</definitionname>
                        <classname>wcf\system\condition\UserAvatarCondition</classname>
                        <conditiongroup>general</conditiongroup>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.signature</name>
+                       <definitionname>com.woltlab.wcf.condition.userSearch</definitionname>
+                       <classname>wcf\system\condition\UserSignatureCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.coverPhoto</name>
+                       <definitionname>com.woltlab.wcf.condition.userSearch</definitionname>
+                       <classname>wcf\system\condition\UserCoverPhotoCondition</classname>
+                       <conditiongroup>general</conditiongroup>
+               </type>
                <type>
                        <name>com.woltlab.wcf.state</name>
                        <definitionname>com.woltlab.wcf.condition.userSearch</definitionname>
                        <classname>wcf\system\condition\user\trophy\UserTrophyExcludedTrophyCategoriesCondition</classname>
                </type>
                <!-- /trophy list box condition -->
+               <!-- user list box condition -->
+               <type>
+                       <name>com.woltlab.wcf.userGroup</name>
+                       <definitionname>com.woltlab.wcf.box.userList.condition</definitionname>
+                       <classname>wcf\system\condition\UserGroupCondition</classname>
+               </type>
+               <!-- /user list box condition -->
+               <!-- user online list box condition -->
+               <type>
+                       <name>com.woltlab.wcf.userGroup</name>
+                       <definitionname>com.woltlab.wcf.box.userOnlineList.condition</definitionname>
+                       <classname>wcf\system\condition\UserGroupCondition</classname>
+               </type>
+               <!-- /user online list box condition -->
                <!-- media -->
                <type>
                        <name>com.woltlab.wcf.media.caption</name>
index 4f666e40e2e602f5392aed8bce96333a451a4630..62bee674bf2edc1f4c1d1a2f1dc46df8371ae5ed 100644 (file)
                        <name>com.woltlab.wcf.box.recentActivityList.condition</name>
                        <interfacename>wcf\system\condition\IObjectListCondition</interfacename>
                </definition>
+               <definition>
+                       <name>com.woltlab.wcf.box.userList.condition</name>
+                       <interfacename>wcf\system\condition\IObjectListCondition</interfacename>
+               </definition>
                <definition>
                        <name>com.woltlab.wcf.box.userTrophyList.condition</name>
                        <interfacename>wcf\system\condition\IObjectListCondition</interfacename>
                </definition>
+               <definition>
+                       <name>com.woltlab.wcf.box.userOnlineList.condition</name>
+                       <interfacename>wcf\system\condition\IObjectListCondition</interfacename>
+               </definition>
                <!-- /box conditions -->
                <definition>
                        <name>com.woltlab.wcf.sitemap.object</name>
index 8d53043e68594fab516014be5f2b9ca8f588fa16..4b09f063639fbeb0674024e5f15d998dfc63fe24 100644 (file)
                                <optiontype>boolean</optiontype>
                                <defaultvalue>1</defaultvalue>
                        </option>
+                       <option name="module_amp">
+                               <categoryname>module.content</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>1</defaultvalue>
+                       </option>
                        <option name="module_smiley">
                                <categoryname>module.customization</categoryname>
                                <optiontype>boolean</optiontype>
index 4e027918c34c400c258b622eb54c05ab5229083d..68b2e34fd659c123a74c7178a6c3a76d863f8b40 100644 (file)
                
                <instruction type="script">acp/post_install.php</instruction>
        </instructions>
-       
+
+       <instructions type="update" fromversion="5.2.*">
+               <!-- Delete wcfsetup/install/files/lib/form/MailForm.class.php during the upgrade to make it 100% non-functional. -->
+       </instructions>
+
        <instructions type="update" fromversion="3.1.*">
                <instruction type="file">files_preUpdate.tar</instruction>
                <instruction type="script" flushCache="false">acp/update_com.woltlab.wcf_5.2_preUpdate.php</instruction>
index e9668807d60bcbd75943b9670c6a4c5babb45f57..ade42ac8e1a992338be5e944a49dd87353750306 100644 (file)
                                <title>Kennwort vergessen</title>
                        </content>
                </page>
-               <page identifier="com.woltlab.wcf.Mail">
-                       <pageType>system</pageType>
-                       <controller>wcf\form\MailForm</controller>
-                       <name language="de">E-Mail-Formular</name>
-                       <name language="en">Mail Form</name>
-                       <requireObjectID>1</requireObjectID>
-                       <content language="en">
-                               <title>Mail Form</title>
-                       </content>
-                       <content language="de">
-                               <title>E-Mail-Formular</title>
-                       </content>
-               </page>
                <page identifier="com.woltlab.wcf.NewPassword">
                        <pageType>system</pageType>
                        <controller>wcf\form\NewPasswordForm</controller>
@@ -822,4 +809,7 @@ E-Mail: [E-Mail-Adresse der verantwortlichen Stelle]</p><p><br></p><p>Verantwort
                        </content>
                </page>
        </import>
+       <delete>
+               <page identifier="com.woltlab.wcf.Mail" />
+       </delete>
 </data>
index 8719c7e04b314e89bf4813d64be5fe80a9ef11bf..d6ad54d0e6734b2f878fc27a813382009202a20e 100644 (file)
        </form>
 {/if}
 
+{if $form->needsRequiredFieldsInfo()}
+       <div class="section requiredFieldsInfo">
+               <p><span class="formFieldRequired">*</span> {lang}wcf.global.form.required{/lang}</p>
+               
+               {event name='requiredFieldsInfo'}
+       </div>
+{/if}
+
 <script data-relocate="true">
        {* after all dependencies have been added, check them *}
        require(['WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager'], function(FormBuilderFieldDependencyManager) {
index eec1c6606bafb02b1a7f00040ff9c4a65e161d2b..b7ff418e62a84edc09bac06d4757d816b50fca80 100644 (file)
@@ -6,11 +6,11 @@
        {if $container->getLabel() !== null}
                {if $container->getDescription() !== null}
                        <header class="sectionHeader">
-                               <h2 class="sectionTitle">{@$container->getLabel()}</h2>
+                               <h2 class="sectionTitle">{@$container->getLabel()}{if $container->markAsRequired()} <span class="formFieldRequired">*</span>{/if}</h2>
                                <p class="sectionDescription">{@$container->getDescription()}</p>
                        </header>
                {else}
-                       <h2 class="sectionTitle">{@$container->getLabel()}</h2>
+                       <h2 class="sectionTitle">{@$container->getLabel()}{if $container->markAsRequired()} <span class="formFieldRequired">*</span>{/if}</h2>
                {/if}
        {/if}
        
index d726d5aa69c0a87177d92d03f6dfccc1cdc2ee60..d59c467b7d915a33579687c696fa057fa9ffdf5f 100644 (file)
@@ -1,5 +1,5 @@
 <dl id="{@$field->getPrefixedId()}Container" {if !$field->getClasses()|empty} class="{implode from=$field->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{foreach from=$field->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{if !$field->checkDependencies()} style="display: none;"{/if}>
-       <dt>{if $field->getLabel() !== null}<label for="{@$field->getPrefixedId()}">{@$field->getLabel()}</label>{/if}</dt>
+       <dt>{if $field->getLabel() !== null}<label for="{@$field->getPrefixedId()}">{@$field->getLabel()}</label>{if $field->isRequired()} <span class="formFieldRequired">*</span>{/if}{/if}</dt>
        <dd>
                {@$field->getFieldHtml()}
                
diff --git a/com.woltlab.wcf/templates/__labelSelection.tpl b/com.woltlab.wcf/templates/__labelSelection.tpl
new file mode 100644 (file)
index 0000000..e8ff3d2
--- /dev/null
@@ -0,0 +1,32 @@
+{foreach from=$labelGroups item=labelGroup}
+       {if $labelGroup|count}
+               <dt>##<label>{$labelGroup->getTitle()}</label></dt>
+               <dd>
+                       <ul class="labelList jsOnly">
+                               <li class="dropdown labelChooser" id="labelGroup{@$labelGroup->groupID}" data-group-id="{@$labelGroup->groupID}">
+                                       <div class="dropdownToggle" data-toggle="labelGroup{@$labelGroup->groupID}"><span class="badge label">{lang}wcf.label.none{/lang}</span></div>
+                                       <div class="dropdownMenu">
+                                               <ul class="scrollableDropdownMenu">
+                                                       {foreach from=$labelGroup item=label}
+                                                               <li data-label-id="{@$label->labelID}"><span><span class="badge label{if $label->getClassNames()} {@$label->getClassNames()}{/if}">{$label->getTitle()}</span></span></li>
+                                                       {/foreach}
+                                               </ul>
+                                       </div>
+                               </li>
+                       </ul>
+                       {if $noLabelSelectionNoScript|empty}
+                               <noscript>
+                                       {foreach from=$labelGroups item=labelGroup}
+                                               <select name="labelIDs[{@$labelGroup->groupID}]">
+                                                       <option value="0">{lang}wcf.label.none{/lang}</option>
+                                                       <option value="-1">{lang}wcf.label.withoutSelection{/lang}</option>
+                                                       {foreach from=$labelGroup item=label}
+                                                               <option value="{@$label->labelID}"{if $labelIDs[$labelGroup->groupID]|isset && $labelIDs[$labelGroup->groupID] == $label->labelID} selected{/if}>{$label->getTitle()}</option>
+                                                       {/foreach}
+                                               </select>
+                                       {/foreach}
+                               </noscript>
+                       {/if}
+               </dd>
+       {/if}
+{/foreach}
index fd0798d128460f82fa181a63153db1e9385ac5d0..45abd51bfe275b236ba30f274d9ceb816c908f95 100644 (file)
@@ -6,7 +6,7 @@ WCF.Language.addObject({
        'wcf.message.quote.quoteAndReply': '{lang}wcf.message.quote.quoteAndReply{/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}'
+       'wcf.message.quote.showQuotes': '{lang __literal=true}wcf.message.quote.showQuotes{/lang}'
 });
 
 {if !$wysiwygSelector|isset}{assign var=wysiwygSelector value=''}{/if}
index 68dfff497e7def488d945626cd67d68bf54a535b..b94e36991c37ce6f5a93e639773065249dc28a58 100644 (file)
                                {/foreach}
                        </ol>
                        {if $__wcf->getBoxHandler()->getBoxByIdentifier('com.woltlab.wcf.FooterMenu')}
-                               <ol>
-                                       {foreach from=$__wcf->getBoxHandler()->getBoxByIdentifier('com.woltlab.wcf.FooterMenu')->getMenu()->getMenuItemNodeList() item=menuItemNode}
-                                               <li><a href="{$menuItemNode->getURL()}">{$menuItemNode->getTitle()}</a></li>
-                                       {/foreach}
-                               </ol>
+                               {hascontent}
+                                       <ol>
+                                               {content}       
+                                                       {foreach from=$__wcf->getBoxHandler()->getBoxByIdentifier('com.woltlab.wcf.FooterMenu')->getMenu()->getMenuItemNodeList() item=menuItemNode}
+                                                               {if $menuItemNode->getDepth() == 1 || $menuItemNode->getParentNode()->isActiveNode()}
+                                                               <li>
+                                                                       <a href="{$menuItemNode->getURL()}">{$menuItemNode->getTitle()}</a>
+               
+                                                                       {if $menuItemNode->hasChildren() && $menuItemNode->isActiveNode()}<ol>{else}</li>{/if}
+               
+                                                                       {if !$menuItemNode->hasChildren() && $menuItemNode->isLastSibling()}
+                                                                               {@"</ol></li>"|str_repeat:$menuItemNode->getOpenParentNodes()}
+                                                                       {/if}
+                                                               {/if}
+                                                       {/foreach}
+                                               {/content}
+                                       </ol>
+                               {/hascontent}
                        {/if}
                        <h3>{lang}wcf.menu.page.location{/lang}</h3>
                        <ol class="breadcrumbs">
index 9cb2f6d7926a13792c57488d470eeb9d21564181..b84c63f62bb5373323301f0e8bbd827c92f98ae9 100644 (file)
                        {/if}
                {/foreach}
        {/if}
-       <link rel="amphtml" href="{link controller='ArticleAmp' object=$articleContent}{/link}">
+       {if MODULE_AMP}
+               <link rel="amphtml" href="{link controller='ArticleAmp' object=$articleContent}{/link}">
+       {/if}
 {/capture}
 
 {include file='header'}
index ecccf4db062804000a6db9a34a12a11661df8ceb..1e7c297af51f1f7a9bd4732b882754b255dec6fa 100644 (file)
                                
                                <div class="boxContent">
                                        <dl>
-                                               {foreach from=$labelGroups item=labelGroup}
-                                                       {if $labelGroup|count}
-                                                               <dt><label>{$labelGroup->getTitle()}</label></dt>
-                                                               <dd>
-                                                                       <ul class="labelList jsOnly">
-                                                                               <li class="dropdown labelChooser" id="labelGroup{@$labelGroup->groupID}" data-group-id="{@$labelGroup->groupID}">
-                                                                                       <div class="dropdownToggle" data-toggle="labelGroup{@$labelGroup->groupID}"><span class="badge label">{lang}wcf.label.none{/lang}</span></div>
-                                                                                       <div class="dropdownMenu">
-                                                                                               <ul class="scrollableDropdownMenu">
-                                                                                                       {foreach from=$labelGroup item=label}
-                                                                                                               <li data-label-id="{@$label->labelID}"><span><span class="badge label{if $label->getClassNames()} {@$label->getClassNames()}{/if}">{$label->getTitle()}</span></span></li>
-                                                                                                       {/foreach}
-                                                                                               </ul>
-                                                                                       </div>
-                                                                               </li>
-                                                                       </ul>
-                                                                       <noscript>
-                                                                               {foreach from=$labelGroups item=labelGroup}
-                                                                                       <select name="labelIDs[{@$labelGroup->groupID}]">
-                                                                                               <option value="0">{lang}wcf.label.none{/lang}</option>
-                                                                                               <option value="-1">{lang}wcf.label.withoutSelection{/lang}</option>
-                                                                                               {foreach from=$labelGroup item=label}
-                                                                                                       <option value="{@$label->labelID}"{if $labelIDs[$labelGroup->groupID]|isset && $labelIDs[$labelGroup->groupID] == $label->labelID} selected{/if}>{$label->getTitle()}</option>
-                                                                                               {/foreach}
-                                                                                       </select>
-                                                                               {/foreach}
-                                                                       </noscript>
-                                                               </dd>
-                                                       {/if}
-                                               {/foreach}
+                                               {include file='__labelSelection'}
                                        </dl>
                                        <div class="formSubmit">
                                                <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
index dedc0181d4d6daafa79d3ee901038635f2184524..527b13f3f8241e29ad9d2aeab4f113fedcd12ee8 100644 (file)
                                
                                <div class="boxContent">
                                        <dl>
-                                               {foreach from=$labelGroups item=labelGroup}
-                                                       {if $labelGroup|count}
-                                                               <dt><label>{$labelGroup->getTitle()}</label></dt>
-                                                               <dd>
-                                                                       <ul class="labelList jsOnly">
-                                                                               <li class="dropdown labelChooser" id="labelGroup{@$labelGroup->groupID}" data-group-id="{@$labelGroup->groupID}">
-                                                                                       <div class="dropdownToggle" data-toggle="labelGroup{@$labelGroup->groupID}"><span class="badge label">{lang}wcf.label.none{/lang}</span></div>
-                                                                                       <div class="dropdownMenu">
-                                                                                               <ul class="scrollableDropdownMenu">
-                                                                                                       {foreach from=$labelGroup item=label}
-                                                                                                               <li data-label-id="{@$label->labelID}"><span><span class="badge label{if $label->getClassNames()} {@$label->getClassNames()}{/if}">{$label->getTitle()}</span></span></li>
-                                                                                                       {/foreach}
-                                                                                               </ul>
-                                                                                       </div>
-                                                                               </li>
-                                                                       </ul>
-                                                                       <noscript>
-                                                                               {foreach from=$labelGroups item=labelGroup}
-                                                                                       <select name="labelIDs[{@$labelGroup->groupID}]">
-                                                                                               <option value="0">{lang}wcf.label.none{/lang}</option>
-                                                                                               <option value="-1">{lang}wcf.label.withoutSelection{/lang}</option>
-                                                                                               {foreach from=$labelGroup item=label}
-                                                                                                       <option value="{@$label->labelID}"{if $labelIDs[$labelGroup->groupID]|isset && $labelIDs[$labelGroup->groupID] == $label->labelID} selected{/if}>{$label->getTitle()}</option>
-                                                                                               {/foreach}
-                                                                                       </select>
-                                                                               {/foreach}
-                                                                       </noscript>
-                                                               </dd>
-                                                       {/if}
-                                               {/foreach}
+                                               {include file='__labelSelection'}
                                        </dl>
                                        <div class="formSubmit">
                                                <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
index f822ef17b891a56389bc4850b2b4b073583ecf6e..51a29c2ebcd0c590b03b6c52a323a2b3ff4d2c98 100644 (file)
@@ -1,7 +1,7 @@
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <meta name="format-detection" content="telephone=no">
-{if $allowSpidersToIndexThisPage|empty && ($__wcf->getActivePage() == null || !$__wcf->getActivePage()->allowSpidersToIndex)}<meta name="robots" content="noindex,nofollow">{/if}
+{include file='headIncludeRobotsMetaTag'}
 {implode from=$__wcf->getMetaTagHandler() item=__metaTag glue="\n"}{@$__metaTag}{/implode}
 {event name='metaTags'}
 
diff --git a/com.woltlab.wcf/templates/headIncludeRobotsMetaTag.tpl b/com.woltlab.wcf/templates/headIncludeRobotsMetaTag.tpl
new file mode 100644 (file)
index 0000000..bb1bf50
--- /dev/null
@@ -0,0 +1 @@
+{if ($allowSpidersToIndexThisPage|empty && ($__wcf->getActivePage() == null || !$__wcf->getActivePage()->allowSpidersToIndex)) || ($allowSpidersToIndexThisPage|isset && $allowSpidersToIndexThisPage == false)}<meta name="robots" content="noindex">{/if}
diff --git a/com.woltlab.wcf/templates/mail.tpl b/com.woltlab.wcf/templates/mail.tpl
deleted file mode 100644 (file)
index 6c64a29..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-{include file='header'}
-
-{include file='formError'}
-
-<form method="post" action="{link controller='Mail' object=$user}{/link}">
-       <section class="section">
-               <h2 class="sectionTitle">{lang}wcf.user.mail.information{/lang}</h2>
-               
-               <dl{if $errorField == 'subject'} class="formError"{/if}>
-                       <dt><label for="subject">{lang}wcf.user.mail.subject{/lang}</label></dt>
-                       <dd>
-                               <input type="text" id="subject" name="subject" value="{$subject}" required class="long">
-                               {if $errorField == 'subject'}
-                                       <small class="innerError">
-                                               {if $errorType == 'empty'}
-                                                       {lang}wcf.global.form.error.empty{/lang}
-                                               {else}
-                                                       {lang}wcf.user.mail.subject.error.{@$errorType}{/lang}
-                                               {/if}
-                                       </small>
-                               {/if}
-                       </dd>
-               </dl>
-               
-               {if $__wcf->user->userID}
-                       <dl>
-                               <dt></dt>
-                               <dd><label><input type="checkbox" name="showAddress" value="1"{if $showAddress == 1} checked{/if}> {lang}wcf.user.mail.showAddress{/lang}</label></dd>
-                       </dl>
-               {else}
-                       <dl{if $errorField == 'email'} class="formError"{/if}>
-                               <dt><label for="email">{lang}wcf.user.mail.senderEmail{/lang}</label></dt>
-                               <dd>
-                                       <input type="email" id="email" name="email" value="{$email}" required class="medium">
-                                       {if $errorField == 'email'}
-                                               <small class="innerError">
-                                                       {if $errorType == 'empty'}
-                                                               {lang}wcf.global.form.error.empty{/lang}
-                                                       {elseif $errorType == 'invalid'}
-                                                               {lang}wcf.user.email.error.invalid{/lang}
-                                                       {else}
-                                                               {lang}wcf.user.mail.senderEmail.error.{@$errorType}{/lang}
-                                                       {/if}
-                                               </small>
-                                       {/if}
-                               </dd>
-                       </dl>
-               {/if}
-               
-               {event name='informationFields'}
-       </section>
-       
-       <section class="section">
-               <h2 class="sectionTitle">{lang}wcf.user.mail.message{/lang}</h2>
-               
-               <dl class="wide{if $errorField == 'message'} formError{/if}">
-                       <dd>
-                               <textarea rows="15" cols="40" name="message" id="message" required>{$message}</textarea>
-                               {if $errorField == 'message'}
-                                       <small class="innerError">
-                                               {if $errorType == 'empty'}
-                                                       {lang}wcf.global.form.error.empty{/lang}
-                                               {else}
-                                                       {lang}wcf.user.mail.message.error.{@$errorType}{/lang}
-                                               {/if}
-                                       </small>
-                               {/if}
-                       </dd>
-               </dl>
-               
-               {event name='messageFields'}
-       </section>
-       
-       {event name='sections'}
-       
-       {include file='captcha' supportsAsyncCaptcha=true}
-       
-       <div class="formSubmit">
-               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
-               {@SECURITY_TOKEN_INPUT_TAG}
-       </div>
-</form>
-
-{include file='footer'}
index 3ac04c656b870f00b8c309689f254c1c2c603eeb..f9f30ea21fff01fdc737dce485189350c0e08c97 100644 (file)
@@ -3,47 +3,6 @@
 {capture assign='contentTitle'}{if $status == 2}{lang}wcf.moderation.doneItems{/lang}{else}{lang}wcf.moderation.outstandingItems{/lang}{/if} <span class="badge">{#$items}</span>{/capture}
 
 {capture assign='sidebarRight'}
-       <section class="box" data-static-box-identifier="com.woltlab.wcf.ModerationListFilters">
-               {* moderation type *}
-               <h2 class="boxTitle">{lang}wcf.moderation.filterByType{/lang}</h2>
-               
-               <nav class="boxContent">
-                       <ul class="boxMenu">
-                               <li{if $definitionID == 0} class="active"{/if}><a class="boxMenuLink" href="{link controller='ModerationList'}definitionID=0&assignedUserID={@$assignedUserID}&status={@$status}&pageNo={@$pageNo}&sortField={@$sortField}&sortOrder={@$sortOrder}{/link}">{lang}wcf.moderation.type.all{/lang}</a></li>
-                               {foreach from=$availableDefinitions key=__definitionID item=definitionName}
-                                       <li{if $definitionID == $__definitionID} class="active"{/if}><a class="boxMenuLink" href="{link controller='ModerationList'}definitionID={@$__definitionID}&assignedUserID={@$assignedUserID}&status={@$status}&pageNo={@$pageNo}&sortField={@$sortField}&sortOrder={@$sortOrder}{/link}">{lang}wcf.moderation.type.{$definitionName}{/lang}</a></li>
-                               {/foreach}
-                               
-                               {event name='sidebarModerationType'}
-                       </ul>
-               </nav>
-               
-               {* assigned user *}
-               <h2 class="boxTitle">{lang}wcf.moderation.filterByUser{/lang}</h2>
-               
-               <nav class="boxContent">
-                       <ul class="boxMenu">
-                               <li{if $assignedUserID == -1} class="active"{/if}><a class="boxMenuLink" href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID=-1&status={@$status}&pageNo={@$pageNo}&sortField={@$sortField}&sortOrder={@$sortOrder}{/link}">{lang}wcf.moderation.filterByUser.allEntries{/lang}</a></li>
-                               <li{if $assignedUserID == 0} class="active"{/if}><a class="boxMenuLink" href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID=0&status={@$status}&pageNo={@$pageNo}&sortField={@$sortField}&sortOrder={@$sortOrder}{/link}">{lang}wcf.moderation.filterByUser.nobody{/lang}</a></li>
-                               <li{if $assignedUserID == $__wcf->getUser()->userID} class="active"{/if}><a class="boxMenuLink" href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID={@$__wcf->getUser()->userID}&status={@$status}&pageNo={@$pageNo}&sortField={@$sortField}&sortOrder={@$sortOrder}{/link}">{lang}wcf.moderation.filterByUser.myself{/lang}</a></li>
-                               
-                               {event name='sidebarAssignedUser'}
-                       </ul>
-               </nav>
-               
-               {* status *}
-               <h2 class="boxTitle">{lang}wcf.moderation.status{/lang}</h2>
-               
-               <nav class="boxContent">
-                       <ul class="boxMenu">
-                               <li{if $status == -1} class="active"{/if}><a class="boxMenuLink" href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID={@$assignedUserID}&status=-1&pageNo={@$pageNo}&sortField={@$sortField}&sortOrder={@$sortOrder}{/link}">{lang}wcf.moderation.status.outstanding{/lang}</a></li>
-                               <li{if $status == 2} class="active"{/if}><a class="boxMenuLink" href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID={@$assignedUserID}&status=2&pageNo={@$pageNo}&sortField={@$sortField}&sortOrder={@$sortOrder}{/link}">{lang}wcf.moderation.status.done{/lang}</a></li>
-                               
-                               {event name='sidebarStatus'}
-                       </ul>
-               </nav>
-       </section>
-       
        {event name='sidebarBoxes'}
 {/capture}
 
                <ol class="tabularList">
                        <li class="tabularListRow tabularListRowHead">
                                <ol class="tabularListColumns">
-                                       <li class="columnSubject">{lang}wcf.moderation.title{/lang}</li>
-                                       <li class="columnStats{if $sortField == 'comments'} active {@$sortOrder}{/if}"><a href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID={@$assignedUserID}&status={@$status}&pageNo={@$pageNo}&sortField=comments&sortOrder={if $sortField == 'comments' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.comments{/lang}</a></li>
-                                       <li class="columnLastPost{if $sortField === 'lastChangeTime'} active {@$sortOrder}{/if}"><a href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID={@$assignedUserID}&status={@$status}&pageNo={@$pageNo}&sortField=lastChangeTime&sortOrder={if $sortField == 'lastChangeTime' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.moderation.lastChangeTime{/lang}</a></li>
-                                       
-                                       {event name='columnHeads'}
+                                       <li class="columnSort">
+                                               <ul class="inlineList">
+                                                       <li>
+                                                               <a href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID={@$assignedUserID}&status={@$status}&pageNo={@$pageNo}&sortField={$sortField}&sortOrder={if $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">
+                                                                       <span class="icon icon16 fa-sort-amount-{$sortOrder|strtolower} jsTooltip" title="{lang}wcf.search.sortBy{/lang} ({lang}wcf.global.sortOrder.{if $sortOrder === 'ASC'}ascending{else}descending{/if}{/lang})"></span>
+                                                               </a>
+                                                       </li>
+                                                       <li>
+                                                               <div class="dropdown">
+                                                                       <span class="dropdownToggle">{lang}wcf.moderation.{$sortField}{/lang}</span>
+               
+                                                                       <ul class="dropdownMenu">
+                                                                               {foreach from=$validSortFields item=_sortField}
+                                                                                       <li{if $_sortField === $sortField} class="active"{/if}><a href="{link controller='ModerationList'}definitionID={@$definitionID}&assignedUserID={@$assignedUserID}&status={@$status}&pageNo={@$pageNo}&sortField={$_sortField}&sortOrder={if $sortField == $_sortField && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.moderation.{$_sortField}{/lang}</a></li>
+                                                                               {/foreach}
+                                                                       </ul>
+                                                               </div>
+                                                       </li>
+                                               </ul>
+                                       </li>
+                                       {hascontent}
+                                               <li class="columnFilter">
+                                                       <ul class="inlineList">
+                                                               {content}
+                                                                       {if $definitionID}
+                                                                               <li>
+                                                                                       <span class="icon icon16 fa-tag jsTooltip" title="{lang}wcf.moderation.filterByType{/lang}"></span>
+                                                                                       {lang}wcf.moderation.type.{$availableDefinitions[$definitionID]}{/lang}
+                                                                               </li>
+                                                                       {/if}
+                                                                       
+                                                                       {if !$assignedUserID || $assignedUserID == $__wcf->getUser()->userID}
+                                                                               <li>
+                                                                                       <span class="icon icon16 fa-user jsTooltip" title="{lang}wcf.moderation.filterByUser{/lang}"></span>
+                                                                                       {if !$assignedUserID}
+                                                                                               {lang}wcf.moderation.filterByUser.nobody{/lang}
+                                                                                       {else}
+                                                                                               {lang}wcf.moderation.filterByUser.myself{/lang}
+                                                                                       {/if}
+                                                                               </li>
+                                                                       {/if}
+                                                                       
+                                                                       {if $status == -1 || $status == 2}
+                                                                               <li>
+                                                                                       {if $status == -1}
+                                                                                               <span class="icon icon16 fa-circle-o jsTooltip" title="{lang}wcf.moderation.status{/lang}"></span>
+                                                                                               {lang}wcf.moderation.status.outstanding{/lang}
+                                                                                       {else}
+                                                                                               <span class="icon icon16 fa-check-circle-o jsTooltip" title="{lang}wcf.moderation.status{/lang}"></span>
+                                                                                               {lang}wcf.moderation.status.done{/lang}
+                                                                                       {/if}
+                                                                               </li>
+                                                                       {/if}
+                                                               {/content}
+                                                       </ul>
+                                               </li>
+                                       {/hascontent}
+                                       <li class="columnApplyFilter jsOnly">
+                                               <button class="small jsStaticDialog" data-dialog-id="moderationListSortFilter"><span class="icon icon16 fa-filter"></span> {lang}wcf.global.filter{/lang}</button>
+                                       </li>
                                </ol>
                        </li>
                        
                {/hascontent}
        </footer>
 {else}
-       <p class="info" role="status">{lang}wcf.global.noItems{/lang}</p>
+       <p class="info" role="status">{lang}wcf.moderation.noEntries{/lang}</p>
 {/if}
 
+<div id="moderationListSortFilter" class="jsStaticDialogContent" data-title="{lang}wcf.moderation.filter{/lang}">
+       <form method="post" action="{link controller='ModerationList'}{/link}">
+               <div class="section">
+                       <dl>
+                               <dt><label for="definitionID">{lang}wcf.moderation.filterByType{/lang}</label></dt>
+                               <dd>
+                                       <select name="definitionID" id="definitionID">
+                                               <option value="0">{lang}wcf.moderation.type.all{/lang}</option>
+                                               {foreach from=$availableDefinitions key=__definitionID item=definitionName}
+                                                       <option value="{$__definitionID}"{if $__definitionID == $definitionID} selected{/if}>{lang}wcf.moderation.type.{$definitionName}{/lang}</option>
+                                               {/foreach}
+
+                                               {event name='filterModerationType'}
+                                       </select>
+                               </dd>
+                       </dl>
+                       
+                       <dl>
+                               <dt><label for="assignedUserID">{lang}wcf.moderation.filterByUser{/lang}</label></dt>
+                               <dd>
+                                       <select name="assignedUserID" id="assignedUserID">
+                                               <option value="-1"{if $assignedUserID == -1} selected{/if}>{lang}wcf.moderation.filterByUser.allEntries{/lang}</option>
+                                               <option value="0"{if $assignedUserID == 0} selected{/if}>{lang}wcf.moderation.filterByUser.nobody{/lang}</option>
+                                               <option value="{@$__wcf->getUser()->userID}"{if $assignedUserID == $__wcf->getUser()->userID} selected{/if}>{lang}wcf.moderation.filterByUser.myself{/lang}</option>
+                                               
+                                               {event name='filterAssignedUser'}
+                                       </select>
+                               </dd>
+                       </dl>
+
+                       <dl>
+                               <dt><label for="status">{lang}wcf.moderation.status{/lang}</label></dt>
+                               <dd>
+                                       <select name="status" id="status">
+                                               <option value="-1"{if $status == -1} selected{/if}>{lang}wcf.moderation.status.outstanding{/lang}</option>
+                                               <option value="2"{if $status == 2} selected{/if}>{lang}wcf.moderation.status.done{/lang}</option>
+                                               
+                                               {event name='filterStatus'}
+                                       </select>
+                               </dd>
+                       </dl>
+               </div>
+
+               <div class="formSubmit">
+                       <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+                       <a href="{link controller='ModerationList'}{/link}" class="button">{lang}wcf.global.button.reset{/lang}</a>
+                       <input type="hidden" name="sortField" value="{$sortField}">
+                       <input type="hidden" name="sortOrder" value="{$sortOrder}">
+               </div>
+       </form>
+</div>
+
 <script data-relocate="true">
        $(function() {
                new WCF.Moderation.Queue.MarkAsRead();
index 35d30bd7b6e46d2f47f3b5443b3ed43c4fb5db83..197b5e2d36a98e15cbac7cb2f0c5e053a933ef55 100644 (file)
 
 {include file='formError'}
 
-<section class="section">
-       <header class="sectionHeader">
-               <h2 class="sectionTitle">{lang}wcf.moderation.report.reportedContent{/lang}</h2>
-               <p class="sectionDescription">{lang}wcf.moderation.type.{@$queue->getObjectTypeName()}{/lang}</p>
-       </header>
-       
-       {@$reportedContent}
-</section>
-
 <section class="section">
        <h2 class="sectionTitle">{lang}wcf.moderation.report.reportedBy{/lang}</h2>
        
        </div>
 </section>
 
+<section class="section">
+       <header class="sectionHeader">
+               <h2 class="sectionTitle">{lang}wcf.moderation.report.reportedContent{/lang}</h2>
+               <p class="sectionDescription">{lang}wcf.moderation.type.{@$queue->getObjectTypeName()}{/lang}</p>
+       </header>
+       
+       {@$reportedContent}
+</section>
+
 {include file='__commentJavaScript' commentContainerID='moderationQueueCommentList'}
 
 <section id="comments" class="section sectionContainerList moderationComments">
index 587d4cd3ea989ad276c04803126acb81aedc56ae..a560be0c069703975bb80317caac3e0a6f789566 100644 (file)
                                
                                {if $menuItemNode->hasChildren()}<ol class="menuOverlayItemList">{else}</li>{/if}
                                        
-                                       {if !$menuItemNode->hasChildren() && $menuItemNode->isLastSibling()}
-                                               {@"</ol></li>"|str_repeat:$menuItemNode->getOpenParentNodes()}
-                                       {/if}
+                               {if !$menuItemNode->hasChildren() && $menuItemNode->isLastSibling()}
+                                       {@"</ol></li>"|str_repeat:$menuItemNode->getOpenParentNodes()}
+                               {/if}
                {/foreach}
+
+                {if $__wcf->getBoxHandler()->getBoxByIdentifier('com.woltlab.wcf.FooterMenu')}
+                       {hascontent}
+                               <li class="menuOverlayItemSpacer"></li>
+                               {content}       
+                                       {foreach from=$__wcf->getBoxHandler()->getBoxByIdentifier('com.woltlab.wcf.FooterMenu')->getMenu()->getMenuItemNodeList() item=menuItemNode}
+                                               {* Does not use `data-identifier` to prevent compatibility issues. See https://github.com/WoltLab/WCF/pull/2813 *}
+                                               <li class="menuOverlayItem" data-mobile-identifier="{@$menuItemNode->identifier}">
+                                                       {assign var=__outstandingItems value=$menuItemNode->getOutstandingItems()}
+                                                       <a href="{$menuItemNode->getURL()}" class="menuOverlayItemLink{if $__outstandingItems} menuOverlayItemBadge{/if}{if $menuItemNode->isActiveNode()} active{/if}"{if $menuItemNode->isExternalLink() && EXTERNAL_LINK_TARGET_BLANK} target="_blank"{/if}>
+                                                               <span class="menuOverlayItemTitle">{$menuItemNode->getTitle()}</span>
+                                                               {if $__outstandingItems}
+                                                                       <span class="badge badgeUpdate">{#$__outstandingItems}</span>
+                                                               {/if}
+                                                       </a>
+                       
+                                                       {if $menuItemNode->hasChildren()}<ol class="menuOverlayItemList">{else}</li>{/if}
+                       
+                                                       {if !$menuItemNode->hasChildren() && $menuItemNode->isLastSibling()}
+                                                               {@"</ol></li>"|str_repeat:$menuItemNode->getOpenParentNodes()}
+                                                       {/if}
+                                       {/foreach}
+                               {/content}
+                       {/hascontent}
+               {/if}
+
                <li class="menuOverlayItemSpacer"></li>
                <li class="menuOverlayItem" data-more="com.woltlab.wcf.search">
                        <a href="#" class="menuOverlayItemLink box24">
index ba91fc32c31c69d34937c71ee19e30fd0826a115..5c3b239a6647cd8d10cf1026c47925f2518b816a 100644 (file)
@@ -10,4 +10,8 @@
                        </div>
                </li>
        {/foreach}
-</ol>
\ No newline at end of file
+</ol>
+
+{if $poll->endTime && !$poll->isFinished()}
+       <p><small>{lang}wcf.poll.endTimeInfo{/lang}</small></p>
+{/if}
index e0851c72b129faf3d2898f2ab09a7cb6e18b3b5c..db54d216f884b7450b3ecc030426718a869afa6d 100644 (file)
                                
                                <div class="boxContent">
                                        <dl>
-                                               {foreach from=$labelGroups item=labelGroup}
-                                                       {if $labelGroup|count}
-                                                               <dt><label>{$labelGroup->getTitle()}</label></dt>
-                                                               <dd>
-                                                                       <ul class="labelList jsOnly">
-                                                                               <li class="dropdown labelChooser" id="labelGroup{@$labelGroup->groupID}" data-group-id="{@$labelGroup->groupID}">
-                                                                                       <div class="dropdownToggle" data-toggle="labelGroup{@$labelGroup->groupID}"><span class="badge label">{lang}wcf.label.none{/lang}</span></div>
-                                                                                       <div class="dropdownMenu">
-                                                                                               <ul class="scrollableDropdownMenu">
-                                                                                                       {foreach from=$labelGroup item=label}
-                                                                                                               <li data-label-id="{@$label->labelID}"><span><span class="badge label{if $label->getClassNames()} {@$label->getClassNames()}{/if}">{$label->getTitle()}</span></span></li>
-                                                                                                       {/foreach}
-                                                                                               </ul>
-                                                                                       </div>
-                                                                               </li>
-                                                                       </ul>
-                                                                       <noscript>
-                                                                               {foreach from=$labelGroups item=labelGroup}
-                                                                                       <select name="labelIDs[{@$labelGroup->groupID}]">
-                                                                                               <option value="0">{lang}wcf.label.none{/lang}</option>
-                                                                                               <option value="-1">{lang}wcf.label.withoutSelection{/lang}</option>
-                                                                                               {foreach from=$labelGroup item=label}
-                                                                                                       <option value="{@$label->labelID}"{if $labelIDs[$labelGroup->groupID]|isset && $labelIDs[$labelGroup->groupID] == $label->labelID} selected{/if}>{$label->getTitle()}</option>
-                                                                                               {/foreach}
-                                                                                       </select>
-                                                                               {/foreach}
-                                                                       </noscript>
-                                                               </dd>
-                                                       {/if}
-                                               {/foreach}
+                                               {include file='__labelSelection'}
                                        </dl>
                                        <div class="formSubmit">
                                                <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
index b69b4fad726dc10572cdd76b402b3405c8e82f08..ccf92ba95538ade89c1fd59e46c100a857aeb7c9 100644 (file)
                                                                {if $user->userID != $__wcf->user->userID}
                                                                        {if $user->isAccessible('canViewEmailAddress') || $__wcf->session->getPermission('admin.user.canEditMailAddress')}
                                                                                <li><a href="mailto:{@$user->getEncodedEmail()}">{lang}wcf.user.button.mail{/lang}</a></li>
-                                                                       {elseif $user->isAccessible('canMail') && $__wcf->session->getPermission('user.profile.canMail')}
-                                                                               <li><a href="{link controller='Mail' object=$user}{/link}">{lang}wcf.user.button.mail{/lang}</a></li>
                                                                        {/if}
                                                                {/if}
                                                                
index baa38d23fb69a2c6c15080fce826c8b33d644ce6..7c0eb233dd8e371ef0a663e13f324acc92ef6e57 100644 (file)
@@ -9,8 +9,6 @@
                                {if $user->userID != $__wcf->user->userID}
                                        {if $user->isAccessible('canViewEmailAddress')}
                                                <li><a class="jsTooltip" href="mailto:{@$user->getEncodedEmail()}" title="{lang}wcf.user.button.mail{/lang}"><span class="icon icon16 fa-envelope-o"></span> <span class="invisible">{lang}wcf.user.button.mail{/lang}</span></a></li>
-                                       {elseif $user->isAccessible('canMail') && $__wcf->session->getPermission('user.profile.canMail')}
-                                               <li><a class="jsTooltip" href="{link controller='Mail' object=$user}{/link}" title="{lang}wcf.user.button.mail{/lang}"><span class="icon icon16 fa-envelope-o"></span> <span class="invisible">{lang}wcf.user.button.mail{/lang}</span></a></li>
                                        {/if}
                                {/if}
                                
index a94cd4e9bc702defc42aa9b6c6f4d6f56665ab13..7c7f50640f01623d07923dfa3116380a2c009986 100644 (file)
@@ -1,15 +1,6 @@
 {capture assign='sidebarLeft'}
        {assign var=__userMenuActiveItems value=$__wcf->getUserMenu()->getActiveMenuItems()}
        
-       <script data-relocate="true">
-               $(function() {
-                       // mobile safari hover workaround
-                       if ($(window).width() <= 800) {
-                               $('.sidebar').addClass('mobileSidebar').hover(function() { });
-                       }
-               });
-       </script>
-       
        <section class="box" data-static-box-identifier="com.woltlab.wcf.UserMenu">
                {foreach from=$__wcf->getUserMenu()->getMenuItems('') item=menuCategory}
                        <h2 class="boxTitle">{$menuCategory->getTitle()}</h2>
index b3c83701f107d80b0c3c119fa457c5ecea9fa758..6d6ca7a7595483435e0931a26a9826de92a5119d 100644 (file)
                                
                                <div class="boxContent">
                                        <dl>
-                                               {foreach from=$labelGroups item=labelGroup}
-                                                       {if $labelGroup|count}
-                                                               <dt><label>{$labelGroup->getTitle()}</label></dt>
-                                                               <dd>
-                                                                       <ul class="labelList jsOnly">
-                                                                               <li class="dropdown labelChooser" id="labelGroup{@$labelGroup->groupID}" data-group-id="{@$labelGroup->groupID}">
-                                                                                       <div class="dropdownToggle" data-toggle="labelGroup{@$labelGroup->groupID}"><span class="badge label">{lang}wcf.label.none{/lang}</span></div>
-                                                                                       <div class="dropdownMenu">
-                                                                                               <ul class="scrollableDropdownMenu">
-                                                                                                       {foreach from=$labelGroup item=label}
-                                                                                                               <li data-label-id="{@$label->labelID}"><span><span class="badge label{if $label->getClassNames()} {@$label->getClassNames()}{/if}">{$label->getTitle()}</span></span></li>
-                                                                                                       {/foreach}
-                                                                                               </ul>
-                                                                                       </div>
-                                                                               </li>
-                                                                       </ul>
-                                                                       <noscript>
-                                                                               {foreach from=$labelGroups item=labelGroup}
-                                                                                       <select name="labelIDs[{@$labelGroup->groupID}]">
-                                                                                               <option value="0">{lang}wcf.label.none{/lang}</option>
-                                                                                               <option value="-1">{lang}wcf.label.withoutSelection{/lang}</option>
-                                                                                               {foreach from=$labelGroup item=label}
-                                                                                                       <option value="{@$label->labelID}"{if $labelIDs[$labelGroup->groupID]|isset && $labelIDs[$labelGroup->groupID] == $label->labelID} selected{/if}>{$label->getTitle()}</option>
-                                                                                               {/foreach}
-                                                                                       </select>
-                                                                               {/foreach}
-                                                                       </noscript>
-                                                               </dd>
-                                                       {/if}
-                                               {/foreach}
+                                               {include file='__labelSelection'}
                                        </dl>
                                        <div class="formSubmit">
                                                <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
index 65b8422b994d2a7fcfd8e7ee18dd49efd5b0f131..b90c6b7aa2c3f54f04d6e731469fae371ca2c4de 100644 (file)
@@ -663,11 +663,6 @@ pdf</defaultvalue>
                        </option>
                        <!-- /user.signature -->
                        <!-- user.profile -->
-                       <option name="user.profile.canMail">
-                               <categoryname>user.profile</categoryname>
-                               <optiontype>boolean</optiontype>
-                               <defaultvalue>0</defaultvalue>
-                       </option>
                        <option name="user.profile.canChangeEmail">
                                <categoryname>user.profile</categoryname>
                                <optiontype>boolean</optiontype>
@@ -1010,4 +1005,7 @@ png</defaultvalue>
                        </option>
                </options>
        </import>
+       <delete>
+               <option name="user.profile.canMail" />
+       </delete>
 </data>
index 1af648de6da5c8de8fd6a3c58cd035a0a41a119d..3a6d8bb871438ade2e367c5dca1183f5d8cb2a37 100644 (file)
                        <classname>wcf\system\user\notification\event\ArticleUserNotificationEvent</classname>
                        <preset>1</preset>
                </event>
+               <event>
+                       <name>like</name>
+                       <objecttype>com.woltlab.wcf.likeableArticle.notification</objecttype>
+                       <classname>wcf\system\user\notification\event\ArticleLikeUserNotificationEvent</classname>
+                       <options>module_like</options>
+                       <preset>1</preset>
+               </event>
                
                <event>
                        <name>registration</name>
index 7d6bfe93eb416d717231dd30b001984dcade8d71..769aade42c8018b5e6ee95a3622ba71a49dcc62e 100644 (file)
                                <visible>15</visible>
                                <editable>3</editable>
                        </option>
-                       <option name="icq">
-                               <categoryname>profile.contact</categoryname>
-                               <optiontype>text</optiontype>
-                               <validationpattern>^$|^([0-9](-| )?)+[0-9]$</validationpattern>
-                               <searchable>1</searchable>
-                               <visible>15</visible>
-                               <editable>3</editable>
-                               <isdisabled>1</isdisabled>
-                               <contentpattern>^(\d{3})-(\d{3})-(\d{3})$</contentpattern>
-                       </option>
                        <option name="skype">
                                <categoryname>profile.contact</categoryname>
                                <optiontype>text</optiontype>
                                <selectoptions>0:wcf.user.access.everyone
 1:wcf.user.access.registered
 2:wcf.user.access.following
-3:wcf.user.access.nobody</selectoptions>
-                               <editable>3</editable>
-                       </option>
-                       <option name="canMail">
-                               <categoryname>settings.privacy.messaging</categoryname>
-                               <optiontype>select</optiontype>
-                               <defaultvalue>1</defaultvalue>
-                               <selectoptions>0:wcf.user.access.everyone
-1:wcf.user.access.registered
-2:wcf.user.access.following
 3:wcf.user.access.nobody</selectoptions>
                                <editable>3</editable>
                        </option>
                        </option>
                </options>
        </import>
+       <delete>
+               <option name="canMail" />
+       </delete>
 </data>
index 3eaa4c47b26ef0a70c4172b8541374e0cf4f8c44..df9f020767ff19fcd5b916d8fb5b075c7f686f69 100644 (file)
   "lockfileVersion": 1,
   "dependencies": {
     "@types/md5-file": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/@types/md5-file/-/md5-file-4.0.0.tgz",
-      "integrity": "sha512-t+qg7R25oYo6z3iWI+9CRky2mgQ51RGvLqlCPV+xa6dKp0YDomv0TArLK/CdcReFZwQHn/YMNRZx+4AUWXPtlg==",
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/@types/md5-file/-/md5-file-4.0.1.tgz",
+      "integrity": "sha512-uK6vlo/LJp6iNWinpSzZwMe8Auzs0UYxesm7OGfQS3oz6PJciHtrKcqVOGk4wjYKawrl234vwNWvHyXH1ZzRyQ==",
       "dev": true
     },
     "@types/node": {
-      "version": "10.12.15",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz",
-      "integrity": "sha512-9kROxduaN98QghwwHmxXO2Xz3MaWf+I1sLVAA6KJDF5xix+IyXVhds0MAfdNwtcpSrzhaTsNB0/jnL86fgUhqA==",
+      "version": "13.11.1",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-13.11.1.tgz",
+      "integrity": "sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==",
       "dev": true
     },
-    "align-text": {
-      "version": "0.1.4",
-      "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
-      "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
-      "requires": {
-        "kind-of": "^3.0.2",
-        "longest": "^1.0.1",
-        "repeat-string": "^1.5.2"
-      }
-    },
-    "arrify": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
-      "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0="
+    "arg": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
     },
     "buffer-from": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
       "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
     },
-    "camelcase": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
-      "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk="
-    },
-    "center-align": {
-      "version": "0.1.3",
-      "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
-      "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
-      "requires": {
-        "align-text": "^0.1.3",
-        "lazy-cache": "^1.0.3"
-      }
-    },
-    "cliui": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
-      "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
-      "requires": {
-        "center-align": "^0.1.1",
-        "right-align": "^0.1.1",
-        "wordwrap": "0.0.2"
-      }
-    },
-    "decamelize": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
-      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
+    "commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
     },
     "diff": {
-      "version": "3.5.0",
-      "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
-      "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA=="
-    },
-    "is-buffer": {
-      "version": "1.1.6",
-      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
-      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
-    },
-    "kind-of": {
-      "version": "3.2.2",
-      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-      "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
-      "requires": {
-        "is-buffer": "^1.1.5"
-      }
-    },
-    "lazy-cache": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
-      "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4="
-    },
-    "longest": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
-      "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc="
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="
     },
     "make-error": {
-      "version": "1.3.5",
-      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
-      "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g=="
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
     },
     "md5-file": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-4.0.0.tgz",
-      "integrity": "sha512-UC0qFwyAjn4YdPpKaDNw6gNxRf7Mcx7jC1UGCY4boCzgvU2Aoc1mOGzTtrjjLKhM5ivsnhoKpQVxKPp+1j1qwg==",
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-5.0.0.tgz",
+      "integrity": "sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==",
       "dev": true
     },
-    "minimist": {
-      "version": "1.2.0",
-      "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
-      "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
-    },
-    "mkdirp": {
-      "version": "0.5.1",
-      "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
-      "requires": {
-        "minimist": "0.0.8"
-      },
-      "dependencies": {
-        "minimist": {
-          "version": "0.0.8",
-          "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
-          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
-        }
-      }
-    },
-    "repeat-string": {
-      "version": "1.6.1",
-      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
-      "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
-    },
     "requirejs": {
       "version": "2.3.6",
       "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz",
       "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg=="
     },
-    "right-align": {
-      "version": "0.1.3",
-      "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
-      "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
-      "requires": {
-        "align-text": "^0.1.1"
-      }
-    },
     "source-map": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
       "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
     },
     "source-map-support": {
-      "version": "0.5.9",
-      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz",
-      "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==",
+      "version": "0.5.16",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
+      "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
       "requires": {
         "buffer-from": "^1.0.0",
         "source-map": "^0.6.0"
       }
     },
     "ts-node": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz",
-      "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==",
+      "version": "8.8.2",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.8.2.tgz",
+      "integrity": "sha512-duVj6BpSpUpD/oM4MfhO98ozgkp3Gt9qIp3jGxwU2DFvl/3IRaEAvbLa8G60uS7C77457e/m5TMowjedeRxI1Q==",
       "requires": {
-        "arrify": "^1.0.0",
-        "buffer-from": "^1.1.0",
-        "diff": "^3.1.0",
+        "arg": "^4.1.0",
+        "diff": "^4.0.1",
         "make-error": "^1.1.1",
-        "minimist": "^1.2.0",
-        "mkdirp": "^0.5.1",
         "source-map-support": "^0.5.6",
-        "yn": "^2.0.0"
+        "yn": "3.1.1"
       }
     },
     "typescript": {
-      "version": "3.2.2",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz",
-      "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==",
+      "version": "3.8.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
+      "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
       "dev": true
     },
     "uglify-js": {
-      "version": "2.8.29",
-      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
-      "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
-      "requires": {
-        "source-map": "~0.5.1",
-        "uglify-to-browserify": "~1.0.0",
-        "yargs": "~3.10.0"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.5.7",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
-        }
-      }
-    },
-    "uglify-to-browserify": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
-      "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=",
-      "optional": true
-    },
-    "window-size": {
-      "version": "0.1.0",
-      "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
-      "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0="
-    },
-    "wordwrap": {
-      "version": "0.0.2",
-      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
-      "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8="
-    },
-    "yargs": {
-      "version": "3.10.0",
-      "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
-      "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
+      "version": "3.8.1",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.1.tgz",
+      "integrity": "sha512-W7KxyzeaQmZvUFbGj4+YFshhVrMBGSg2IbcYAjGWGvx8DHvJMclbTDMpffdxFUGPBHjIytk7KJUR/KUXstUGDw==",
       "requires": {
-        "camelcase": "^1.0.2",
-        "cliui": "^2.1.0",
-        "decamelize": "^1.0.0",
-        "window-size": "0.1.0"
+        "commander": "~2.20.3",
+        "source-map": "~0.6.1"
       }
     },
     "yn": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz",
-      "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo="
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="
     }
   }
 }
index 70f7c5e7c49e44295a95d34fb530d3d577b74111..b5ac1acffd4cd6599ed508ce5e7584099fc406ee 100644 (file)
@@ -1,13 +1,13 @@
 {
   "dependencies": {
     "requirejs": "^2.3.6",
-    "ts-node": "^7.0.1",
-    "uglify-js": "^2.0.0"
+    "ts-node": "^8.8.2",
+    "uglify-js": "^3.8.1"
   },
   "devDependencies": {
-    "@types/md5-file": "^4.0.0",
-    "@types/node": "^10.12.15",
-    "md5-file": "^4.0.0",
-    "typescript": "^3.2.2"
+    "@types/md5-file": "^4.0.1",
+    "@types/node": "^13.11.1",
+    "md5-file": "^5.0.0",
+    "typescript": "^3.8.3"
   }
 }
index 1abd13f0ccb24f85dca9b7f626c6700dc38b1d94..6223b81ccdfd504ac51e27ab110cdfcb8e2430b0 100644 (file)
        <spider ident="DNAbot/1.0">
                <name>DNAbot</name>
        </spider>
+       <spider ident="DotBot/1.1">
+               <name>Moz DotBot</name>
+               <url>http://www.opensiteexplorer.org/dotbot</url>
+       </spider>
        <spider ident="DragonBot/1.0 libwww/5.0">
                <name>DragonBot</name>
        </spider>
                <name>Google Stackdriver Monitoring</name>
                <url>https://cloud.google.com/monitoring/alerts/uptime-checks</url>
        </spider>
+       <spider ident="Google-Ads-Creatives-Assistant">
+               <name>Google-Ads-Creatives-Assistant</name>
+       </spider>
+       <spider ident="Google-AdWords-Express">
+               <name>Google-AdWords-Express</name>
+       </spider>
+       <spider ident="AdsBot-Google">
+               <name>Google Ads-Bot</name>
+               <url>http://www.google.com/adsbot.html</url>
+       </spider>
        <spider ident="Gpostbot">
                <name>Gpostbot</name>
                <url>http://www.gpost.info/help.php?c=bot</url>
        <spider ident="PageBoy">
                <name>PageBoy</name>
        </spider>
+       <spider ident="Pandalytics">
+               <name>Pandalytics</name>
+               <url>https://domainsbot.com/pandalytics/</url>
+       </spider>
        <spider ident="ParaSite">
                <name>ParaSite</name>
        </spider>
                <name>Sensis Web Crawler</name>
                <url>http://www.sensis.com.au/help.do</url>
        </spider>
+       <spider ident="SentiBot">
+               <name>SentiBot</name>
+               <url>http://www.sentibot.eu</url>
+       </spider>
+       <spider ident="SeznamBot/3.2">
+               <name>Seznam Bot</name>
+               <url>http://napoveda.seznam.cz/en/seznambot-intro/</url>
+       </spider>
        <spider ident="SG-Scout">
                <name>SG-Scout</name>
        </spider>
index 6318fe7bbe4e39f688cbc2d5b1abf20e5363d566..7a91253f6ea3a234e6a1a9ea7465c9ca5f1207b9 100644 (file)
@@ -2056,7 +2056,8 @@ WCF.ACP.Stat.Chart = Class.extend({
                                        $("#chartTooltip").html(item.series.xaxis.tickFormatter(item.datapoint[0], item.series.xaxis) + ', ' + WCF.String.formatNumeric(item.datapoint[1]) + ' ' + item.series.label).show();
                                        UiAlignment.set($("#chartTooltip")[0], span, {
                                                verticalOffset: 5,
-                                               horizontal: 'center'
+                                               horizontal: 'center',
+                                               vertical: 'top'
                                        });
                                }
                                else {
index 8719c7e04b314e89bf4813d64be5fe80a9ef11bf..d6ad54d0e6734b2f878fc27a813382009202a20e 100644 (file)
        </form>
 {/if}
 
+{if $form->needsRequiredFieldsInfo()}
+       <div class="section requiredFieldsInfo">
+               <p><span class="formFieldRequired">*</span> {lang}wcf.global.form.required{/lang}</p>
+               
+               {event name='requiredFieldsInfo'}
+       </div>
+{/if}
+
 <script data-relocate="true">
        {* after all dependencies have been added, check them *}
        require(['WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager'], function(FormBuilderFieldDependencyManager) {
index eec1c6606bafb02b1a7f00040ff9c4a65e161d2b..b7ff418e62a84edc09bac06d4757d816b50fca80 100644 (file)
@@ -6,11 +6,11 @@
        {if $container->getLabel() !== null}
                {if $container->getDescription() !== null}
                        <header class="sectionHeader">
-                               <h2 class="sectionTitle">{@$container->getLabel()}</h2>
+                               <h2 class="sectionTitle">{@$container->getLabel()}{if $container->markAsRequired()} <span class="formFieldRequired">*</span>{/if}</h2>
                                <p class="sectionDescription">{@$container->getDescription()}</p>
                        </header>
                {else}
-                       <h2 class="sectionTitle">{@$container->getLabel()}</h2>
+                       <h2 class="sectionTitle">{@$container->getLabel()}{if $container->markAsRequired()} <span class="formFieldRequired">*</span>{/if}</h2>
                {/if}
        {/if}
        
index d726d5aa69c0a87177d92d03f6dfccc1cdc2ee60..d59c467b7d915a33579687c696fa057fa9ffdf5f 100644 (file)
@@ -1,5 +1,5 @@
 <dl id="{@$field->getPrefixedId()}Container" {if !$field->getClasses()|empty} class="{implode from=$field->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{foreach from=$field->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{if !$field->checkDependencies()} style="display: none;"{/if}>
-       <dt>{if $field->getLabel() !== null}<label for="{@$field->getPrefixedId()}">{@$field->getLabel()}</label>{/if}</dt>
+       <dt>{if $field->getLabel() !== null}<label for="{@$field->getPrefixedId()}">{@$field->getLabel()}</label>{if $field->isRequired()} <span class="formFieldRequired">*</span>{/if}{/if}</dt>
        <dd>
                {@$field->getFieldHtml()}
                
index fd0798d128460f82fa181a63153db1e9385ac5d0..45abd51bfe275b236ba30f274d9ceb816c908f95 100644 (file)
@@ -6,7 +6,7 @@ WCF.Language.addObject({
        'wcf.message.quote.quoteAndReply': '{lang}wcf.message.quote.quoteAndReply{/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}'
+       'wcf.message.quote.showQuotes': '{lang __literal=true}wcf.message.quote.showQuotes{/lang}'
 });
 
 {if !$wysiwygSelector|isset}{assign var=wysiwygSelector value=''}{/if}
index 0f02a1be1a14f455436c1e4b6608822fd4b6fbb9..dbb6ef31a6229504291a773f7f8e8bc99b0cc682 100644 (file)
        
        <nav class="contentHeaderNavigation">
                <ul>
-                       <li><a href="{link controller='LabelGroupList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.label.group.list{/lang}</span></a></li>
+                       {if $action == 'edit'}
+                               <li><a href="{link controller='LabelList' id=$groupID}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.label.list{/lang}</span></a></li>
+                       {/if}
+                       <li><a href="{link controller='LabelGroupList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.label.group.list{/lang}</span></a></li>
                        
                        {event name='contentHeaderNavigation'}
                </ul>
index eaa7ef5671512bacaa1186026b06c3dcdcedd90b..f5ccb86be86f61296ecc549eae86b0d1a49cca4d 100644 (file)
        </nav>
 </header>
 
+<form action="{link controller='LabelGroupList'}{/link}" method="post">
+       <section class="section">
+               <h2 class="sectionTitle">{lang}wcf.acp.label.filter{/lang}</h2>
+               
+               <div class="row rowColGap formGrid">
+                       <dl class="col-xs-12 col-md-4">
+                               <dt></dt>
+                               <dd>
+                                       <input type="text" id="groupName" name="groupName" value="{$groupName}" placeholder="{lang}wcf.global.title{/lang}" class="long">
+                               </dd>
+                       </dl>
+                       
+                       <dl class="col-xs-12 col-md-4">
+                               <dt></dt>
+                               <dd>
+                                       <input type="text" id="groupDescription" name="groupDescription" value="{$groupDescription}" placeholder="{lang}wcf.global.description{/lang}"  class="long">
+                               </dd>
+                       </dl>
+               </div>
+       </section>
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+               {@SECURITY_TOKEN_INPUT_TAG}
+       </div>
+</form>
+
 {hascontent}
        <div class="paginationTop">
-               {content}{pages print=true assign=pagesLinks controller="LabelGroupList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}{/content}
+               {content}
+                       {assign var='linkParameters' value=''}
+                       {if $groupName}
+                               {append var='linkParameters' value='&groupName='}
+                               {append var='linkParameters' value=$groupName|rawurlencode}
+                       {/if}
+                       {if $groupDescription}
+                               {append var='linkParameters' value='&groupDescription='}
+                               {append var='linkParameters' value=$groupDescription|rawurlencode}
+                       {/if}
+                   
+                       {pages print=true assign=pagesLinks controller="LabelGroupList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder$linkParameters"}
+               {/content}
        </div>
 {/hascontent}
 
                <table class="table">
                        <thead>
                                <tr>
-                                       <th class="columnID columnLabelGroupID{if $sortField == 'groupID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='LabelGroupList'}pageNo={@$pageNo}&sortField=groupID&sortOrder={if $sortField == 'groupID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
-                                       <th class="columnTitle columnGroupName{if $sortField == 'groupName'} active {@$sortOrder}{/if}"><a href="{link controller='LabelGroupList'}pageNo={@$pageNo}&sortField=groupName&sortOrder={if $sortField == 'groupName' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.title{/lang}</a></th>
-                                       <th class="columnText columnGroupDescription{if $sortField == 'groupDescription'} active {@$sortOrder}{/if}"><a href="{link controller='LabelGroupList'}pageNo={@$pageNo}&sortField=groupDescription&sortOrder={if $sortField == 'groupDescription' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.description{/lang}</a></th>
-                                       <th class="columnDigits columnShowOrder{if $sortField == 'showOrder'} active {@$sortOrder}{/if}"><a href="{link controller='LabelGroupList'}pageNo={@$pageNo}&sortField=showOrder&sortOrder={if $sortField == 'showOrder' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.showOrder{/lang}</a></th>
+                                       <th class="columnID columnLabelGroupID{if $sortField == 'groupID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='LabelGroupList'}pageNo={@$pageNo}&sortField=groupID&sortOrder={if $sortField == 'groupID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                                       <th class="columnTitle columnGroupName{if $sortField == 'groupName'} active {@$sortOrder}{/if}"><a href="{link controller='LabelGroupList'}pageNo={@$pageNo}&sortField=groupName&sortOrder={if $sortField == 'groupName' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.title{/lang}</a></th>
+                                       <th class="columnText columnGroupDescription{if $sortField == 'groupDescription'} active {@$sortOrder}{/if}"><a href="{link controller='LabelGroupList'}pageNo={@$pageNo}&sortField=groupDescription&sortOrder={if $sortField == 'groupDescription' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.description{/lang}</a></th>
+                                       <th class="columnDigits columnLabels{if $sortField == 'labels'} active {@$sortOrder}{/if}"><a href="{link controller='LabelGroupList'}pageNo={@$pageNo}&sortField=labels&sortOrder={if $sortField == 'labels' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.acp.label.list{/lang}</a></th>
+                                       <th class="columnDigits columnShowOrder{if $sortField == 'showOrder'} active {@$sortOrder}{/if}"><a href="{link controller='LabelGroupList'}pageNo={@$pageNo}&sortField=showOrder&sortOrder={if $sortField == 'showOrder' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{@$linkParameters}{/link}">{lang}wcf.global.showOrder{/lang}</a></th>
                                        
                                        {event name='columnHeads'}
                                </tr>
                                                <td class="columnID">{@$group->groupID}</td>
                                                <td class="columnTitle columnGroupName"><a href="{link controller='LabelGroupEdit' object=$group}{/link}">{$group}</a></td>
                                                <td class="columnText columnGroupDescription">{$group->groupDescription}</td>
+                                               <td class="columnDigits columnLabels"><a href="{link controller='LabelList' id=$group->groupID}{/link}">{#$group->labels}</a></td>
                                                <td class="columnDigits columnShowOrder">{@$group->showOrder}</td>
                                                
                                                {event name='columns'}
index d7f4c53b975a21626ae262daf91a26b8a540c180..dff6b975e4b2bdb9d2fceb5a1abb2ed6fd4d35e0 100644 (file)
                                                                        </dd>
                                                                </dl>
                                                                
+                                                               {event name='informationFieldsMultilingual'}
+                                                               
                                                                {if $pageType != 'system'}
                                                                        {assign var='__errorFieldName' value='content_'|concat:$availableLanguage->languageID}
                                                                        <dl{if $errorField == $__errorFieldName} class="formError"{/if}>
                                                                                </dd>
                                                                        </dl>
                                                                        
+                                                                       {event name='messageFieldsMultilingual'}
+                                                                       
                                                                        {if $pageType == 'text'}
                                                                                {include file='messageFormTabs' wysiwygContainerID='content'|concat:$availableLanguage->languageID}
                                                                        {/if}
                                                                                        {/if}
                                                                                </dd>
                                                                        </dl>
+                                                                       
+                                                                       {event name='metaFieldsMultilingual'}
                                                                {/if}
                                                        </div>
                                                </div>
index 1b240fe994db894da14d836b0fd0ac4b2a3fd159..93b8f8b8ee0940702591a6c6df339da2da6d5b78 100644 (file)
                                                <dd>
                                                        <select name="templateGroupID" id="templateGroupID">
                                                                <option value="0">{lang}wcf.acp.template.group.default{/lang}</option>
-                                                               {foreach from=$availableTemplateGroups item=templateGroup}
-                                                                       <option value="{@$templateGroup->templateGroupID}"{if $templateGroup->templateGroupID == $templateGroupID} selected{/if}>{$templateGroup->getName()}</option>
-                                                               {/foreach}
+                                                               {htmlOptions options=$availableTemplateGroups selected=$templateGroupID disableEncoding=true}
                                                        </select>
                                                        {if $errorField == 'templateGroupID'}
                                                                <small class="innerError">
index 6590ba2399a170be9ad205e4fafb176988083fff..b9213e3033f2753663ff10e61f410280e79a82f7 100644 (file)
@@ -4,7 +4,7 @@
 (function (window, undefined) { !function(t){"use strict";function e(t,i){return new e.prototype.init(t,i)}Function.prototype.bind||(Function.prototype.bind=function(t){var e=this;return function(){return e.apply(t)}});var i=0,r=null;"function"==typeof window.require&&require(["Environment"],function(t){r=t}),t.fn.redactor=function(i){var r=[],n=Array.prototype.slice.call(arguments,1);return"string"==typeof i?this.each(function(){var e,s=t.data(this,"redactor");if("-1"!==i.search(/\./)?(e=i.split("."),void 0!==s[e[0]]&&(e=s[e[0]][e[1]])):e=s[i],void 0!==s&&t.isFunction(e)){var o=e.apply(s,n);void 0!==o&&o!==s&&r.push(o)}else t.error('No such method "'+i+'" for Redactor')}):this.each(function(){t.data(this,"redactor",{}),t.data(this,"redactor",e(this,i))}),0===r.length?this:1===r.length?r[0]:r},t.Redactor=e,t.Redactor.VERSION="2.99",t.Redactor.modules=["air","autosave","block","buffer","build","button","caret","clean","code","core","detect","dropdown","events","file","focus","image","indent","inline","insert","keydown","keyup","lang","line","link","linkify","list","marker","modal","observe","offset","paragraphize","paste","placeholder","progress","selection","shortcuts","storage","toolbar","upload","uploads3","utils","browser"],t.Redactor.settings={},t.Redactor.opts={animation:!1,lang:"en",direction:"ltr",spellcheck:!0,overrideStyles:!0,stylesClass:!1,scrollTarget:document,focus:!1,focusEnd:!1,clickToEdit:!1,structure:!1,tabindex:!1,minHeight:!1,maxHeight:!1,maxWidth:!1,plugins:!1,callbacks:{},placeholder:!1,linkify:!0,enterKey:!0,pastePlainText:!1,pasteImages:!0,pasteLinks:!0,pasteBlockTags:["pre","h1","h2","h3","h4","h5","h6","table","tbody","thead","tfoot","th","tr","td","ul","ol","li","blockquote","p","figure","figcaption"],pasteInlineTags:["br","strong","ins","code","del","span","samp","kbd","sup","sub","mark","var","cite","small","b","u","em","i"],preClass:!1,preSpaces:4,tabAsSpaces:!1,tabKey:!0,autosave:!1,autosaveName:!1,autosaveFields:!1,imageUpload:null,imageUploadParam:"file",imageUploadFields:!1,imageUploadForms:!1,imageTag:"figure",imageEditable:!0,imageCaption:!0,imagePosition:!1,imageResizable:!1,imageFloatMargin:"10px",dragImageUpload:!0,multipleImageUpload:!0,clipboardImageUpload:!0,fileUpload:null,fileUploadParam:"file",fileUploadFields:!1,fileUploadForms:!1,dragFileUpload:!0,s3:!1,linkNewTab:!1,linkTooltip:!0,linkNofollow:!1,linkSize:30,linkValidation:!0,pasteLinkTarget:!1,videoContainerClass:"video-container",toolbar:!0,toolbarFixed:!0,toolbarFixedTarget:document,toolbarFixedTopOffset:0,toolbarExternal:!1,toolbarOverflow:!1,air:!1,airWidth:!1,formatting:["p","blockquote","pre","h1","h2","h3","h4","h5","h6"],formattingAdd:!1,buttons:["format","bold","italic","deleted","lists","image","file","link","horizontalrule"],buttonsTextLabeled:!1,buttonsHide:[],buttonsHideOnMobile:[],script:!0,removeNewlines:!1,removeComments:!0,replaceTags:{b:"strong",i:"em",strike:"del"},keepStyleAttr:[],keepInlineOnEnter:!1,shortcuts:{"ctrl+shift+m, meta+shift+m":{func:"inline.removeFormat"},"ctrl+b, meta+b":{func:"inline.format",params:["bold"]},"ctrl+i, meta+i":{func:"inline.format",params:["italic"]},"ctrl+h, meta+h":{func:"inline.format",params:["superscript"]},"ctrl+l, meta+l":{func:"inline.format",params:["subscript"]},"ctrl+k, meta+k":{func:"link.show"},"ctrl+shift+7":{func:"list.toggle",params:["orderedlist"]},"ctrl+shift+8":{func:"list.toggle",params:["unorderedlist"]}},shortcutsAdd:!1,activeButtons:["deleted","italic","bold"],activeButtonsStates:{b:"bold",strong:"bold",i:"italic",em:"italic",del:"deleted",strike:"deleted"},langs:{en:{format:"Format",image:"Image",file:"File",link:"Link",bold:"Bold",italic:"Italic",deleted:"Strikethrough",underline:"Underline","bold-abbr":"B","italic-abbr":"I","deleted-abbr":"S","underline-abbr":"U",lists:"Lists","link-insert":"Insert link","link-edit":"Edit link","link-in-new-tab":"Open link in new tab",unlink:"Unlink",cancel:"Cancel",close:"Close",insert:"Insert",save:"Save",delete:"Delete",text:"Text",edit:"Edit",title:"Title",paragraph:"Normal text",quote:"Quote",code:"Code",heading1:"Heading 1",heading2:"Heading 2",heading3:"Heading 3",heading4:"Heading 4",heading5:"Heading 5",heading6:"Heading 6",filename:"Name",optional:"optional",unorderedlist:"Unordered List",orderedlist:"Ordered List",outdent:"Outdent",indent:"Indent",horizontalrule:"Line","upload-label":"Drop file here or ",caption:"Caption",bulletslist:"Bullets",numberslist:"Numbers","image-position":"Position",none:"None",left:"Left",right:"Right",center:"Center","accessibility-help-label":"Rich text editor"}},type:"textarea",inline:!1,inlineTags:["a","span","strong","strike","b","u","em","i","code","del","ins","samp","kbd","sup","sub","mark","var","cite","small"],blockTags:["pre","ul","ol","li","p","h1","h2","h3","h4","h5","h6","dl","dt","dd","div","td","blockquote","output","figcaption","figure","address","section","header","footer","aside","article","iframe"],paragraphize:!0,paragraphizeBlocks:["table","div","pre","form","ul","ol","h1","h2","h3","h4","h5","h6","dl","blockquote","figcaption","address","section","header","footer","aside","article","object","style","script","iframe","select","input","textarea","button","option","map","area","math","hr","fieldset","legend","hgroup","nav","figure","details","menu","summary","p"],emptyHtml:"<p>&#x200b;</p>",invisibleSpace:"&#x200b;",emptyHtmlRendered:t("").html("​").html(),imageTypes:["image/png","image/jpeg","image/gif"],userAgent:navigator.userAgent.toLowerCase(),observe:{dropdowns:[]},regexps:{linkyoutube:/https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.\-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/gi,linkvimeo:/https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/,linkimage:/((https?|www)[^\s]+\.)(jpe?g|png|gif)(\?[^\s-]+)?/gi,url:/(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/gi}},e.fn=t.Redactor.prototype={keyCode:{BACKSPACE:8,DELETE:46,UP:38,DOWN:40,ENTER:13,SPACE:32,ESC:27,TAB:9,CTRL:17,META:91,SHIFT:16,ALT:18,RIGHT:39,LEFT:37,LEFT_WIN:91},init:function(e,r){if(this.$element=t(e),this.uuid=i++,this.sBuffer=[],this.sRebuffer=[],this.loadOptions(r),this.loadModules(),this.opts.clickToEdit&&!this.$element.hasClass("redactor-click-to-edit"))return this.loadToEdit(r);this.$element.hasClass("redactor-click-to-edit")&&this.$element.removeClass("redactor-click-to-edit"),this.reIsBlock=new RegExp("^("+this.opts.blockTags.join("|").toUpperCase()+")$","i"),this.reIsInline=new RegExp("^("+this.opts.inlineTags.join("|").toUpperCase()+")$","i"),this.opts.dragImageUpload=null!==this.opts.imageUpload&&this.opts.dragImageUpload,this.opts.dragFileUpload=null!==this.opts.fileUpload&&this.opts.dragFileUpload,this.formatting={},this.lang.load(),t.extend(this.opts.shortcuts,this.opts.shortcutsAdd),this.$editor=this.$element,this.detectType(),this.core.callback("start"),this.core.callback("startToEdit"),this.start=!0,this.build.start()},detectType:function(){this.build.isInline()||this.opts.inline?this.opts.type="inline":this.build.isTag("DIV")?this.opts.type="div":this.build.isTag("PRE")&&(this.opts.type="pre")},loadToEdit:function(e){this.$element.on("click.redactor-click-to-edit",t.proxy(function(){this.initToEdit(e)},this)),this.$element.addClass("redactor-click-to-edit")},initToEdit:function(e){t.extend(e.callbacks,{startToEdit:function(){this.insert.node(this.marker.get(),!1)},initToEdit:function(){this.selection.restore(),this.clickToCancelStorage=this.code.get(),t(this.opts.clickToCancel).off(".redactor-click-to-edit"),t(this.opts.clickToCancel).show().on("click.redactor-click-to-edit",t.proxy(function(i){i.preventDefault(),this.core.destroy(),this.events.syncFire=!1,this.$element.html(this.clickToCancelStorage),this.core.callback("cancel",this.clickToCancelStorage),this.events.syncFire=!0,this.clickToCancelStorage="",t(this.opts.clickToCancel).hide(),t(this.opts.clickToSave).hide(),this.$element.on("click.redactor-click-to-edit",t.proxy(function(){this.initToEdit(e)},this)),this.$element.addClass("redactor-click-to-edit")},this)),t(this.opts.clickToSave).off(".redactor-click-to-edit"),t(this.opts.clickToSave).show().on("click.redactor-click-to-edit",t.proxy(function(i){i.preventDefault(),this.core.destroy(),this.core.callback("save",this.code.get()),t(this.opts.clickToCancel).hide(),t(this.opts.clickToSave).hide(),this.$element.on("click.redactor-click-to-edit",t.proxy(function(){this.initToEdit(e)},this)),this.$element.addClass("redactor-click-to-edit")},this))}}),this.$element.redactor(e),this.$element.off(".redactor-click-to-edit")},loadOptions:function(e){var i={};void 0!==t.Redactor.settings.namespace?this.$element.hasClass(t.Redactor.settings.namespace)&&(i=t.Redactor.settings):i=t.Redactor.settings,this.opts=t.extend({},t.Redactor.opts,this.$element.data(),e),this.opts=t.extend({},this.opts,i)},getModuleMethods:function(t){return Object.getOwnPropertyNames(t).filter(function(e){return"function"==typeof t[e]})},loadModules:function(){for(var e=t.Redactor.modules.length,i=0;i<e;i++)this.bindModuleMethods(t.Redactor.modules[i])},bindModuleMethods:function(t){if(void 0!==this[t]){this[t]=this[t]();for(var e=this.getModuleMethods(this[t]),i=e.length,r=0;r<i;r++)this[t][e[r]]=this[t][e[r]].bind(this)}},air:function(){return{enabled:!1,collapsed:function(){},collapsedEnd:function(){},build:function(){},append:function(){},createContainer:function(){},show:function(){},bindHide:function(){},hide:function(){}}},autosave:function(){return{enabled:!1,html:!1,init:function(){},is:function(){},send:function(){},getHiddenFields:function(){},success:function(){},disable:function(){}}},block:function(){return{format:function(e,i,r,n){if(e="quote"===e?"blockquote":e,this.block.tags=["p","blockquote","pre","h1","h2","h3","h4","h5","h6","div","figure"],-1!==t.inArray(e,this.block.tags))return"p"===e&&void 0===i&&(i="class"),this.buffer.set(),this.utils.isCollapsed()?this.block.formatCollapsed(e,i,r,n):this.block.formatUncollapsed(e,i,r,n)},formatCollapsed:function(e,i,r,n){this.selection.save();var s=this.selection.block(),o=s.tagName.toLowerCase();if(-1===t.inArray(o,this.block.tags))return void this.selection.restore();var a=!1;o===e&&void 0===i&&(e="p",a=!0),a&&(this.block.removeAllClass(),this.block.removeAllAttr());var l;if("blockquote"===o&&this.utils.isEndOfElement(s)){this.marker.remove(),l=document.createElement("p"),l.innerHTML=this.opts.invisibleSpace,t(s).after(l),this.caret.start(l);var c=t(s).children().last();0!==c.length&&"BR"===c[0].tagName&&c.remove()}else l=this.utils.replaceToTag(s,e);if("object"==typeof i){n=r;for(var h in i)l=this.block.setAttr(l,h,i[h],n)}else l=this.block.setAttr(l,i,r,n);return"pre"===e&&1===l.length&&t(l).html(t.trim(t(l).html())),this.selection.restore(),this.block.removeInlineTags(l),l},formatUncollapsed:function(e,i,r,n){this.selection.save();var s=[],o=this.selection.blocks();o[0]&&(t(o[0]).hasClass("redactor-in")||t(o[0]).hasClass("redactor-box"))&&(o=this.core.editor().find(this.opts.blockTags.join(", ")));for(var a=o.length,l=0;l<a;l++){var c=o[l].tagName.toLowerCase();if(-1!==t.inArray(c,this.block.tags)&&"figure"!==c){var h=this.utils.replaceToTag(o[l],e);if("object"==typeof i){n=r;for(var d in i)h=this.block.setAttr(h,d,i[d],n)}else h=this.block.setAttr(h,i,r,n);s.push(h),this.block.removeInlineTags(h)}}if(this.selection.restore(),"pre"===e&&0!==s.length){var u=s[0];t.each(s,function(e,i){0!==e&&(t(u).append("\n"+t.trim(i.html())),t(i).remove())}),s=[],s.push(u)}return s},removeInlineTags:function(e){e=e[0]||e;var i=this.opts.inlineTags,r=["PRE","H1","H2","H3","H4","H5","H6"];if(-1!==t.inArray(e.tagName,r)){if("PRE"!==e.tagName){var n=i.indexOf("a");i.splice(n,1)}t(e).find(i.join(",")).not(".redactor-selection-marker").contents().unwrap()}},setAttr:function(t,e,i,r){if(void 0===e)return t;var n=void 0===r?"replace":r;return t="class"===e?this.block[n+"Class"](i,t):"remove"===n?this.block[n+"Attr"](e,t):"removeAll"===n?this.block[n+"Attr"](e,t):this.block[n+"Attr"](e,i,t)},getBlocks:function(e){if(e=void 0===e?this.selection.blocks():e,t(e).hasClass("redactor-box")){var i=[],r=this.core.editor().children();return t.each(r,t.proxy(function(t,e){this.utils.isBlock(e)&&i.push(e)},this)),i}return e},replaceClass:function(e,i){return t(this.block.getBlocks(i)).removeAttr("class").addClass(e)[0]},toggleClass:function(e,i){return t(this.block.getBlocks(i)).toggleClass(e)[0]},addClass:function(e,i){return t(this.block.getBlocks(i)).addClass(e)[0]},removeClass:function(e,i){return t(this.block.getBlocks(i)).removeClass(e)[0]},removeAllClass:function(e){return t(this.block.getBlocks(e)).removeAttr("class")[0]},replaceAttr:function(e,i,r){return r=this.block.removeAttr(e,r),t(r).attr(e,i)[0]},toggleAttr:function(e,i,r){r=this.block.getBlocks(r);var n=this,s=[];return t.each(r,function(r,o){t(o).attr(e)?s.push(n.block.removeAttr(e,o)):s.push(n.block.addAttr(e,i,o))}),s},addAttr:function(e,i,r){return t(this.block.getBlocks(r)).attr(e,i)[0]},removeAttr:function(e,i){return t(this.block.getBlocks(i)).removeAttr(e)[0]},removeAllAttr:function(e){e=this.block.getBlocks(e);var i=[];return t.each(e,function(t,e){if(void 0!==e.attributes)for(;e.attributes.length;)e.removeAttribute(e.attributes[0].name);i.push(e)}),i}}},buffer:function(){return{set:function(t){void 0===t&&this.buffer.clear(),void 0===t||"undo"===t?this.buffer.setUndo():this.buffer.setRedo()},setUndo:function(){var t=this.selection.saveInstant(),e=this.sBuffer[this.sBuffer.length-1],i=this.core.editor().html();(void 0===e||e[0]!==i)&&this.sBuffer.push([i,t])},setRedo:function(){var t=this.selection.saveInstant();this.sRebuffer.push([this.core.editor().html(),t])},add:function(){this.sBuffer.push([this.core.editor().html(),0])},undo:function(){if(0!==this.sBuffer.length){var t=this.sBuffer.pop();this.buffer.set("redo"),this.core.editor().html(t[0]),this.selection.restoreInstant(t[1]),this.selection.restore(),this.observe.load()}},redo:function(){if(0!==this.sRebuffer.length){var t=this.sRebuffer.pop();this.buffer.set("undo"),this.core.editor().html(t[0]),this.selection.restoreInstant(t[1]),this.selection.restore(),this.observe.load()}},clear:function(){this.sRebuffer=[]}}},build:function(){return{start:function(){if("textarea"!==this.opts.type)throw new Error("Only `<textarea>` types are allowed.");this.build.startTextarea(),this.build.setIn(),this.build.setId(),this.build.enableEditor(),this.build.setOptions(),this.build.callEditor()},createContainerBox:function(){this.$box=t('<div class="redactor-box" role="application" />')},setIn:function(){this.core.editor().addClass("redactor-in")},setId:function(){var t="textarea"===this.opts.type?"redactor-uuid-"+this.uuid:this.$element.attr("id");this.core.editor().attr("id",void 0===t?"redactor-uuid-"+this.uuid:t)},getName:function(){var t=this.$element.attr("name");return void 0===t?"content-"+this.uuid:t},buildTextarea:function(){},loadFromTextarea:function(){this.$editor=t("<div />"),this.$textarea=this.$element,this.$element.attr("name",this.build.getName()),this.$box.insertAfter(this.$element).append(this.$editor).append(this.$element),this.build.setStartAttrs(),this.$editor.addClass("redactor-layer"),this.opts.overrideStyles&&this.$editor.addClass("redactor-styles"),this.$element.hide(),this.$box.prepend('<span class="redactor-voice-label" id="redactor-voice-'+this.uuid+'" aria-hidden="false">'+this.lang.get("accessibility-help-label")+"</span>")},setStartAttrs:function(){this.$editor.attr({"aria-labelledby":"redactor-voice-"+this.uuid,role:"presentation"})},startTextarea:function(){this.build.createContainerBox(),this.build.loadFromTextarea(),this.code.start(this.core.textarea().val()),this.core.textarea().val(this.clean.onSync(this.$editor.html()))},isTag:function(t){return this.$element[0].tagName===t},isInline:function(){return!this.build.isTag("TEXTAREA")&&!this.build.isTag("DIV")&&!this.build.isTag("PRE")},enableEditor:function(){this.core.editor().attr({contenteditable:!0})},setOptions:function(){"inline"===this.opts.type&&(this.opts.enterKey=!1),"inline"!==this.opts.type&&"pre"!==this.opts.type||(this.opts.toolbarMobile=!1,this.opts.toolbar=!1),this.core.editor().attr("spellcheck",this.opts.spellcheck),this.opts.structure&&this.core.editor().addClass("redactor-structure"),this.opts.stylesClass&&this.core.editor().addClass(this.opts.stylesClass),"textarea"===this.opts.type&&(this.core.box().attr("dir",this.opts.direction),this.core.editor().attr("dir",this.opts.direction),this.opts.tabindex&&this.core.editor().attr("tabindex",this.opts.tabindex),this.opts.minHeight?this.core.editor().css("min-height",this.opts.minHeight):this.core.editor().css("min-height","40px"),this.opts.maxHeight&&this.core.editor().css("max-height",this.opts.maxHeight),this.opts.maxWidth&&this.core.editor().css({"max-width":this.opts.maxWidth,margin:"auto"}))},callEditor:function(){this.build.disableBrowsersEditing(),this.events.init(),this.build.setHelpers(),this.toolbarsButtons=this.button.init(),this.toolbar.build(),this.core.editor().on("mouseup.redactor-observe."+this.uuid+" keyup.redactor-observe."+this.uuid+" focus.redactor-observe."+this.uuid+" touchstart.redactor-observe."+this.uuid,t.proxy(this.observe.toolbar,this)),this.core.element().on("blur.callback.redactor",t.proxy(function(){this.button.setInactiveAll()},this)),this.modal.templates(),this.build.plugins(),this.code.html=this.code.cleaned(this.core.editor().html()),this.core.callback("init"),this.core.callback("initToEdit"),this.start=!1},setHelpers:function(){this.opts.focus?setTimeout(this.focus.start,100):this.opts.focusEnd&&setTimeout(this.focus.end,100)},disableBrowsersEditing:function(){try{document.execCommand("enableObjectResizing",!1,!1),document.execCommand("enableInlineTableEditing",!1,!1),document.execCommand("AutoUrlDetect",!1,!1)}catch(t){}},plugins:function(){this.opts.plugins&&t.each(this.opts.plugins,t.proxy(function(i,r){var n="undefined"!=typeof RedactorPlugins&&void 0!==RedactorPlugins[r]?RedactorPlugins:e.fn;if(t.isFunction(n[r])){this[r]=n[r]();for(var s=this.getModuleMethods(this[r]),o=s.length,a=0;a<o;a++)this[r][s[a]]=this[r][s[a]].bind(this);if(void 0!==this[r].langs){var l={};void 0!==this[r].langs[this.opts.lang]?l=this[r].langs[this.opts.lang]:void 0===this[r].langs[this.opts.lang]&&void 0!==this[r].langs.en&&(l=this[r].langs.en);var c=this;t.each(l,function(t,e){void 0===c.opts.curLang[t]&&(c.opts.curLang[t]=e)})}"function"==typeof this[r].init&&this[r].init()}},this))}}},button:function(){return{toolbar:function(){return void 0!==this.button.$toolbar&&this.button.$toolbar?this.button.$toolbar:this.$toolbar},init:function(){return{format:{title:this.lang.get("format"),icon:!0,dropdown:{p:{title:this.lang.get("paragraph"),func:"block.format"},blockquote:{title:this.lang.get("quote"),func:"block.format"},pre:{title:this.lang.get("code"),func:"block.format"},h1:{title:this.lang.get("heading1"),func:"block.format"},h2:{title:this.lang.get("heading2"),func:"block.format"},h3:{title:this.lang.get("heading3"),func:"block.format"},h4:{title:this.lang.get("heading4"),func:"block.format"},h5:{title:this.lang.get("heading5"),func:"block.format"},h6:{title:this.lang.get("heading6"),func:"block.format"}}},bold:{title:this.lang.get("bold-abbr"),icon:!0,label:this.lang.get("bold"),func:"inline.format"},italic:{title:this.lang.get("italic-abbr"),icon:!0,label:this.lang.get("italic"),func:"inline.format"},deleted:{title:this.lang.get("deleted-abbr"),icon:!0,label:this.lang.get("deleted"),func:"inline.format"},underline:{title:this.lang.get("underline-abbr"),icon:!0,label:this.lang.get("underline"),func:"inline.format"},lists:{title:this.lang.get("lists"),icon:!0,dropdown:{unorderedlist:{title:"&bull; "+this.lang.get("unorderedlist"),func:"list.toggle"},orderedlist:{title:"1. "+this.lang.get("orderedlist"),func:"list.toggle"},outdent:{title:"< "+this.lang.get("outdent"),func:"indent.decrease",observe:{element:"li",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}},indent:{title:"> "+this.lang.get("indent"),func:"indent.increase",observe:{element:"li",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}}}},ul:{title:"&bull; "+this.lang.get("bulletslist"),icon:!0,func:"list.toggle"},ol:{title:"1. "+this.lang.get("numberslist"),icon:!0,func:"list.toggle"},outdent:{title:this.lang.get("outdent"),icon:!0,func:"indent.decrease"},indent:{title:this.lang.get("indent"),icon:!0,func:"indent.increase"},image:{title:this.lang.get("image"),icon:!0,func:"image.show"},file:{title:this.lang.get("file"),icon:!0,func:"file.show"},link:{title:this.lang.get("link"),icon:!0,dropdown:{link:{title:this.lang.get("link-insert"),func:"link.show",observe:{element:"a",in:{title:this.lang.get("link-edit")},out:{title:this.lang.get("link-insert")}}},unlink:{title:this.lang.get("unlink"),func:"link.unlink",observe:{element:"a",out:{attr:{class:"redactor-dropdown-link-inactive","aria-disabled":!0}}}}}},horizontalrule:{title:this.lang.get("horizontalrule"),icon:!0,func:"line.insert"}}},setFormatting:function(){for(var t in this.toolbarsButtons.format.dropdown)this.toolbarsButtons.format.dropdown.hasOwnProperty(t)&&-1===this.opts.formatting.indexOf(t)&&delete this.toolbarsButtons.format.dropdown[t]},hideButtons:function(){0!==this.opts.buttonsHide.length&&this.button.hideButtonsSlicer(this.opts.buttonsHide)},hideButtonsOnMobile:function(){this.detect.isMobile()&&0!==this.opts.buttonsHideOnMobile.length&&this.button.hideButtonsSlicer(this.opts.buttonsHideOnMobile)},hideButtonsSlicer:function(e){t.each(e,t.proxy(function(t,e){var i=this.opts.buttons.indexOf(e);-1!==i&&this.opts.buttons.splice(i,1)},this))},load:function(t){this.button.buttons=[],this.opts.buttons.forEach(function(e){if(("image"!==e||this.image.is())&&this.toolbarsButtons.hasOwnProperty(e)){var i=elCreate("li");i.appendChild(this.button.build(e,this.toolbarsButtons[e])[0]),t[0].appendChild(i)}}.bind(this))},buildButtonTooltip:function(){},build:function(e,i){var r=t('<a href="javascript:void(null);" rel="'+e+'" />');if(r.addClass("re-button re-"+e),r.attr({role:"button",tabindex:"-1"}),r.html(i.title),(i.func||i.command||i.dropdown)&&this.button.setEvent(r,e,i),i.dropdown){r.addClass("redactor-toolbar-link-dropdown").attr("aria-haspopup",!0);var n=t('<ul class="dropdownMenu redactor-dropdown-menu redactor-dropdown-menu-'+r[0].rel+'" data-dropdown-allow-flip="horizontal" data-dropdown-ignore-page-scroll="true" />');r.data("dropdown",n),this.dropdown.build(e,n,i.dropdown),this.button.setupDropdown(r[0],n[0])}return this.button.buttons.push(r),r},setupDropdown:function(t,e){require(["Ui/SimpleDropdown"],function(i){i.initFragment(t,e),i.registerCallback(t.id,function(t,e){"close"===e&&this.dropdown.hideOut()}.bind(this)),elData(t,"a11y-mouse-event","mousedown"),elData(t,"aria-expanded",!1),t.addEventListener("click",function(t){t.preventDefault(),t.stopPropagation()})}.bind(this))},getButtons:function(){return this.button.toolbar().find("a.re-button")},getButtonsKeys:function(){return this.button.buttons},setEvent:function(e,i,r){e.on("mousedown",t.proxy(function(t){if(t.preventDefault(),e.hasClass("redactor-button-disabled"))return!1;var n="func",s=r.func;return r.command?(n="command",s=r.command):r.dropdown&&(n="dropdown",s=!1),this.button.toggle(t,i,n,s),!1},this))},toggle:function(t,e,i,r,n){!this.detect.isIe()&&this.detect.isDesktop()||(this.utils.freezeScroll(),t.returnValue=!1),"command"===i?this.inline.format(r):"dropdown"===i?this.dropdown.show(t,e):this.button.clickCallback(t,r,e,n),"dropdown"!==i&&this.dropdown.hideAll(!1),!this.detect.isIe()&&this.detect.isDesktop()||this.utils.unfreezeScroll()},clickCallback:function(e,i,r,n){var s;if(e instanceof Event?e.preventDefault():e&&e.originalEvent&&e.originalEvent.preventDefault(),n=void 0===n?r:n,t.isFunction(i))i.call(this,r);else if("-1"!==i.search(/\./)){if(s=i.split("."),void 0===this[s[0]])return;"object"==typeof n?this[s[0]][s[1]].apply(this,n):this[s[0]][s[1]].call(this,n)}else"object"==typeof n?this[i].apply(this,n):this[i].call(this,n);this.observe.buttons(e,r)},all:function(){return this.button.buttons},get:function(t){if(!1!==this.opts.toolbar)return this.button.toolbar().find("a.re-"+t)},set:function(t,e){if(!1!==this.opts.toolbar){var i=this.button.toolbar().find("a.re-"+t);return i.html(e).attr("aria-label",e),i}},add:function(e,i){if(!0!==this.button.isAdded(e))return t();var r=this.button.build(e,{title:i});return this.button.toolbar().append(t("<li>").append(r)),r},addFirst:function(e,i){if(!0!==this.button.isAdded(e))return t();var r=this.button.build(e,{title:i});return this.button.toolbar().prepend(t("<li>").append(r)),r},addAfter:function(e,i,r){if(!0!==this.button.isAdded(i))return t();var n=this.button.build(i,{title:r}),s=this.button.get(e);return 0!==s.length?s.parent().after(t("<li>").append(n)):this.button.toolbar().append(t("<li>").append(n)),n},addBefore:function(e,i,r){if(!0!==this.button.isAdded(i))return t();var n=this.button.build(i,{title:r}),s=this.button.get(e);return 0!==s.length?s.parent().before(t("<li>").append(n)):this.button.toolbar().append(t("<li>").append(n)),n},isAdded:function(t){var e=this.opts.buttonsHideOnMobile.indexOf(t);return!(!1===this.opts.toolbar||-1!==e&&this.detect.isMobile())},setIcon:function(t,e){this.opts.buttonsTextLabeled||t.html(e).addClass("re-button-icon")},changeIcon:function(t,e){var i=this.button.get(t);0!==i.length&&i.find("i").removeAttr("class").addClass("re-icon-"+e)},addCallback:function(e,i){if(void 0!==e&&!1!==this.opts.toolbar){var r="dropdown"===i?"dropdown":"func",n=e.attr("rel");e.on("mousedown",t.proxy(function(t){if(e.hasClass("redactor-button-disabled"))return!1;this.button.toggle(t,n,r,i)},this))}},addDropdown:function(e,i){if(!1!==this.opts.toolbar){e.addClass("redactor-toolbar-link-dropdown").attr("aria-haspopup",!0);var r=e.attr("rel");this.button.addCallback(e,"dropdown");var n=t('<ul class="dropdownMenu redactor-dropdown-menu redactor-dropdown-menu-'+r+'" data-dropdown-allow-flip="horizontal" data-dropdown-ignore-page-scroll="true" />');return e.data("dropdown",n),i&&(this.dropdown.build(r,n,i),this.button.setupDropdown(e[0],n[0])),n}},setActive:function(t){this.button.get(t).addClass("redactor-act").attr({"aria-pressed":!0,tabindex:0})},setInactive:function(t){this.button.get(t).removeClass("redactor-act").attr({"aria-pressed":!1,tabindex:"html"===t?0:-1})},setInactiveAll:function(t){var e=this.button.toolbar().find("a.re-button");void 0!==t&&(e=e.not(".re-"+t)),e.removeClass("redactor-act").attr({"aria-pressed":!1,tabindex:"html"===t?0:-1})},disable:function(t){this.button.get(t).addClass("redactor-button-disabled").attr("aria-disabled",!0)},enable:function(t){this.button.get(t).removeClass("redactor-button-disabled").attr("aria-disabled",!1)},disableAll:function(t){var e=this.button.toolbar().find("a.re-button");void 0!==t&&(Array.isArray(t)||(t=[t]),t=t.map(function(t){return".re-"+t}),e=e.not(t.join(","))),e.addClass("redactor-button-disabled").attr("aria-disabled",!0)},enableAll:function(){this.button.toolbar().find("a.re-button").removeClass("redactor-button-disabled").attr("aria-disabled",!1)},remove:function(t){this.button.get(t).remove()}}},caret:function(){return{set:function(t,e,i){var r=this.core.editor().scrollTop();this.core.editor().focus(),this.core.editor().scrollTop(r),i=void 0===i?0:1,t=t[0]||t,e=e[0]||e;var n=this.selection.get(),s=this.selection.range(n);try{s.setStart(t,0),s.setEnd(e,i)}catch(t){}this.selection.update(n,s)},prepare:function(t){return this.detect.isFirefox()&&void 0!==this.start&&this.core.editor().focus(),t[0]||t},start:function(e){var i,r;if(e=this.caret.prepare(e)){if("BR"===e.tagName)return this.caret.before(e);var n=t(e).children().first(),s=this.utils.isInlineTag(e.tagName);""===e.innerHTML||s?this.caret.setStartEmptyOrInline(e,s):n&&0!==n.length&&this.utils.isInlineTag(n[0].tagName)&&""===n.text()?this.caret.setStartEmptyOrInline(n[0],!0):(i=window.getSelection(),i.removeAllRanges(),r=document.createRange(),r.selectNodeContents(e),r.collapse(!0),i.addRange(r))}},setStartEmptyOrInline:function(e,i){var r=window.getSelection(),n=document.createRange(),s=document.createTextNode("​");n.setStart(e,0),n.insertNode(s),n.setStartAfter(s),n.collapse(!0),r.removeAllRanges(),r.addRange(n),i||this.core.editor().on("keydown.redactor-remove-textnode",function(){t(s).remove(),t(this).off("keydown.redactor-remove-textnode")})},end:function(e){var i,r;if(e=this.caret.prepare(e)){if("BR"!==e.tagName&&""===e.innerHTML)return this.caret.start(e);if("BR"===e.tagName){var n=document.createElement("span");return n.className="redactor-invisible-space",n.innerHTML="&#x200b;",t(e).after(n),i=window.getSelection(),i.removeAllRanges(),r=document.createRange(),r.setStartBefore(n),r.setEndBefore(n),i.addRange(r),void t(n).replaceWith(function(){return t(this).contents()})}if(e.lastChild&&1===e.lastChild.nodeType)return this.caret.after(e.lastChild);var i=window.getSelection();if(i.getRangeAt||i.rangeCount)try{var r=i.getRangeAt(0);r.selectNodeContents(e),r.collapse(!1),i.removeAllRanges(),i.addRange(r)}catch(t){}}},after:function(e){var i,r;if(e=this.caret.prepare(e)){if("BR"===e.tagName)return this.caret.end(e);if(this.utils.isBlockTag(e.tagName)){var n=this.caret.next(e);return void(void 0===n?this.caret.end(e):("TABLE"===n.tagName?n=t(n).find("th, td").first()[0]:"UL"!==n.tagName&&"OL"!==n.tagName||(n=t(n).find("li").first()[0]),this.caret.start(n)))}var s=document.createTextNode("​");i=window.getSelection(),i.removeAllRanges(),r=document.createRange(),r.setStartAfter(e),r.insertNode(s),r.setStartAfter(s),r.collapse(!0),i.addRange(r)}},before:function(e){var i,r;if(e=this.caret.prepare(e)){if(this.utils.isBlockTag(e.tagName)){var n=this.caret.prev(e);return void(void 0===n?this.caret.start(e):("TABLE"===n.tagName?n=t(n).find("th, td").last()[0]:"UL"!==n.tagName&&"OL"!==n.tagName||(n=t(n).find("li").last()[0]),this.caret.end(n)))}i=window.getSelection(),i.removeAllRanges(),r=document.createRange(),r.setStartBefore(e),r.collapse(!0),i.addRange(r)}},next:function(e){var i=t(e).next();return i.hasClass("redactor-script-tag, redactor-selection-marker")?i.next()[0]:i[0]},prev:function(e){var i=t(e).prev();return i.hasClass("redactor-script-tag, redactor-selection-marker")?i.prev()[0]:i[0]},offset:function(t){return this.offset.get(t)}}},clean:function(){return{onSet:function(e){e=this.clean.savePreCode(e),this.opts.script&&(e=e.replace(/<script(.*?[^>]?)>([\w\W]*?)<\/script>/gi,'<pre class="redactor-script-tag" $1>$2</pre>')),e=e.replace(/\$/g,"&#36;"),e=e.replace(/&amp;/g,"&"),e=e.replace(/<a href="(.*?[^>]?)®(.*?[^>]?)">/gi,'<a href="$1&reg$2">'),e=e.replace(/<span id="selection-marker-1"(.*?[^>]?)>​<\/span>/gi,"###marker1###"),e=e.replace(/<span id="selection-marker-2"(.*?[^>]?)>​<\/span>/gi,"###marker2###");var i=this,r=t("<div/>").html(t.parseHTML(e,document,!0)),n=this.opts.replaceTags;if(n){var s=Object.keys(this.opts.replaceTags);r.find(s.join(",")).each(function(t,e){i.utils.replaceToTag(e,n[e.tagName.toLowerCase()])})}r.find("span, a").attr("data-redactor-span",!0),r.find(this.opts.inlineTags.join(",")).each(function(){var e=t(this);e.attr("style")&&e.attr("data-redactor-style-cache",e.attr("style"))}),e=r.html();var o=["font","html","head","link","body","meta","applet"];return this.opts.script||o.push("script"),e=this.clean.stripTags(e,o),this.opts.removeComments&&(e=e.replace(/<!--[\s\S]*?-->/gi,"")),e=this.paragraphize.load(e),e=e.replace("###marker1###",'<span id="selection-marker-1" class="redactor-selection-marker">​</span>'),e=e.replace("###marker2###",'<span id="selection-marker-2" class="redactor-selection-marker">​</span>'),-1!==e.search(/^(||\s||<br\s?\/?>||&nbsp;)$/i)?this.opts.emptyHtml:e},onGet:function(t){return this.clean.onSync(t)},onSync:function(e){if(e=e.replace(/\u200B/g,""),e=e.replace(/&#x200b;/gi,""),
 -1!==e.search(/^<p>(||\s||<br\s?\/?>||&nbsp;)<\/p>$/i))return"";e=e.replace(/<span(.*?)id="redactor-image-box"(.*?[^>])>([\w\W]*?)<img(.*?)><\/span>/gi,"$3<img$4>"),e=e.replace(/<span(.*?)id="redactor-image-resizer"(.*?[^>])>(.*?)<\/span>/gi,""),e=e.replace(/<span(.*?)id="redactor-image-editter"(.*?[^>])>(.*?)<\/span>/gi,""),e=e.replace(/<img(.*?)style="(.*?)opacity: 0\.5;(.*?)"(.*?)>/gi,'<img$1style="$2$3"$4>');var i=t("<div/>").html(t.parseHTML(e,document,!0));i.find('*[style=""]').removeAttr("style"),i.find('*[class=""]').removeAttr("class"),i.find('*[rel=""]').removeAttr("rel"),i.find('*[data-image=""]').removeAttr("data-image"),i.find('*[alt=""]').removeAttr("alt"),i.find('*[title=""]').removeAttr("title"),i.find("*[data-redactor-style-cache]").removeAttr("data-redactor-style-cache"),i.find(".redactor-invisible-space, .redactor-unlink").each(function(){t(this).contents().unwrap()}),i.find("span, a").removeAttr("data-redactor-span data-redactor-style-cache").each(function(){0===this.attributes.length&&t(this).contents().unwrap()}),i.find("img").removeAttr("rel"),i.find(".redactor-selection-marker, #redactor-insert-marker").remove(),e=i.html(),this.opts.script&&(e=e.replace(/<pre class="redactor-script-tag"(.*?[^>]?)>([\w\W]*?)<\/pre>/gi,"<script$1>$2<\/script>")),e=this.clean.restoreFormTags(e),e=e.replace(new RegExp("<br\\s?/?></h","gi"),"</h"),e=e.replace(new RegExp("<br\\s?/?></li>","gi"),"</li>"),e=e.replace(new RegExp("</li><br\\s?/?>","gi"),"</li>"),e=e.replace(/<pre>/gi,"<pre>\n"),this.opts.preClass&&(e=e.replace(/<pre>/gi,'<pre class="'+this.opts.preClass+'">')),this.opts.linkNofollow&&(e=e.replace(/<a(.*?)rel="nofollow"(.*?[^>])>/gi,"<a$1$2>"),e=e.replace(/<a(.*?[^>])>/gi,'<a$1 rel="nofollow">'));var r={"™":"&trade;","©":"&copy;","…":"&hellip;","—":"&mdash;","‐":"&dash;"};return t.each(r,function(t,i){e=e.replace(new RegExp(t,"g"),i)}),e=e.replace(/&amp;/g,"&"),e=e.replace(/\n{2,}/g,"\n"),this.opts.removeNewlines&&(e=e.replace(/\r?\n/g,"")),e},onPaste:function(e,i,r){if(!0!==r){e=e.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi,"$2"),e=e.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi,"$3"),e=e.replace(/<span[^>]*(font-style: italic; font-weight: bold|font-weight: bold; font-style: italic)[^>]*>([\w\W]*?)<\/span>/gi,"<b><i>$2</i></b>"),e=e.replace(/<span[^>]*(font-style: italic; font-weight: 700|font-weight: 700; font-style: italic)[^>]*>([\w\W]*?)<\/span>/gi,"<b><i>$2</i></b>"),e=e.replace(/<span[^>]*font-style: italic[^>]*>([\w\W]*?)<\/span>/gi,"<i>$1</i>"),e=e.replace(/<span[^>]*font-weight: bold[^>]*>([\w\W]*?)<\/span>/gi,"<b>$1</b>"),e=e.replace(/<span[^>]*font-weight: 700[^>]*>([\w\W]*?)<\/span>/gi,"<b>$1</b>"),e=e.replace(/<o:p[^>]*>/gi,""),e=e.replace(/<\/o:p>/gi,"");this.clean.isHtmlMsWord(e)&&(e=this.clean.cleanMsWord(e))}return e=t.trim(e),i.pre?this.opts.preSpaces&&(e=e.replace(/\t/g,new Array(this.opts.preSpaces+1).join(" "))):(e=this.clean.replaceBrToNl(e),e=this.clean.removeTagsInsidePre(e)),!0!==r&&(e=this.clean.removeEmptyInlineTags(e),!1===i.encode&&(e=e.replace(/&/g,"&amp;"),e=this.clean.convertTags(e,i),e=this.clean.getPlainText(e),e=this.clean.reconvertTags(e,i))),i.text&&(e=this.clean.replaceNbspToSpaces(e),e=this.clean.getPlainText(e)),i.lists&&(e=e.replace("\n","<br>")),i.encode&&(e=this.clean.encodeHtml(e)),i.paragraphize&&(e=e.replace(/ \n/g," "),e=e.replace(/\n /g," "),e=this.paragraphize.load(e),e=e.replace(/<p><\/p>/g,"")),e=e.replace(/<li><p>/g,"<li>"),e=e.replace(/<\/p><\/li>/g,"</li>")},getCurrentType:function(t,e){var i=this.selection.blocks(),r={text:!1,encode:!1,paragraphize:!0,line:this.clean.isHtmlLine(t),blocks:this.clean.isHtmlBlocked(t),pre:!1,lists:!1,block:!0,inline:!0,links:!0,images:!0};return 1===i.length&&this.utils.isCurrentOrParent(["h1","h2","h3","h4","h5","h6","a","figcaption"])?(r.text=!0,r.paragraphize=!1,r.inline=!1,r.images=!1,r.links=!1,r.line=!0):"inline"===this.opts.type||!1===this.opts.enterKey?(r.paragraphize=!1,r.block=!1,r.line=!0):1===i.length&&this.utils.isCurrentOrParent(["li"])?(r.lists=!0,r.block=!1,r.paragraphize=!1,r.images=!1):1===i.length&&this.utils.isCurrentOrParent(["th","td","blockquote"])?(r.block=!1,r.paragraphize=!1):("pre"===this.opts.type||1===i.length&&this.utils.isCurrentOrParent("pre"))&&(r.inline=!1,r.block=!1,r.encode=!0,r.pre=!0,r.paragraphize=!1,r.images=!1,r.links=!1),!0===r.line&&(r.paragraphize=!1),!0===e&&(r.text=!1),r},isHtmlBlocked:function(t){var e=t.match(new RegExp("</("+this.opts.blockTags.join("|").toUpperCase()+")>","gi")),i=t.match(new RegExp("<hr(.*?[^>])>","gi"));return null!==e||null!==i},isHtmlLine:function(t){if(this.clean.isHtmlBlocked(t))return!1;var e=t.match(/<br\s?\/?>/gi),i=t.match(/\n/gi);return!e&&!i},isHtmlMsWord:function(t){return t.match(/class="?Mso|style="[^"]*\bmso-|style='[^'']*\bmso-|w:WordDocument/i)},removeEmptyInlineTags:function(e){var i=this.opts.inlineTags,r=t("<div/>").html(t.parseHTML(e,document,!0)),n=this,s=r.find("span"),o=r.find(i.join(","));return o.removeAttr("style"),o.each(function(){var e=t(this).html();0===this.attributes.length&&n.utils.isEmpty(e)&&t(this).replaceWith(function(){return t(this).contents()})}),s.each(function(){t(this).html();0===this.attributes.length&&t(this).replaceWith(function(){return t(this).contents()})}),e=r.html(),e=e.replace("\x3c!--?php","<?php"),e=e.replace("\x3c!--?","<?"),e=e.replace("?--\x3e","?>"),r.remove(),e},cleanMsWord:function(e){e=e.replace(/<!--[\s\S]*?-->/g,""),e=e.replace(/<o:p>[\s\S]*?<\/o:p>/gi,""),e=e.replace(/\n/g," "),e=e.replace(/<\/p>/gi,'</p><p><br data-redactor="br"></p>'),e=e.replace(/<\/div>|<\/li>|<\/td>/gi,"\n\n");var i=t("<div/>").html(e);elBySelAll("br",i[0],function(t){if("br"===elData(t,"redactor"))t.removeAttribute("data-redactor");else{var e=t.parentNode;if(e&&"P"===e.nodeName){for(var i=elCreate("p");t.nextSibling;)i.appendChild(t.nextSibling);e.parentNode.insertBefore(i,e.nextSibling),elRemove(t)}}});var r=!1,n=1,s=[];return i.find("p[style]").each(function(){var e=t(this).attr("style").match(/mso\-list\:l([0-9]+)\slevel([0-9]+)/);if(e){var o=parseInt(e[1]),a=parseInt(e[2]),l=t(this).html().match(/^[\w]+\./)?"ol":"ul",c=t("<li/>").html(t(this).html());if(c.html(c.html().replace(/^([\w\.]+)</,"<")),c.find("span:first").remove(),1==a&&-1==t.inArray(o,s)){var h=t("<"+l+"/>").attr({"data-level":a,"data-list":o}).html(c);t(this).replaceWith(h),r=o,s.push(o)}else{if(a>n){for(var d=i.find('[data-level="'+n+'"][data-list="'+r+'"]'),u=d,p=n;p<a;p++)h=t("<"+l+"/>"),h.appendTo(u.find("li").last()),u=h;u.attr({"data-level":a,"data-list":o}).html(c)}else{var d=i.find('[data-level="'+a+'"][data-list="'+o+'"]').last();d.append(c)}n=a,r=o,t(this).remove()}}}),i.find("[data-level][data-list]").removeAttr("data-level data-list"),elBySelAll("ol, ul",i[0],function(t){["nextElementSibling","previousElementSibling"].forEach(function(e){for(var i=t[e];i&&"P"===i.nodeName&&""===i.className&&"<br>"===i.innerHTML;)elRemove(i),i=t[e]})}),e=i.html()},replaceNbspToSpaces:function(t){return t.replace("&nbsp;"," ")},replaceBrToNl:function(t){return t.replace(/<br\s?\/?>/gi,"\n")},replaceNlToBr:function(t){return t.replace(/\n/g,"<br />")},convertTags:function(e,i){var r=t("<div>").html(e);r.find("iframe").remove();var n=r.find("a");if(n.removeAttr("style"),!1!==this.opts.pasteLinkTarget&&n.attr("target",this.opts.pasteLinkTarget),i.links&&this.opts.pasteLinks&&r.find("a").each(function(t,e){if(e.href){for(var i,r='#####[a href="'+e.href+'"',n=0,s=e.attributes.length;n<s;n++)i=e.attributes.item(n),"href"!==i.name&&(r+=" "+i.name+'="'+i.value+'"');e.outerHTML=r+"]#####"+e.innerHTML+"#####[/a]#####"}}),e=r.html(),i.images&&this.opts.pasteImages&&(e=e.replace(/<img(.*?)src="(.*?)"(.*?[^>])>/gi,'#####[img$1src="$2"$3]#####')),this.opts.pastePlainText)return e;var s,o=i.lists?["ul","ol","li"]:this.opts.pasteBlockTags;s=i.block||i.lists?i.inline?o.concat(this.opts.pasteInlineTags):o:i.inline?this.opts.pasteInlineTags:[];for(var a=s.length,l=0;l<a;l++)e=e.replace(new RegExp("</"+s[l]+">","gi"),"###/"+s[l]+"###"),"td"===s[l]||"th"===s[l]?e=e.replace(new RegExp("<"+s[l]+'(.*?[^>])((colspan|rowspan)="(.*?[^>])")?(.*?[^>])>',"gi"),"###"+s[l]+" $2###"):this.utils.isInlineTag(s[l])?(e=e.replace(new RegExp("<"+s[l]+'([^>]*)class="([^>]*)"[^>]*>',"gi"),"###"+s[l]+' class="$2"###'),e=e.replace(new RegExp("<"+s[l]+'([^>]*)data-redactor-style-cache="([^>]*)"[^>]*>',"gi"),"###"+s[l]+' cache="$2"###'),e=e.replace(new RegExp("<"+s[l]+"[^>]*>","gi"),"###"+s[l]+"###")):e=e.replace(new RegExp("<"+s[l]+"[^>]*>","gi"),"###"+s[l]+"###");return e},reconvertTags:function(t,e){if((e.links&&this.opts.pasteLinks||e.images&&this.opts.pasteImages)&&(t=t.replace(new RegExp("#####\\[","gi"),"<"),t=t.replace(new RegExp("\\]#####","gi"),">")),this.opts.pastePlainText)return t;var i,r=e.lists?["ul","ol","li"]:this.opts.pasteBlockTags;i=e.block||e.lists?e.inline?r.concat(this.opts.pasteInlineTags):r:e.inline?this.opts.pasteInlineTags:[];for(var n=i.length,s=0;s<n;s++)t=t.replace(new RegExp("###/"+i[s]+"###","gi"),"</"+i[s]+">");for(var s=0;s<n;s++)t=t.replace(new RegExp("###"+i[s]+"###","gi"),"<"+i[s]+">");for(var s=0;s<n;s++)if("td"===i[s]||"th"===i[s])t=t.replace(new RegExp("###"+i[s]+"s?(.*?[^#])###","gi"),"<"+i[s]+"$1>");else if(this.utils.isInlineTag(i[s])){var o="span"===i[s]?' data-redactor-span="true"':"";t=t.replace(new RegExp("###"+i[s]+' cache="(.*?[^#])"###',"gi"),"<"+i[s]+' style="$1"'+o+' data-redactor-style-cache="$1">'),t=t.replace(new RegExp("###"+i[s]+"s?(.*?[^#])###","gi"),"<"+i[s]+"$1>")}return t},cleanPre:function(e){e=void 0===e?t(this.selection.block()).closest("pre",this.core.editor()[0]):e,t(e).find("br").replaceWith(function(){return document.createTextNode("\n")}),t(e).find("p").replaceWith(function(){return t(this).contents()})},removeTagsInsidePre:function(e){var i=t("<div />").append(e);return i.find("pre").replaceWith(function(){var e=t(this).html();return e=e.replace(/<br\s?\/?>|<\/p>|<\/div>|<\/li>|<\/td>/gi,"\n"),e=e.replace(/(<([^>]+)>)/gi,""),t("<pre />").append(e)}),e=i.html(),i.remove(),e},getPlainText:function(e){e=e.replace(/<!--[\s\S]*?-->/gi,""),e=e.replace(/<style[\s\S]*?style>/gi,""),e=e.replace(/<p><\/p>/g,""),e=e.replace(/<\/div>|<\/li>|<\/td>/gi,"\n"),e=e.replace(/<\/p>/gi,"\n\n"),e=e.replace(/<\/H[1-6]>/gi,"\n\n");var i=document.createElement("div");return i.innerHTML=e,e=i.textContent||i.innerText,t.trim(e)},savePreCode:function(t){return t=this.clean.savePreFormatting(t),t=this.clean.saveCodeFormatting(t),t=this.clean.restoreSelectionMarkers(t)},savePreFormatting:function(e){var i=e.match(/<pre(.*?)>([\w\W]*?)<\/pre>/gi);return null===i?e:(t.each(i,t.proxy(function(t,i){var r,n,s,o=[],a=!1;i.match(/<pre(.*?)>(([\n\r\s]+)?)<code(.*?)>/i)?(o=i.match(/<pre(.*?)>(([\n\r\s]+)?)<code(.*?)>([\w\W]*?)<\/code>(([\n\r\s]+)?)<\/pre>/i),a=!0,r=o[5],n=o[1],s=o[4]):(o=i.match(/<pre(.*?)>([\w\W]*?)<\/pre>/i),r=o[2],n=o[1]),r=r.replace(/<br\s?\/?>/g,"\n"),r=r.replace(/&nbsp;/g," "),this.opts.preSpaces&&(r=r.replace(/\t/g,new Array(this.opts.preSpaces+1).join(" "))),r=this.clean.encodeEntities(r),r=r.replace(/\$/g,"&#36;"),e=a?e.replace(i,"<pre"+n+"><code"+s+">"+r+"</code></pre>"):e.replace(i,"<pre"+n+">"+r+"</pre>")},this)),e)},saveCodeFormatting:function(e){var i=e.match(/<code(.*?)>([\w\W]*?)<\/code>/gi);return null===i?e:(t.each(i,t.proxy(function(t,i){var r=i.match(/<code(.*?)>([\w\W]*?)<\/code>/i);r[2]=r[2].replace(/&nbsp;/g," "),r[2]=this.clean.encodeEntities(r[2]),r[2]=r[2].replace(/\$/g,"&#36;"),e=e.replace(i,"<code"+r[1]+">"+r[2]+"</code>")},this)),e)},restoreSelectionMarkers:function(t){return t=t.replace(/&lt;span id=&quot;selection-marker-([0-9])&quot; class=&quot;redactor-selection-marker&quot;&gt;​&lt;\/span&gt;/g,'<span id="selection-marker-$1" class="redactor-selection-marker">​</span>')},saveFormTags:function(t){return t},restoreFormTags:function(t){return t.replace(/<section(.*?) rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi,"<form$1$2>$3</form>")},encodeHtml:function(t){return t=t.replace(/”/g,'"'),t=t.replace(/“/g,'"'),t=t.replace(/‘/g,"'"),t=t.replace(/’/g,"'"),t=this.clean.encodeEntities(t)},encodeEntities:function(t){return t=String(t).replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"'),t=t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},stripTags:function(t,e){if(void 0===e)return t.replace(/(<([^>]+)>)/gi,"");var i=/<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;return t.replace(i,function(t,i){return-1===e.indexOf(i.toLowerCase())?t:""})},removeMarkers:function(t){return t.replace(/<span(.*?[^>]?)class="redactor-selection-marker"(.*?[^>]?)>([\w\W]*?)<\/span>/gi,"")},removeSpaces:function(e){return e=t.trim(e),e=e.replace(/\n/g,""),e=e.replace(/[\t]*/g,""),e=e.replace(/\n\s*\n/g,"\n"),e=e.replace(/^[\s\n]*/g," "),e=e.replace(/[\s\n]*$/g," "),e=e.replace(/>\s{2,}</g,"> <"),e=e.replace(/\n\n/g,"\n"),e=e.replace(/\u200B/g,"")},removeSpacesHard:function(e){return e=t.trim(e),e=e.replace(/\n/g,""),e=e.replace(/[\t]*/g,""),e=e.replace(/\n\s*\n/g,"\n"),e=e.replace(/^[\s\n]*/g,""),e=e.replace(/[\s\n]*$/g,""),e=e.replace(/>\s{2,}</g,"><"),e=e.replace(/\n\n/g,"\n"),e=e.replace(/\u200B/g,"")},normalizeCurrentHeading:function(){var t=this.selection.block();this.utils.isCurrentOrParentHeader()&&t&&t.normalize()}}},code:function(){return{syncFire:!0,html:!1,start:function(e){e=t.trim(e),e=e.replace(/^(<span id="selection-marker-1" class="redactor-selection-marker">​<\/span>)/,""),e=this.clean.onSet(e),e=e.replace(/<p><span id="selection-marker-1" class="redactor-selection-marker">​<\/span><\/p>/,""),this.events.stopDetectChanges(),this.core.editor().html(e),this.observe.load(),this.events.startDetectChanges()},set:function(e,i){e=t.trim(e),i=i||{},i.start&&(this.start=i.start),"textarea"===this.opts.type?e=this.clean.onSet(e):"div"===this.opts.type&&""===e&&(e=this.opts.emptyHtml),this.core.editor().html(e),"textarea"===this.opts.type&&this.code.sync()},get:function(){if("textarea"===this.opts.type)return this.core.textarea().val();var t=this.core.editor().html();return t=this.clean.onGet(t)},sync:function(){if(this.code.syncFire){var e=this.core.editor().html(),i=this.code.cleaned(e);if(!this.code.isSync(i)){if(this.code.html=i,"textarea"!==this.opts.type)return this.core.callback("sync",e),void this.core.callback("change",e);"textarea"===this.opts.type&&setTimeout(t.proxy(function(){this.code.startSync(e)},this),10)}}},startSync:function(t){t=this.core.callback("syncBefore",t),t=this.clean.onSync(t),this.core.textarea().val(t),this.core.callback("sync",t),!1===this.start&&this.core.callback("change",t),this.start=!1},isSync:function(t){var e=!1!==this.code.html&&this.code.html;return!1!==e&&e===t},cleaned:function(t){return t=t.replace(/\u200B/g,""),this.clean.removeMarkers(t)}}},core:function(){return{id:function(){return this.$editor.attr("id")},element:function(){return this.$element},editor:function(){return void 0===this.$editor?t():this.$editor},textarea:function(){return this.$textarea},box:function(){return"textarea"===this.opts.type?this.$box:this.$element},toolbar:function(){return!!this.$toolbar&&this.$toolbar},air:function(){return!!this.$air&&this.$air},object:function(){return t.extend({},this)},structure:function(){this.core.editor().toggleClass("redactor-structure")},addEvent:function(t){this.core.event=t},getEvent:function(){return this.core.event},callback:function(e,i,r){var n=!1,s=t._data(this.core.element()[0],"events");if(void 0!==s&&void 0!==s[e])for(var o=s[e].length,a=0;a<o;a++){var l=s[e][a].namespace;if("callback.redactor"===l){var c=s[e][a].handler,h=void 0===r?[i]:[i,r];n=void 0===h?c.call(this,i):c.call(this,i,h)}}if(n)return n;if(void 0===this.opts.callbacks[e])return void 0===r?i:r;var d=this.opts.callbacks[e];return t.isFunction(d)?void 0===r?d.call(this,i):d.call(this,i,r):void 0===r?i:r},destroy:function(){this.opts.destroyed=!0,this.core.callback("destroy"),t("#redactor-voice-"+this.uuid).remove(),this.core.editor().removeClass("redactor-in redactor-styles redactor-structure redactor-layer-img-edit"),this.core.editor().off("keydown.redactor-remove-textnode"),this.core.editor().off(".redactor-observe."+this.uuid),this.$element.off(".redactor").removeData("redactor"),this.core.editor().off(".redactor"),t(document).off(".redactor-air."+this.uuid),t(document).off("mousedown.redactor-blur."+this.uuid),t(document).off("mousedown.redactor."+this.uuid),t(document).off("touchstart.redactor."+this.uuid+" click.redactor."+this.uuid),t(window).off(".redactor-toolbar."+this.uuid),t(window).off("touchmove.redactor."+this.uuid),t("body").off("scroll.redactor."+this.uuid),t(this.opts.toolbarFixedTarget).off("scroll.redactor."+this.uuid);var e=this;!1!==this.opts.plugins&&t.each(this.opts.plugins,function(i,r){t(window).off(".redactor-plugin-"+r),t(document).off(".redactor-plugin-"+r),t("body").off(".redactor-plugin-"+r),e.core.editor().off(".redactor-plugin-"+r)}),this.$element.off("click.redactor-click-to-edit"),this.$element.removeClass("redactor-click-to-edit"),this.core.editor().removeClass("redactor-layer"),this.core.editor().removeAttr("contenteditable");var i=this.code.get();this.opts.toolbar&&this.$toolbar&&this.$toolbar.find("a").each(function(){var e=t(this);e.data("dropdown")&&(e.data("dropdown").remove(),e.data("dropdown",{}))}),"textarea"===this.opts.type&&(this.$box.after(this.$element),this.$box.remove(),this.$element.val(i).show()),this.opts.toolbar&&this.$toolbar&&this.$toolbar.remove(),this.$modalBox&&this.$modalBox.remove(),this.$modalOverlay&&this.$modalOverlay.remove(),t(".redactor-link-tooltip").remove()}}},detect:function(){return{isWebkit:function(){return/webkit/.test(this.opts.userAgent)},isFirefox:function(){return this.opts.userAgent.indexOf("firefox")>-1},isIe:function(t){if(document.documentMode||/Edge/.test(navigator.userAgent))return"edge";var e;return e=RegExp("msie"+(isNaN(t)?"":"\\s"+t),"i").test(navigator.userAgent),e||(e=!!navigator.userAgent.match(/Trident.*rv[ :]*11\./)),e},isMobile:function(){return/(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent)},isDesktop:function(){return!/(iPhone|iPod|iPad|BlackBerry|Android)/.test(navigator.userAgent)},isIpad:function(){return/iPad/.test(navigator.userAgent)}}},dropdown:function(){return{active:!1,button:!1,key:!1,position:[],getDropdown:function(){return this.dropdown.active},build:function(e,i,r){var n,s=document.createDocumentFragment();for(var o in r)if(r.hasOwnProperty(o)){n=r[o];var a=this.dropdown.buildItem(o,n);this.observe.addDropdown(t(a),o,n),s.appendChild(a)}for(var l=!1,c=0,h=s.childNodes.length;c<h;c++)if(s.childNodes[c].nodeType===Node.ELEMENT_NODE){l=!0;break}l&&(i[0].rel=e,i[0].appendChild(s))},buildFormatting:function(){},buildItem:function(e,i){var r=elCreate("li");return void 0!==i.classname&&r.classList.add(i.classname),0===e.toLowerCase().indexOf("divider")?(r.classList.add("redactor-dropdown-divider"),r):(r.innerHTML='<a href="#" class="redactor-dropdown-'+e+'" role="button"><span>'+i.title+"</span></a>",t(r.children[0]).on("mousedown",function(t){t.preventDefault(),this.dropdown.buildClick(t,e,i)}.bind(this)),r)},buildClick:function(e,i,r){if(!t(e.target).hasClass("redactor-dropdown-link-inactive")){var n=this.dropdown.buildCommand(r);void 0!==r.args?this.button.toggle(e,i,n.type,n.callback,r.args):this.button.toggle(e,i,n.type,n.callback)}},buildCommand:function(t){var e={};return e.type="func",e.callback=t.func,t.command?(e.type="command",e.callback=t.command):t.dropdown&&(e.type="dropdown",e.callback=t.dropdown),e},show:function(e,i){this.detect.isDesktop()&&this.core.editor().focus(),this.dropdown.hideAll(!1,i),this.dropdown.key=i,this.dropdown.button=this.button.get(this.dropdown.key),require(["Ui/SimpleDropdown"],function(e){var i=this.dropdown.button[0].id;e.toggleDropdown(i),e.isOpen(i)?(this.dropdown.active=t(e.getDropdownMenu(i)),this.core.callback("dropdownShow",{dropdown:this.dropdown.active,key:this.dropdown.key,button:this.dropdown.button}),this.button.setActive(this.dropdown.key),this.dropdown.button.addClass("dropact").attr("aria-expanded",!0),this.dropdown.enableCallback()):this.dropdown.hide()}.bind(this)),e.preventDefault()},showIsFixedToolbar:function(){},showIsUnFixedToolbar:function(){},enableEvents:function(){},enableCallback:function(){this.core.callback("dropdownShown",{dropdown:this.dropdown.active,key:this.dropdown.key,button:this.dropdown.button})},getButtonPosition:function(){},closeHandler:function(){},hideAll:function(t,e){this.dropdown.hideOut(e)},hide:function(){this.dropdown.hideOut()},hideOut:function(t){if(!1!==this.dropdown.active&&this.dropdown.button[0].rel!==t){this.core.callback("dropdownHide",this.dropdown.active);var e=this.dropdown.button[0].id;require(["Ui/SimpleDropdown"],function(t){t.close(e)}),this.dropdown.button.removeClass("redactor-act dropact").attr("aria-expanded",!1),this.dropdown.button=!1,this.dropdown.key=!1,this.dropdown.active=!1}}}},events:function(){return{focused:!1,blured:!0,dropImage:!1,stopChanges:!1,stopDetectChanges:function(){this.events.stopChanges=!0},startDetectChanges:function(){var t=this;setTimeout(function(){t.events.stopChanges=!1},1)},dragover:function(e){e.preventDefault(),e.stopPropagation(),"IMG"===e.target.tagName&&t(e.target).addClass("redactor-image-dragover")},dragleave:function(t){this.core.editor().find("img").removeClass("redactor-image-dragover")},drop:function(t){return t=t.originalEvent||t,this.core.editor().find("img").removeClass("redactor-image-dragover"),"inline"===this.opts.type||"pre"===this.opts.type?(t.preventDefault(),!1):void 0===window.FormData||!t.dataTransfer||(0===t.dataTransfer.files.length?this.events.onDrop(t):(this.events.onDropUpload(t),void this.core.callback("drop",t)))},click:function(t){var e=this.core.getEvent(),i="click"!==e&&"arrow"!==e&&"click";this.core.addEvent(i),this.utils.disableSelectAll(),this.core.callback("click",t)},focus:function(t){if(!this.rtePaste&&(this.events.isCallback("focus")&&this.core.callback("focus",t),this.events.focused=!0,this.events.blured=!1,!1===this.selection.current())){var e=this.selection.get(),i=this.selection.range(e);i.setStart(this.core.editor()[0],0),i.setEnd(this.core.editor()[0],0),this.selection.update(e,i)}},blur:function(e){this.start||this.rtePaste||0===t(e.target).closest("#"+this.core.id()+", .redactor-toolbar, .redactor-dropdown, #redactor-modal-box").length&&(!this.events.blured&&this.events.isCallback("blur")&&this.core.callback("blur",e),this.events.focused=!1,this.events.blured=!0)},touchImageEditing:function(){var e=-1;this.events.imageEditing=!1,t(window).on("touchmove.redactor."+this.uuid,t.proxy(function(){this.events.imageEditing=!0,-1!==e&&clearTimeout(e),e=setTimeout(t.proxy(function(){this.events.imageEditing=!1},this),500)},this))},init:function(){this.core.editor().on("dragover.redactor dragenter.redactor",t.proxy(this.events.dragover,this)),this.core.editor().on("dragleave.redactor",t.proxy(this.events.dragleave,this)),this.core.editor().on("drop.redactor",t.proxy(this.events.drop,this)),this.core.editor().on("click.redactor",t.proxy(this.events.click,this)),this.core.editor().on("paste.redactor",t.proxy(this.paste.init,this)),this.core.editor().on("keydown.redactor",t.proxy(this.keydown.init,this)),this.core.editor().on("keyup.redactor",t.proxy(this.keyup.init,this)),this.core.editor().on("focus.redactor",t.proxy(this.events.focus,this)),t(document).on("mousedown.redactor-blur."+this.uuid,t.proxy(this.events.blur,this)),this.events.touchImageEditing(),this.events.createObserver(),this.events.setupObserver()},createObserver:function(){var e=this;this.events.observer=new MutationObserver(function(i){i.forEach(t.proxy(e.events.iterateObserver,e))})},iterateObserver:function(t){var e=!1;(("textarea"===this.opts.type||"div"===this.opts.type)&&!this.detect.isFirefox()&&t.target===this.core.editor()[0]||"class"===t.attributeName&&t.target===this.core.editor()[0]||"data-vivaldi-spatnav-clickable"==t.attributeName)&&(e=!0),e||(this.observe.load(),this.events.changeHandler())},setupObserver:function(){this.events.observer.observe(this.core.editor()[0],{attributes:!0,subtree:!0,childList:!0,characterData:!0,characterDataOldValue:!0})},changeHandler:function(){this.events.stopChanges||this.code.sync()},onDropUpload:function(t){if(t.preventDefault(),t.stopPropagation(),(this.opts.dragImageUpload||this.opts.dragFileUpload)&&(null!==this.opts.imageUpload||null!==this.opts.fileUpload)){"IMG"===t.target.tagName&&(this.events.dropImage=t.target);for(var e=t.dataTransfer.files,i=e.length,r=0;r<i;r++)this.upload.directUpload(e[r],t)}},onDrop:function(t){this.core.callback("drop",t)},isCallback:function(e){return void 0!==this.opts.callbacks[e]&&t.isFunction(this.opts.callbacks[e])},stopDetect:function(){this.events.stopDetectChanges()},startDetect:function(){this.events.startDetectChanges()}}},file:function(){return{is:function(){},show:function(){},insert:function(){},release:function(){},text:function(t){}}},focus:function(){return{start:function(){if(this.core.editor().focus(),"inline"!==this.opts.type){var t=this.focus.first();!1!==t&&this.caret.start(t)}},end:function(){this.core.editor().focus();var t=this.opts.inline?this.core.editor():this.focus.last();if(0!==t.length){var e=this.focus.lastChild(t);if(this.detect.isWebkit()||!1===e){var i=this.selection.get(),r=this.selection.range(i);null!==r?(r.selectNodeContents(t[0]),r.collapse(!1),this.selection.update(i,r)):this.caret.end(t)}else this.caret.end(e)}},first:function(){var t=this.core.editor().children().first();return(0!==t.length||0!==t[0].length&&"BR"!==t[0].tagName&&"HR"!==t[0].tagName&&3!==t[0].nodeType)&&("UL"===t[0].tagName||"OL"===t[0].tagName?t.find("li").first():t)},last:function(){return this.core.editor().children().last()},lastChild:function(t){var e=t[0].lastChild;return!(null===e||!this.utils.isInlineTag(e.tagName))&&e},is:function(){return this.core.editor()[0]===document.activeElement}}},image:function(){return{is:function(){return!(!this.opts.imageUpload||!this.opts.imageUpload&&!this.opts.s3)},show:function(){this.modal.load("image",this.lang.get("image"),700),this.upload.init("#redactor-modal-image-droparea",this.opts.imageUpload,this.image.insert),this.modal.show()},insert:function(e,i,r){var n;if(void 0!==e.error)return this.modal.close(),this.events.dropImage=!1,void this.core.callback("imageUploadError",e,r);if(!1!==this.events.dropImage)return n=t(this.events.dropImage),this.core.callback("imageDelete",n[0].src,n),n.attr("src",e.url),this.events.dropImage=!1,void this.core.callback("imageUpload",n,e);var s=t("<"+this.opts.imageTag+">");n=t("<img>"),n.attr("src",e.url);var o=void 0===e.id?"":e.id,a=void 0===e.s3?"image":"s3";n.attr("data-"+a,o),s.append(n);var l=this.utils.isTag(this.selection.current(),"pre");if(i){this.marker.remove();var c=this.insert.nodeToPoint(r,this.marker.get()),h=t(c).next();this.selection.restore(),this.buffer.set(),void 0!==h&&0!==h.length&&"IMG"===h[0].tagName?(this.core.callback("imageDelete",h[0].src,h),h.closest("figure, p",this.core.editor()[0]).replaceWith(s),this.caret.after(s)):(l?t(l).after(s):this.insert.node(s),this.caret.after(s))}else this.modal.close(),this.buffer.set(),l?t(l).after(s):this.insert.node(s),this.caret.after(s);this.events.dropImage=!1;var d=n[0].nextSibling,u=s.next(),p=t(d).text().replace(/\u200B/g,""),f=u.text().replace(/\u200B/g,"");""===p&&t(d).remove(),1===u.length&&"FIGURE"===u[0].tagName&&""===f&&u.remove(),null!==i?this.core.callback("imageUpload",n,e):this.core.callback("imageInserted",n,e)},setEditable:function(e){if(e.on("dragstart",function(t){t.preventDefault()}),this.opts.imageResizable){var i=t.proxy(function(i){this.observe.image=e,this.image.resizer=this.image.loadEditableControls(e),t(document).on("mousedown.redactor-image-resize-hide."+this.uuid,t.proxy(this.image.hideResize,this)),this.image.resizer&&this.image.resizer.on("mousedown.redactor touchstart.redactor",t.proxy(function(t){this.image.setResizable(t,e)},this))},this);e.off("mousedown.redactor").on("mousedown.redactor",t.proxy(this.image.hideResize,this)),e.off("click.redactor touchstart.redactor").on("click.redactor touchstart.redactor",i)}else e.off("click.redactor touchstart.redactor").on("click.redactor touchstart.redactor",t.proxy(function(i){setTimeout(t.proxy(function(){this.image.showEdit(e)},this),200)},this))},setResizable:function(t,e){t.preventDefault(),this.image.resizeHandle={x:t.pageX,y:t.pageY,el:e,ratio:e.width()/e.height(),h:e.height()},t=t.originalEvent||t,t.targetTouches&&(this.image.resizeHandle.x=t.targetTouches[0].pageX,this.image.resizeHandle.y=t.targetTouches[0].pageY),this.image.startResize()},startResize:function(){t(document).on("mousemove.redactor-image-resize touchmove.redactor-image-resize",t.proxy(this.image.moveResize,this)),t(document).on("mouseup.redactor-image-resize touchend.redactor-image-resize",t.proxy(this.image.stopResize,this))},moveResize:function(t){t.preventDefault(),t=t.originalEvent||t;var e=this.image.resizeHandle.h;t.targetTouches?e+=t.targetTouches[0].pageY-this.image.resizeHandle.y:e+=t.pageY-this.image.resizeHandle.y;var i=Math.round(e*this.image.resizeHandle.ratio);e<50||i<100||this.core.editor().width()<=i||(this.image.resizeHandle.el.attr({width:i,height:e}),this.image.resizeHandle.el.width(i),this.image.resizeHandle.el.height(e),this.code.sync())},stopResize:function(){this.handle=!1,t(document).off(".redactor-image-resize"),this.image.hideResize()},hideResize:function(e){if(!e||0===t(e.target).closest("#redactor-image-box",this.$editor[0]).length){if(e&&"IMG"==e.target.tagName){t(e.target)}var i=this.$editor.find("#redactor-image-box");0!==i.length&&(t("#redactor-image-editter").remove(),t("#redactor-image-resizer").remove(),i.find("img").css({marginTop:i[0].style.marginTop,marginBottom:i[0].style.marginBottom,marginLeft:i[0].style.marginLeft,marginRight:i[0].style.marginRight}),i.css("margin",""),i.find("img").css("opacity",""),i.replaceWith(function(){return t(this).contents()}),t(document).off("mousedown.redactor-image-resize-hide."+this.uuid),void 0!==this.image.resizeHandle&&this.image.resizeHandle.el.attr("rel",this.image.resizeHandle.el.attr("style")))}},loadResizableControls:function(e,i){if(this.opts.imageResizable&&!this.detect.isMobile()){var r=t('<span id="redactor-image-resizer" data-redactor="verified"></span>');return this.detect.isDesktop()||r.css({width:"15px",height:"15px"}),r.attr("contenteditable",!1),i.append(r),i.append(e),r}return i.append(e),!1},loadEditableControls:function(e){if(0===t("#redactor-image-box").length){var i=t('<span id="redactor-image-box" data-redactor="verified">');if(i.css("float",e.css("float")).attr("contenteditable",!1),"auto"!=e[0].style.margin?(i.css({marginTop:e[0].style.marginTop,marginBottom:e[0].style.marginBottom,marginLeft:e[0].style.marginLeft,marginRight:e[0].style.marginRight}),e.css("margin","")):i.css({display:"block",margin:"auto"}),e.css("opacity",".5").after(i),this.opts.imageEditable){this.image.editter=t('<span id="redactor-image-editter" data-redactor="verified">'+this.lang.get("edit")+"</span>"),this.image.editter.attr("contenteditable",!1),this.image.editter.on("click",t.proxy(function(){this.image.showEdit(e)},this)),i.append(this.image.editter);var r=this.image.editter.innerWidth();this.image.editter.css("margin-left","-"+r/2+"px")}return this.image.loadResizableControls(e,i)}},showEdit:function(e){if(!this.events.imageEditing){this.observe.image=e;var i=e.closest("a",this.$editor[0]),r=e.closest("figure",this.$editor[0]),n=0!==r.length?r:e;if(this.modal.load("image-edit",this.lang.get("edit"),705),this.image.buttonDelete=this.modal.getDeleteButton().text(this.lang.get("delete")),this.image.buttonSave=this.modal.getActionButton().text(this.lang.get("save")),this.image.buttonDelete.on("click",t.proxy(this.image.remove,this)),
 this.image.buttonSave.on("click",t.proxy(this.image.update,this)),!1===this.opts.imageCaption)t("#redactor-image-caption").val("").hide().prev().hide();else{var s=e.closest(this.opts.imageTag,this.$editor[0]),o=s.find("figcaption");0!==o&&t("#redactor-image-caption").val(o.text()).show()}if(this.opts.imagePosition){var a=0!==r.length?"center"===n.css("text-align"):"block"==n.css("display")&&"none"==n.css("float"),l=a?"center":n.css("float");t("#redactor-image-align").val(l)}else t(".redactor-image-position-option").hide();t("#redactor-image-preview").html(t('<img src="'+e.attr("src")+'" style="max-width: 100%;">')),t("#redactor-image-title").val(e.attr("alt")),0!==i.length&&(t("#redactor-image-link").val(i.attr("href")),"_blank"===i.attr("target")&&t("#redactor-image-link-blank").prop("checked",!0)),t(".redactor-link-tooltip").remove(),this.modal.show(),this.detect.isDesktop()&&t("#redactor-image-title").focus()}},update:function(){var e=this.observe.image,i=e.closest("a",this.core.editor()[0]),r=t("#redactor-image-title").val().replace(/(<([^>]+)>)/gi,"");e.attr("alt",r).attr("title",r),this.image.setFloating(e);var n=t.trim(t("#redactor-image-link").val()).replace(/(<([^>]+)>)/gi,"");if(""!==n){var s="((xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}",o=new RegExp("^(http|ftp|https)://"+s,"i"),a=new RegExp("^"+s,"i");-1===n.search(o)&&0===n.search(a)&&this.opts.linkProtocol&&(n=this.opts.linkProtocol+"://"+n);var l=!!t("#redactor-image-link-blank").prop("checked");if(0===i.length){var c=t('<a href="'+n+'" id="redactor-img-tmp">'+this.utils.getOuterHtml(e)+"</a>");l&&c.attr("target","_blank"),e=e.replaceWith(c),i=this.core.editor().find("#redactor-img-tmp"),i.removeAttr("id")}else i.attr("href",n),l?i.attr("target","_blank"):i.removeAttr("target")}else 0!==i.length&&i.replaceWith(this.utils.getOuterHtml(e));this.image.addCaption(e,i),this.modal.close(),this.buffer.set()},setFloating:function(e){var i=e.closest("figure",this.$editor[0]),r=0!==i.length?i:e,n=t("#redactor-image-align").val(),s="",o="",a="",l="";switch(n){case"left":s="left",a="0 "+this.opts.imageFloatMargin+" "+this.opts.imageFloatMargin+" 0";break;case"right":s="right",a="0 0 "+this.opts.imageFloatMargin+" "+this.opts.imageFloatMargin;break;case"center":0!==i.length?l="center":(o="block",a="auto")}r.css({float:s,display:o,margin:a,"text-align":l}),r.attr("rel",e.attr("style"))},addCaption:function(e,i){var r=t("#redactor-image-caption").val(),n=0!==i.length?i:e,s=n.next();0!==s.length&&"FIGCAPTION"===s[0].tagName||(s=!1),""!==r?!1===s?(s=t("<figcaption />").text(r),n.after(s)):s.text(r):!1!==s&&s.remove()},remove:function(e,i,r){i=void 0===i?t(this.observe.image):i,"boolean"!=typeof e&&this.buffer.set(),this.events.stopDetectChanges();var n=i.closest("a",this.core.editor()[0]),s=i.closest(this.opts.imageTag,this.core.editor()[0]);i.parent();if(!1===this.core.callback("imageDelete",e,i[0]))return e&&e.preventDefault(),!1;0!==t("#redactor-image-box").length&&t("#redactor-image-box").parent();var o,a;0!==s.length?(a=s.prev(),o=s.next(),s.remove()):0!==n.length?(n.parent(),n.remove()):i.remove(),t("#redactor-image-box").remove(),!1!==e&&(o&&0!==o.length?this.caret.start(o):a&&0!==a.length&&this.caret.end(a)),"boolean"!=typeof e&&this.modal.close(),this.utils.restoreScroll(),this.observe.image=!1,this.events.startDetectChanges(),this.code.sync()}}},indent:function(){return{increase:function(){if(this.list.get()){var e=t(this.selection.current()).closest("li"),i=e.closest("ul, ol",this.core.editor()[0]),r=e.closest("li"),n=r.prev();if(0!==n.length&&"LI"===n[0].tagName)if(this.buffer.set(),this.utils.isCollapsed()){var s=i[0].tagName,o=t("<"+s+" />");this.selection.save();var a=n.find("ol").first();if(1===a.length)a.append(e);else{var s=i[0].tagName,o=t("<"+s+" />");o.append(e),n.append(o)}this.selection.restore()}else document.execCommand("indent"),this.selection.save(),this.indent.removeEmpty(),this.indent.normalize(),this.selection.restore()}},decrease:function(){if(this.list.get()){var e=t(this.selection.current()).closest("li");e.closest("ul, ol",this.core.editor()[0]);this.buffer.set(),document.execCommand("outdent");var i=t(this.selection.current()).closest("li",this.core.editor()[0]);if(this.utils.isCollapsed()&&this.indent.repositionItem(i),0===i.length){document.execCommand("formatblock",!1,"p"),i=t(this.selection.current());var r=i.next();0!==r.length&&"BR"===r[0].tagName&&r.remove()}this.selection.save(),this.indent.removeEmpty(),this.indent.normalize(),this.selection.restore()}},repositionItem:function(t){var e=t.next();0===e.length||"UL"===e[0].tagName&&"OL"===e[0].tagName||t.append(e);var i=t.prev();if(0!==i.length&&"LI"!==i[0].tagName){this.selection.save();t.parents("li",this.core.editor()[0]).after(t),this.selection.restore()}},normalize:function(){this.core.editor().find("li").each(t.proxy(function(e,i){var r=t(i),n="";0!==this.opts.keepStyleAttr.length&&(n=","+this.opts.keepStyleAttr.join(",")),r.find(this.opts.inlineTags.join(",")).not("img"+n).removeAttr("style");var s=r.parent();if(0!==s.length&&"LI"===s[0].tagName)return void s.after(r);var o=r.next();0===o.length||"UL"!==o[0].tagName&&"OL"!==o[0].tagName||r.append(o)},this))},removeEmpty:function(e){var i=this.core.editor().find("ul, ol"),r=this.core.editor().find("li");r.each(t.proxy(function(t,e){this.indent.removeItemEmpty(e)},this)),i.each(t.proxy(function(t,e){this.indent.removeItemEmpty(e)},this)),r.each(t.proxy(function(t,e){this.indent.removeItemEmpty(e)},this))},removeItemEmpty:function(e){var i=e.innerHTML.replace(/[\t\s\n]/g,"");""===(i=i.replace(/<span><\/span>/g,""))&&t(e).remove()}}},inline:function(){return{format:function(t,e,i,r){if(!this.utils.isCurrentOrParent(["PRE","CODE"])){var n=this.inline.getParams(e,i,r);t=this.inline.arrangeTag(t),this.buffer.set(),this.utils.isCollapsed()?this.inline.formatCollapsed(t,n):this.inline.formatUncollapsed(t,n)}},formatCollapsed:function(e,i){var r,n=this.selection.inline();if(n){var s=n.tagName.toLowerCase();if(s===e)if(this.utils.isEmpty(n.innerHTML))this.caret.after(n),t(n).remove();else{var o=this.inline.insertBreakpoint(n,s);this.caret.after(o)}else if(0===t(n).closest(e).length)r=this.inline.insertInline(e),r=this.inline.setParams(r,i);else{var o=this.inline.insertBreakpoint(n,s);this.caret.after(o)}}else r=this.inline.insertInline(e),r=this.inline.setParams(r,i)},formatUncollapsed:function(e,i){this.selection.save();var r=this.inline.getClearedNodes();this.inline.setNodesStriked(r,e,i),this.selection.restore(),document.execCommand("strikethrough"),this.selection.saveInstant();for(var n,s,o=this.core.editor()[0].querySelectorAll('[style*="line-through"]'),a=0,l=o.length;a<l;a++)n=o[0],s=document.createElement("strike"),n.parentNode.insertBefore(s,n),s.appendChild(n),n.style.removeProperty("text-decoration");var c=this;this.core.editor().find("strike").each(function(){var r=c.utils.replaceToTag(this,e);c.inline.setParams(r[0],i);var n=r.find(e),s=r.parent(),o=s.parent();if(0!==o.length&&o[0].tagName.toLowerCase()===e&&o.html()==s[0].outerHTML)return r.replaceWith(function(){return t(this).contents()}),void o.replaceWith(function(){return t(this).contents()});0!==n.length&&c.inline.cleanInsideOrParent(n,i),s.html()==r[0].outerHTML&&c.inline.cleanInsideOrParent(s,i),c.detect.isFirefox()&&c.core.editor().find(e+":empty").remove()}),this.selection.restoreInstant()},cleanInsideOrParent:function(t,e){if(e)for(var i in e.data)this.inline.removeSpecificAttr(t,i,e.data[i])},getClearedNodes:function(){for(var e=this.selection.nodes(),i=[],r=e.length,n=0,s=0;s<r;s++)if(t(e[s]).hasClass("redactor-selection-marker")){n=s+2;break}for(var s=0;s<r;s++)s>=n&&!this.utils.isBlockTag(e[s].tagName)&&i.push(e[s]);return i},isConvertableAttr:function(e,i,r){var n=t(e).attr(i);if(n)if("style"===i){r=t.trim(r).replace(/;$/,"");for(var s=r.split(";"),o=0,a=0;a<s.length;a++){var l=s[a].split(":"),c=t.trim(l[0]),h=t.trim(l[1]);if(-1!==c.search(/color/)){var d=t(e).css(c);!d||d!==h&&this.utils.rgb2hex(d)!==h||o++}else t(e).css(c)===h&&o++}if(o===s.length)return 1}else if(n===r)return 1;return 0},isConvertable:function(t,e,i,r){if(e===i){if(!r)return!0;var n=0;for(var s in r.data)n+=this.inline.isConvertableAttr(t,s,r.data[s]);if(n===Object.keys(r.data).length)return!0}return!1},setNodesStriked:function(e,i,r){for(var n=0;n<e.length;n++){var s=e[n].tagName?e[n].tagName.toLowerCase():void 0,o=e[n].parentNode,a=o&&o.tagName?o.tagName.toLowerCase():void 0,l=this.inline.isConvertable(o,a,i,r);if(l){t(o).replaceWith(function(){return t("<strike>").append(t(this).contents())}).attr("data-redactor-inline-converted")}var l=this.inline.isConvertable(e[n],s,i,r);if(l){t(e[n]).replaceWith(function(){return t("<strike>").append(t(this).contents())})}}},insertBreakpoint:function(e,i){var r=document.createElement("span");r.id="redactor-inline-breakpoint",r=this.insert.node(r);var n=this.utils.isEndOfElement(e),s=this.utils.getOuterHtml(e),o=n?"":"<"+i+">";s=s.replace(/<span id="redactor-inline-breakpoint"><\/span>/i,"</"+i+">"+o);var a=t(s);return t(e).replaceWith(a),""!==o&&this.utils.cloneAttributes(e,a.last()),a.first()},insertInline:function(t){var e=document.createElement(t);return this.insert.node(e),this.caret.start(e),e},arrangeTag:function(t){var e=["b","bold","i","italic","underline","strikethrough","deleted","superscript","subscript"],i=["strong","strong","em","em","u","del","del","sup","sub"];t=t.toLowerCase();for(var r=0;r<e.length;r++)t===e[r]&&(t=i[r]);return t},getStyleParams:function(t){for(var e={},i=t.trim().replace(/;$/,"").split(";"),r=0;r<i.length;r++){var n=i[r].split(":");n&&(e[n[0].trim()]=n[1].trim())}return e},getParams:function(t,e,i){var r=!1,n="toggle";return"object"==typeof t?(r=t,n=void 0!==e?e:n):void 0!==t&&void 0!==e&&(r={},r[t]=e,n=void 0!==i?i:n),!!r&&{func:n,data:r}},setParams:function(e,i){if(i)for(var r in i.data){var n=t(e);"style"===r?(e=this.inline[i.func+"Style"](i.data[r],e),n.attr("data-redactor-style-cache",n.attr("style"))):e="class"===r?this.inline[i.func+"Class"](i.data[r],e):"remove"===i.func?this.inline[i.func+"Attr"](r,e):this.inline[i.func+"Attr"](r,i.data[r],e),"style"===r&&"SPAN"===e.tagName&&n.attr("data-redactor-span",!0)}return e},eachInline:function(t,e){var i,r=void 0===t?this.selection.inlines():[t];if(r)for(var n=0;n<r.length;n++)i=e(r[n])[0];return i},replaceClass:function(e,i){return this.inline.eachInline(i,function(i){return t(i).removeAttr("class").addClass(e)})},toggleClass:function(e,i){return this.inline.eachInline(i,function(i){return t(i).toggleClass(e)})},addClass:function(e,i){return this.inline.eachInline(i,function(i){return t(i).addClass(e)})},removeClass:function(e,i){return this.inline.eachInline(i,function(i){return t(i).removeClass(e)})},removeAllClass:function(e){return this.inline.eachInline(e,function(e){return t(e).removeAttr("class")})},replaceAttr:function(e,i,r){return this.inline.eachInline(r,function(i){return t(i).removeAttr(e).attr(e.value)})},toggleAttr:function(e,i,r){return this.inline.eachInline(r,function(i){return t(i).attr(e)?t(i).removeAttr(e):t(i).attr(e.value)})},addAttr:function(e,i,r){return this.inline.eachInline(r,function(r){return t(r).attr(e,i)})},removeAttr:function(e,i){return this.inline.eachInline(i,function(i){var r=t(i);return r.removeAttr(e),"style"===e&&r.removeAttr("data-redactor-style-cache"),r})},removeAllAttr:function(e){return this.inline.eachInline(e,function(e){for(var i=t(e),r=e.attributes.length,n=0;n<r;n++)i.removeAttr(e.attributes[n].name);return i})},removeSpecificAttr:function(e,i,r){var n=t(e);if("style"===i){var s=r.split(":"),o=s[0].trim();n.css(o,""),this.utils.removeEmptyAttr(e,"style")&&n.removeAttr("data-redactor-style-cache")}else n.removeAttr(i)[0]},hasParentStyle:function(t){var e=t.parent();return 1===e.length&&e[0].tagName===t[0].tagName&&e.html()===t[0].outerHTML&&e},addParentStyle:function(e){var i=this.inline.hasParentStyle(e);if(i){var r=this.inline.getStyleParams(e.attr("style"));i.css(r),i.attr("data-redactor-style-cache",i.attr("style")),e.replaceWith(function(){return t(this).contents()})}else e.attr("data-redactor-style-cache",e.attr("style"));return e},replaceStyle:function(e,i){e=this.inline.getStyleParams(e);var r=this;return this.inline.eachInline(i,function(i){var n=t(i);n.removeAttr("style").css(e);var s=n.attr("style");return s&&n.attr("style",s.replace(/"/g,"'")),n=r.inline.addParentStyle(n)})},toggleStyle:function(e,i){e=this.inline.getStyleParams(e);var r=this;return this.inline.eachInline(i,function(i){var n=t(i);for(var s in e){var o=e[s],a=n.css(s);a=r.utils.isRgb(a)?r.utils.rgb2hex(a):a.replace(/"/g,""),o=r.utils.isRgb(o)?r.utils.rgb2hex(o):o.replace(/"/g,""),a===o?n.css(s,""):n.css(s,o)}var l=n.attr("style");return l&&n.attr("style",l.replace(/"/g,"'")),r.utils.removeEmptyAttr(i,"style")?n.removeAttr("data-redactor-style-cache"):n=r.inline.addParentStyle(n),n})},addStyle:function(e,i){e=this.inline.getStyleParams(e);var r=this;return this.inline.eachInline(i,function(i){var n=t(i);n.css(e);var s=n.attr("style");return s&&n.attr("style",s.replace(/"/g,"'")),n=r.inline.addParentStyle(n)})},removeStyle:function(e,i){e=this.inline.getStyleParams(e);var r=this;return this.inline.eachInline(i,function(i){var n=t(i);for(var s in e)n.css(s,"");return r.utils.removeEmptyAttr(i,"style")?n.removeAttr("data-redactor-style-cache"):n.attr("data-redactor-style-cache",n.attr("style")),n})},removeAllStyle:function(e){return this.inline.eachInline(e,function(e){return t(e).removeAttr("style").removeAttr("data-redactor-style-cache")})},removeStyleRule:function(e){var i=this.selection.parent(),r=this.selection.inlines();this.buffer.set(),i&&"SPAN"===i.tagName&&this.inline.removeStyleRuleAttr(t(i),e);for(var n=0;n<r.length;n++){var s=r[n],o=t(s);-1==t.inArray(s.tagName.toLowerCase(),this.opts.inlineTags)||o.hasClass("redactor-selection-marker")||this.inline.removeStyleRuleAttr(o,e)}},removeStyleRuleAttr:function(t,e){t.css(e,""),this.utils.removeEmptyAttr(t,"style")?t.removeAttr("data-redactor-style-cache"):t.attr("data-redactor-style-cache",t.attr("style"))},update:function(t,e,i,r){t=this.inline.arrangeTag(t);var n=this.inline.getParams(e,i,r),s=this.selection.inlines(),o=[];if(s)for(var a=0;a<s.length;a++){var l=s[a];"*"!==t&&l.tagName.toLowerCase()!==t||o.push(this.inline.setParams(l,n))}return o},removeFormat:function(){this.selection.save();for(var e=this.inline.getClearedNodes(),i=0;i<e.length;i++)1===e[i].nodeType&&t(e[i]).replaceWith(function(){return t(this).contents()});this.selection.restore()}}},insert:function(){return{set:function(t){this.code.set(t),this.focus.end()},html:function(e,i){this.core.editor().focus();var r=this.selection.block(),n=this.selection.inline();void 0===i&&(i=this.clean.getCurrentType(e,!0),e=this.clean.onPaste(e,i,!0)),e=t.parseHTML(e);var s=t(e).last(),o=this.selection.get(),a=this.selection.range(o);if(a.deleteContents(),this.selection.update(o,a),i.lists){var l=t(e);if(0!==l.length&&("UL"===l[0].tagName||"OL"===l[0].tagName))return void this.insert.appendLists(r,l)}if(i.blocks&&r)if(this.utils.isSelectAll())this.core.editor().html(e),this.focus.end();else{var c=this.utils.breakBlockTag();if(!1===c)this.insert.placeHtml(e);else{var h=t(e).children().last();h.append(this.marker.get()),"start"===c.type?c.$block.before(e):c.$block.after(e),this.selection.restore(),this.core.editor().find("p").each(function(){""===t.trim(this.innerHTML)&&t(this).remove()})}}else{if(n){var d=t("<div/>").html(e);d.find(n.tagName.toLowerCase()).each(function(){t(this).contents().unwrap()}),e=d.html(),e=t.parseHTML(e),s=t(e).last()}if(this.utils.isSelectAll()){var u=t(this.opts.emptyHtml);this.core.editor().html("").append(u),u.html(e),this.caret.end(u)}else this.insert.placeHtml(e)}this.utils.disableSelectAll(),i.pre&&this.clean.cleanPre(),this.caret.end(s)},text:function(e){e=e.toString(),e=t.trim(e);var i=document.createElement("div");if(i.innerHTML=e,void 0!==(e=i.textContent||i.innerText)){this.core.editor().focus();var r=this.selection.blocks();if(e=e.replace(/\n/g," "),this.utils.isSelectAll()){var n=t(this.opts.emptyHtml);this.core.editor().html("").append(n),n.html(e),this.caret.end(n)}else{var s=this.selection.get(),o=document.createTextNode(e);if(s.getRangeAt&&s.rangeCount){var a=s.getRangeAt(0);a.deleteContents(),a.insertNode(o),a.setStartAfter(o),a.collapse(!0),this.selection.update(s,a)}r.length>1&&(t(o).wrap("<p>"),this.caret.after(o))}this.utils.disableSelectAll(),this.clean.normalizeCurrentHeading()}},raw:function(t){this.core.editor().focus();var e=this.selection.get(),i=this.selection.range(e);i.deleteContents();var r=document.createElement("div");r.innerHTML=t;for(var n,s,o=document.createDocumentFragment();n=r.firstChild;)s=o.appendChild(n);i.insertNode(o),s&&(i=i.cloneRange(),i.setStartAfter(s),i.collapse(!0),e.removeAllRanges(),e.addRange(i))},node:function(e,i){void 0!==this.start&&this.core.editor().focus(),e=e[0]||e;var r=this.selection.block(),n=this.utils.isBlockTag(e.tagName),s=!0;if(this.utils.isSelectAll())n?this.core.editor().html(e):this.core.editor().html(t("<p>").html(e)),this.code.sync();else if(n&&r){var o=this.utils.breakBlockTag();!1===o?this.insert.placeNode(e,i):("start"===o.type?o.$block.before(e):o.$block.after(e),this.core.editor().find("p:empty").remove())}else s=this.insert.placeNode(e,i);return this.utils.disableSelectAll(),s&&this.caret.end(e),e},appendLists:function(e,i){var r,n=t(e),s=this.utils.isEmpty(e.innerHTML);if(s||this.utils.isEndOfElement(e))r=n,i.find("li").each(function(){r.after(this),r=t(this)}),s&&n.remove();else if(this.utils.isStartOfElement(e))i.find("li").each(function(){n.before(this),r=t(this)});else{var o=this.selection.extractEndOfNode(e);n.after(t("<li>").append(o)),n.append(i),r=i}this.marker.remove(),r&&this.caret.end(r)},placeHtml:function(e){var i=document.createElement("span");i.id="redactor-insert-marker",i=this.insert.node(i),t(i).before(e),this.selection.restore(),this.caret.after(i),t(i).remove()},placeNode:function(t,e){var i=this.selection.get(),r=this.selection.range(i);if(null==r)return!1;!1!==e&&r.deleteContents(),r.insertNode(t),r.collapse(!1),this.selection.update(i,r)},nodeToPoint:function(e,i){if(i=i[0]||i,this.utils.isEmpty())return i=this.utils.isBlock(i)?i:t("<p />").append(i),this.core.editor().html(i),i;var r,n=e.clientX,s=e.clientY;if(document.caretPositionFromPoint){var o=document.caretPositionFromPoint(n,s);r=document.getSelection().getRangeAt(0),r.setStart(o.offsetNode,o.offset),r.collapse(!0),r.insertNode(i)}else if(document.caretRangeFromPoint)r=document.caretRangeFromPoint(n,s),r.insertNode(i);else if(void 0!==document.body.createTextRange){r=document.body.createTextRange(),r.moveToPoint(n,s);var a=r.duplicate();a.moveToPoint(n,s),r.setEndPoint("EndToEnd",a),r.select()}return i},nodeToCaretPositionFromPoint:function(t,e){this.insert.nodeToPoint(t,e)},marker:function(){this.marker.insert()}}},keydown:function(){return{init:function(e){if(!this.rtePaste){var i=e.which,n=i>=37&&i<=40;this.keydown.ctrl=e.ctrlKey||e.metaKey,this.keydown.parent=this.selection.parent(),this.keydown.current=this.selection.current(),this.keydown.block=this.selection.block(),this.keydown.pre=this.utils.isTag(this.keydown.current,"pre"),this.keydown.blockquote=this.utils.isTag(this.keydown.current,"blockquote"),this.keydown.figcaption=this.utils.isTag(this.keydown.current,"figcaption"),this.keydown.figure=this.utils.isTag(this.keydown.current,"figure");if(!1===this.core.callback("keydown",e))return e.preventDefault(),!1;if(this.shortcuts.init(e,i),this.keydown.checkEvents(n,i),this.keydown.setupBuffer(e,i),this.utils.isSelectAll()&&(i===this.keyCode.ENTER||i===this.keyCode.BACKSPACE||i===this.keyCode.DELETE))return e.preventDefault(),this.code.set(this.opts.emptyHtml),void this.events.changeHandler();if(this.keydown.addArrowsEvent(n),this.keydown.setupSelectAll(e,i),!this.opts.enterKey&&i===this.keyCode.ENTER){e.preventDefault();var s=this.selection.get(),o=this.selection.range(s);return void(o.collapsed||o.deleteContents())}if(this.opts.enterKey&&i===this.keyCode.DOWN&&this.keydown.onArrowDown(),this.opts.enterKey&&i===this.keyCode.UP&&this.keydown.onArrowUp(),("textarea"===this.opts.type||"div"===this.opts.type)&&this.keydown.current&&3===this.keydown.current.nodeType&&t(this.keydown.parent).hasClass("redactor-in")&&this.keydown.wrapToParagraph(),!this.keyup.lastShiftKey&&i===this.keyCode.SPACE&&(e.ctrlKey||e.shiftKey))return e.preventDefault(),this.keydown.onShiftSpace();if(i===this.keyCode.ENTER&&(e.ctrlKey||e.shiftKey)&&(null===r||"ios"!==r.platform()))return e.preventDefault(),this.keydown.onShiftEnter(e);if(i===this.keyCode.ENTER&&!e.shiftKey&&!e.ctrlKey&&!e.metaKey)return this.keydown.onEnter(e);if(i===this.keyCode.TAB||e.metaKey&&221===i||e.metaKey&&219===i)return this.keydown.onTab(e,i);if(this.detect.isFirefox()&&i===this.keyCode.BACKSPACE&&this.keydown.block&&"P"===this.keydown.block.tagName&&this.utils.isStartOfElement(this.keydown.block)){var a=t(this.keydown.block).prev();if(0!==a.length)return e.preventDefault(),a.append(this.marker.get()),a.append(t(this.keydown.block).html()),t(this.keydown.block).remove(),void this.selection.restore()}if(i===this.keyCode.BACKSPACE||i===this.keyCode.DELETE){if(this.observe.image&&void 0!==this.observe.image&&0!==t("#redactor-image-box").length){e.preventDefault();var a=this.observe.image.closest("figure, p").prev();return this.image.remove(!1),this.observe.image=!1,void(a&&0!==a.length?this.caret.end(a):this.core.editor().focus())}this.keydown.onBackspaceAndDeleteBefore()}if(i===this.keyCode.DELETE){var l=t(this.keydown.block).next();if(this.utils.isEndOfElement(this.keydown.block)&&0!==l.length&&"FIGURE"===l[0].tagName)return l.remove(),!1;if(!(!this.keydown.block||"LI"!==this.keydown.block.tagName)&&this.keydown.block){var c=t(this.keydown.block).parents("ul, ol").last(),h=c.next();if(this.utils.isRedactorParent(c)&&this.utils.isEndOfElement(c)&&0!==h.length&&("UL"===h[0].tagName||"OL"===h[0].tagName))return e.preventDefault(),c.append(h.contents()),h.remove(),!1}if(this.utils.isEndOfElement(this.keydown.block)&&0!==l.length&&"PRE"===l[0].tagName)return t(this.keydown.block).append(l.text()),l.remove(),!1}if(i===this.keyCode.DELETE&&0!==t("#redactor-image-box").length&&this.image.remove(),i===this.keyCode.BACKSPACE){if(this.detect.isFirefox()&&this.line.removeOnBackspace(e),this.list.combineAfterAndBefore(this.keydown.block))return void e.preventDefault();var d=this.selection.block();if(d&&"LI"===d.tagName&&this.utils.isCollapsed()&&this.utils.isStartOfElement())return this.indent.decrease(),void e.preventDefault();this.keydown.removeInvisibleSpace(),this.keydown.removeEmptyListInTable(e)}i!==this.keyCode.BACKSPACE&&i!==this.keyCode.DELETE||this.keydown.onBackspaceAndDeleteAfter(e)}},onShiftSpace:function(){return this.buffer.set(),this.insert.raw("&nbsp;"),!1},onShiftEnter:function(t){return this.buffer.set(),this.keydown.pre?this.keydown.insertNewLine(t):this.insert.raw("<br>")},onBackspaceAndDeleteBefore:function(){this.utils.saveScroll()},onBackspaceAndDeleteAfter:function(e){setTimeout(t.proxy(function(){this.code.syncFire=!1,this.keydown.removeEmptyLists();var t="";0!==this.opts.keepStyleAttr.length&&(t=","+this.opts.keepStyleAttr.join(",")),this.core.editor().find("*[style]").not("img, figure, iframe, #redactor-image-box, #redactor-image-editter, [data-redactor-style-cache], [data-redactor-span]"+t).removeAttr("style"),this.keydown.formatEmpty(e),this.code.syncFire=!0},this),1)},onEnter:function(e){if(!1===this.core.callback("enter",e))return e.preventDefault(),!1;if(this.keydown.blockquote&&!0===this.keydown.exitFromBlockquote(e))return!1;if(this.keydown.pre)return this.keydown.insertNewLine(e);if(this.keydown.blockquote||this.keydown.figcaption)return this.keydown.insertBreakLine(e);if(this.keydown.figure)setTimeout(t.proxy(function(){this.keydown.replaceToParagraph("FIGURE")},this),1);else if(this.keydown.block){if(setTimeout(t.proxy(function(){this.keydown.replaceToParagraph("DIV")},this),1),"LI"===this.keydown.block.tagName){var i=this.selection.current(),r=t(i).closest("li",this.$editor[0]),n=r.parents("ul,ol",this.$editor[0]).last();if(0!==r.length&&this.utils.isEmpty(r.html())&&0===n.next().length&&this.utils.isEmpty(n.find("li").last().html())){n.find("li").last().remove();var s=t(this.opts.emptyHtml);return n.after(s),this.caret.start(s),!1}}}else if(!this.keydown.block)return this.keydown.insertParagraph(e);if(this.detect.isFirefox()&&this.utils.isInline(this.keydown.parent))return void this.keydown.insertBreakLine(e);this.opts.keepInlineOnEnter||setTimeout(t.proxy(function(){var e=this.selection.inline();if(e&&this.utils.isEmpty(e.innerHTML)){var i=this.selection.block();t(e).remove();var r=document.createRange();r.setStart(i,0);var n=document.createTextNode("​");r.insertNode(n),r.setStartAfter(n),r.collapse(!0);var s=window.getSelection();s.removeAllRanges(),s.addRange(r)}},this),1)},checkEvents:function(t,e){t||"click"!==this.core.getEvent()&&"arrow"!==this.core.getEvent()||(this.core.addEvent(!1),this.keydown.checkKeyEvents(e)&&this.buffer.set())},checkKeyEvents:function(e){var i=this.keyCode,r=[i.BACKSPACE,i.DELETE,i.ENTER,i.ESC,i.TAB,i.CTRL,i.META,i.ALT,i.SHIFT];return-1===t.inArray(e,r)},addArrowsEvent:function(t){if(t)return"click"===this.core.getEvent()||"arrow"===this.core.getEvent()?void this.core.addEvent(!1):void this.core.addEvent("arrow")},setupBuffer:function(t,e){return this.keydown.ctrl&&90===e&&!t.shiftKey&&!t.altKey&&this.sBuffer.length?(t.preventDefault(),void this.buffer.undo()):this.keydown.ctrl&&90===e&&t.shiftKey&&!t.altKey&&0!==this.sRebuffer.length?(t.preventDefault(),void this.buffer.redo()):void(this.keydown.ctrl||e!==this.keyCode.SPACE&&e!==this.keyCode.BACKSPACE&&e!==this.keyCode.DELETE&&(e!==this.keyCode.ENTER||t.ctrlKey||t.shiftKey)||this.buffer.set())},exitFromBlockquote:function(e){if(this.utils.isEndOfElement(this.keydown.blockquote)){if(-1!==this.clean.removeSpacesHard(t(this.keydown.blockquote).html()).search(/(<br\s?\/?>){1}$/i)){e.preventDefault();t(this.keydown.blockquote).children().last().filter("br").remove(),t(this.keydown.blockquote).children().last().filter("span").remove();var i=t(this.opts.emptyHtml);return t(this.keydown.blockquote).after(i),this.caret.start(i),!0}}},onArrowDown:function(){for(var t=[this.keydown.blockquote,this.keydown.pre,this.keydown.figcaption],e=0;e<t.length;e++)if(t[e])return this.keydown.insertAfterLastElement(t[e]),!1},onArrowUp:function(){for(var t=[this.keydown.blockquote,this.keydown.pre,this.keydown.figcaption],e=0;e<t.length;e++)if(t[e])return this.keydown.insertBeforeFirstElement(t[e]),!1},insertAfterLastElement:function(e){if(this.utils.isEndOfElement(e)){var i=this.core.editor().contents().last();if(0===("FIGCAPTION"===e.tagName?t(this.keydown.block).parent().next():t(this.keydown.block).next()).length){if(0===i.length&&i[0]!==e)return void this.caret.start(i);var r=t(this.opts.emptyHtml);"FIGCAPTION"===e.tagName?t(e).parent().after(r):t(e).after(r),this.caret.start(r)}}},insertBeforeFirstElement:function(e){if(this.utils.isStartOfElement()&&!(this.core.editor().contents().length>1&&this.core.editor().contents().first()[0]!==e)){var i=t(this.opts.emptyHtml);t(e).before(i),this.caret.start(i)}},onTab:function(t,e){if(!this.opts.tabKey)return!0;var i=this.keydown.block&&"LI"===this.keydown.block.tagName;if(this.utils.isEmpty(this.code.get())||!i&&!this.keydown.pre&&!1===this.opts.tabAsSpaces)return!0;t.preventDefault(),this.buffer.set();var r,n=i&&this.utils.isStartOfElement(this.keydown.block);return this.keydown.pre&&!t.shiftKey?(r=this.opts.preSpaces?document.createTextNode(Array(this.opts.preSpaces+1).join(" ")):document.createTextNode("\t"),this.insert.node(r)):!1===this.opts.tabAsSpaces||n?t.metaKey&&219===e?this.indent.decrease():t.metaKey&&221===e?this.indent.increase():t.shiftKey?this.indent.decrease():this.indent.increase():(r=document.createTextNode(Array(this.opts.tabAsSpaces+1).join(" ")),this.insert.node(r)),!1},setupSelectAll:function(t,e){this.keydown.ctrl&&65===e?this.utils.enableSelectAll():e===this.keyCode.LEFT_WIN||this.keydown.ctrl||this.utils.disableSelectAll()},insertNewLine:function(t){t.preventDefault();var e=document.createTextNode("\n"),i=this.selection.get(),r=this.selection.range(i);return r.deleteContents(),r.insertNode(e),this.caret.after(e),!1},insertParagraph:function(t){t.preventDefault();var e=document.createElement("p");e.innerHTML="<br>";var i=this.selection.get(),r=this.selection.range(i);return r.deleteContents(),r.insertNode(e),this.caret.start(e),!1},insertBreakLine:function(t){return this.keydown.insertBreakLineProcessing(t)},insertDblBreakLine:function(t){return this.keydown.insertBreakLineProcessing(t,!0)},insertBreakLineProcessing:function(t,e){t.stopPropagation();var i=document.createElement("br");if(this.insert.node(i),!0===e){var r=document.createElement("br");this.insert.node(r),this.caret.after(r)}else this.caret.after(i);return!1},wrapToParagraph:function(){var e=t(this.keydown.current),i=t("<p>").append(e.clone());e.replaceWith(i);var r=t(i).next();void 0!==r[0]&&"BR"===r[0].tagName&&r.remove(),this.caret.end(i)},replaceToParagraph:function(e){var i=this.selection.block(),r=t(i).prev(),n=i.innerHTML.replace(/<br\s?\/?>/gi,"");if(i.tagName===e&&this.utils.isEmpty(n)&&!t(i).hasClass("redactor-in")){var s=document.createElement("p");return t(i).replaceWith(s),this.keydown.setCaretToParagraph(s),!1}if("P"===i.tagName)return t(i).removeAttr("class").removeAttr("style"),this.detect.isIe()&&this.utils.isEmpty(n)&&this.utils.isInline(this.keydown.parent)&&t(i).on("input",t.proxy(function(){var e=this.selection.parent();if(this.utils.isInline(e)){var r=t(e).html();t(i).html(r),this.caret.end(i)}t(i).off("keyup")},this)),!1;if(r.hasClass(this.opts.videoContainerClass)){r.removeAttr("class");var s=document.createElement("p");return r.replaceWith(s),this.keydown.setCaretToParagraph(s),!1}},setCaretToParagraph:function(t){var e=document.createRange();e.setStart(t,0);var i=document.createTextNode("​");e.insertNode(i),e.setStartAfter(i),e.collapse(!0);var r=window.getSelection();r.removeAllRanges(),r.addRange(e)},removeInvisibleSpace:function(){var e=t(this.keydown.current);0===e.text().search(/^\u200B$/g)&&e.remove()},removeEmptyListInTable:function(e){var i=t(this.keydown.current),r=t(this.keydown.parent),n=i.closest("td",this.$editor[0]);if(0!==n.length&&i.closest("li",this.$editor[0])&&1===r.children("li").length){if(!this.utils.isEmpty(i.text()))return;e.preventDefault(),i.remove(),r.remove(),this.caret.start(n)}},removeEmptyLists:function(){var e=function(){""===t.trim(this.innerHTML).replace(/\/t\/n/g,"")&&t(this).remove()};this.core.editor().find("li").each(e),this.core.editor().find("ul, ol").each(e)},formatEmpty:function(e){var i=t.trim(this.core.editor().html());if(this.utils.isEmpty(i)){if(e.preventDefault(),"inline"===this.opts.type||"pre"===this.opts.type)this.core.editor().html(this.marker.html()),this.selection.restore();else{var n=function(){this.core.editor().html(this.opts.emptyHtml),this.focus.start()}.bind(this);null!==r&&"ios"===r.platform()?setTimeout(n,50):n()}return!1}}}},keyup:function(){return{init:function(e){if(!this.rtePaste){var i=e.which;this.keyup.block=this.selection.block(),this.keyup.current=this.selection.current(),this.keyup.parent=this.selection.parent(),this.keyup.lastShiftKey=e.shiftKey;if(!1===this.core.callback("keyup",e))return e.preventDefault(),!1;if(i===this.keyCode.ENTER&&this.keyup.block&&"FIGURE"===this.keyup.block.tagName){var r=t(this.keyup.block).prev();if(0!==r.length&&"FIGURE"===r[0].tagName){var n=this.utils.replaceToTag(r,"p");return void this.caret.start(n)}}if(i===this.keyCode.BACKSPACE||i===this.keyCode.DELETE){if(this.utils.isSelectAll())return void this.focus.start()
-;if(this.keyup.block&&this.keydown.block&&"FIGURE"===this.keyup.block.tagName&&this.utils.isStartOfElement(this.keydown.block)){e.preventDefault(),this.selection.save(),t(this.keyup.block).find("figcaption").remove(),t(this.keyup.block).find("img").first().remove(),this.utils.replaceToTag(this.keyup.block,"p");var s=this.marker.find();return t("html, body").animate({scrollTop:s.position().top+20},500),void this.selection.restore()}if(this.keyup.block&&"P"===this.keyup.block.tagName){var o=t(this.keyup.block).find("img").length;""===t(this.keyup.block).text().replace(/\u200B/g,"")&&0!==o&&this.utils.replaceToTag(this.keyup.block,"figure")}this.keyup.block&&"FIGURE"===this.keyup.block.tagName&&0===t(this.keyup.block).find("img").length&&(this.selection.save(),this.utils.replaceToTag(this.keyup.block,"p"),this.selection.restore())}}}}},lang:function(){return{load:function(){this.opts.curLang=this.opts.langs[this.opts.lang]},get:function(t){return void 0!==this.opts.curLang[t]?this.opts.curLang[t]:""}}},line:function(){return{insert:function(){this.buffer.set(),this.insert.html(this.line.getLineHtml());var t=this.core.editor().find("#redactor-hr-tmp-id");return t.removeAttr("id"),this.core.callback("insertedLine",t),t},getLineHtml:function(){var t='<hr id="redactor-hr-tmp-id" />';return!this.detect.isFirefox()&&this.utils.isEmpty()&&(t+="<p>"+this.opts.emptyHtml+"</p>"),t},removeOnBackspace:function(e){if(this.utils.isCollapsed()){var i=t(this.selection.block());if(0!==i.length&&this.utils.isStartOfElement(i)){var r=i.prev();r&&0!==r.length&&"HR"===r[0].tagName&&(e.preventDefault(),r.remove())}}}}},link:function(){return{get:function(){return t(this.selection.inlines("a"))},is:function(){var e=this.selection.nodes(),i=t(this.selection.current()).closest("a",this.core.editor()[0]);return!(0===i.length||e.length>1)&&i},unlink:function(t){void 0!==t&&t.preventDefault&&t.preventDefault(),this.buffer.set();var e=this.selection.inlines("a");if(0!==e.length){var i=this.link.replaceLinksToText(e);this.observe.closeAllTooltip(),this.core.callback("deletedLink",i)}},insert:function(e,i){var r=this.link.is();if(!0!==i&&!1===(e=this.link.buildLinkFromObject(r,e)))return!1;if(this.buffer.set(),e=this.core.callback("beforeInsertingLink",e),!1===r){r=t("<a />"),r=this.link.update(r,e),r=t(this.insert.node(r));var n=r.parent();!1===this.utils.isRedactorParent(n)&&r.wrap("<p>"),n.hasClass("redactor-unlink")&&n.replaceWith(function(){return t(this).contents()}),this.caret.after(r),this.core.callback("insertedLink",r)}else r=this.link.update(r,e),this.caret.after(r);return r},update:function(t,e){return t.text(e.text),t.attr("href",e.url),this.link.target(t,e.target),t},target:function(t,e){return e?t.attr("target","_blank"):t.removeAttr("target")},show:function(e){void 0!==e&&e.preventDefault&&e.preventDefault(),this.observe.closeAllTooltip();var i=this.link.is();this.link.buildModal(i);var r=this.link.buildLinkFromElement(i);r.url=this.link.removeSelfHostFromUrl(r.url),this.opts.linkNewTab&&!i&&(r.target=!0),this.link.setModalValues(r),this.modal.show(),this.detect.isDesktop()&&t("#redactor-link-url").focus()},setModalValues:function(e){t("#redactor-link-blank").prop("checked",e.target),t("#redactor-link-url").val(e.url),t("#redactor-link-url-text").val(e.text)},buildModal:function(e){this.modal.load("link",this.lang.get(!1===e?"link-insert":"link-edit"),600),this.modal.getActionButton().text(this.lang.get(!1===e?"insert":"save")).on("click",t.proxy(this.link.callback,this))},callback:function(){var t=this.link.buildLinkFromModal();if(!1===t)return!1;this.modal.close(),this.link.insert(t,!0)},cleanUrl:function(e){return void 0===e?"":t.trim(e.replace(/[^\W\w\D\d+&\'@#\/%?=~_|!:,.;\(\)]/gi,""))},cleanText:function(e){return void 0===e?"":t.trim(e.replace(/(<([^>]+)>)/gi,""))},getText:function(t){return""===t.text&&""!==t.url?this.link.truncateUrl(t.url.replace(/<|>/g,"")):t.text},isUrl:function(t){return!!new RegExp("^((https?|ftp):\\/\\/)?(([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$","i").test(t)&&t},isMailto:function(t){return-1!==t.search("@")&&!1===/(http|ftp|https):\/\//i.test(t)},isEmpty:function(t){return""===t.url||""===t.text&&""===t.url},truncateUrl:function(t){return t.length>this.opts.linkSize?t.substring(0,this.opts.linkSize)+"...":t},parse:function(t){return this.link.isMailto(t.url)?t.url="mailto:"+t.url.replace("mailto:",""):0!==t.url.search("#")&&this.opts.linkValidation&&(t.url=this.link.isUrl(t.url)?"http://"+t.url.replace(/(ftp|https?):\/\//gi,""):t.url),!this.link.isEmpty(t)&&!1!==t.url&&t},buildLinkFromModal:function(){var e={};return e.url=this.link.cleanUrl(t("#redactor-link-url").val()),e.text=this.link.cleanText(t("#redactor-link-url-text").val()),e.text=this.link.getText(e),e.target=!!t("#redactor-link-blank").prop("checked"),this.link.parse(e)},buildLinkFromObject:function(t,e){return e.url=this.link.cleanUrl(e.url),e.text=void 0===e.text&&this.selection.is()?this.selection.text():this.link.cleanText(e.text),e.text=this.link.getText(e),e.target=!1===t?e.target:this.link.buildTarget(t),this.link.parse(e)},buildLinkFromElement:function(t){var e={url:"",text:this.selection.is()?this.selection.text():"",target:!1};return!1!==t&&(e.url=t.attr("href"),e.text=t.text(),e.target=this.link.buildTarget(t)),e},buildTarget:function(t){return void 0!==t.attr("target")&&"_blank"===t.attr("target")},removeSelfHostFromUrl:function(t){var e=self.location.href.replace("#","").replace(/\/$/i,"");return t.replace(/^\/\#/,"#").replace(e,"").replace("mailto:","")},replaceLinksToText:function(e){var i,r=t.each(e,function(e,r){var n=t(r),s=t('<span class="redactor-unlink" />').append(n.contents());return n.replaceWith(s),0===e&&(i=s),n});return 1===e.length&&this.selection.isCollapsed()&&this.caret.after(i),r}}},linkify:function(){return{isKey:function(){},isLink:function(){},isFiltered:function(){},handler:function(){},format:function(){},convertVideoLinks:function(){},convertImages:function(){},convertLinks:function(){}}},list:function(){return{toggle:function(e){if(!this.utils.inBlocks(["table","td","th","tr"])){e="orderedlist"===e?"ol":e,e="unorderedlist"===e?"ul":e,e=e.toLowerCase(),this.buffer.set(),this.selection.save();var i=this.list._getBlocks(),r=this.selection.block(),n=t(r).parents("ul, ol").last();return 0===i.length&&0!==n.length&&(i=[n.get(0)]),i=this.list._isUnformat(e,i)?this.list._unformat(e,i):this.list._format(e,i),this.selection.restore(),i}},get:function(){var e=this.selection.current(),i=t(e).closest("ul, ol",this.core.editor()[0]);return 0!==i.length&&i},combineAfterAndBefore:function(e){var i=t(e).prev(),r=t(e).next(),n=e&&"P"===e.tagName&&("<br>"===e.innerHTML||""===e.innerHTML),s=1===i.closest("ol, ul",this.core.editor()[0]).length&&1===r.closest("ol, ul",this.core.editor()[0]).length;return!(!n||!s)&&(i.children("li").last().append(this.marker.get()),i.append(r.contents()),this.selection.restore(),!0)},_getBlocks:function(){for(var e=[],i=this.selection.blocks(),r=0;r<i.length;r++){t(i[r]).parent().hasClass("redactor-in")&&e.push(i[r])}return e},_isUnformat:function(t,e){for(var i=0,r=0;r<e.length;r++)if(3!==e[r].nodeType){var n=e[r].tagName.toLowerCase();n!==t&&"figure"!==n||i++}return i===e.length},_uniteBlocks:function(e,i){for(var r=0,n={0:[]},s=!1,o=0;o<e.length;o++){var a=t(e[o]),l=a.closest("th, td");0!==l.length?(l.get(0)!==s&&(r++,n[r]=[]),this.list._isUniteBlock(e[o],i)&&n[r].push(e[o])):this.list._isUniteBlock(e[o],i)?n[r].push(e[o]):(r++,n[r]=[]),s=l.get()}return n},_isUniteBlock:function(t,e){return 3===t.nodeType||-1!==e.indexOf(t.tagName.toLowerCase())},_createList:function(e,i,r){var n=i[i.length-1],s=t(n),o=t("<"+e+">");return s.after(o),o},_createListItem:function(e){var i=t("<li>");if(3===e.nodeType)i.append(e);else{var r=t(e);i.append(r.contents()),r.remove()}return i},_format:function(e,i){var r=["p","div","blockquote","pre","h1","h2","h3","h4","h5","h6","ul","ol"],n=this.list._uniteBlocks(i,r),s=[];for(var o in n){for(var a=n[o],l=this.list._createList(e,n[o]),c=0;c<a.length;c++){var h;3===a[c].nodeType||"UL"!==a[c].tagName&&"OL"!==a[c].tagName?(h=this.list._createListItem(a[c]),l.append(h)):(h=t(a[c]).contents(),l.append(h))}s.push(l.get(0))}return s},_unformat:function(e,i){if(1===i.length){var r=t(i[0]),n=r.find("li"),s=this.selection.blocks(["li"]),o=this.selection.block(),a=t(o).closest("li");if(0===s.length&&0!==a.length&&(s=[a.get(0)]),s.length===n.length)return this.list._unformatEntire(i[0]);var l=this.list._getItemsPosition(n,s);if("Top"===l)return this.list._unformatAtSide("before",s,r);if("Bottom"===l)return s.reverse(),this.list._unformatAtSide("after",s,r);if("Middle"===l){var c=t(s[s.length-1]),h=!1,d=!1,u=t("<"+r.get(0).tagName.toLowerCase()+">");n.each(function(e,i){if(h){var r=t(i);r.children("ul, ol").length;0!==r.closest(".redactor-split-item").length||!1!==d&&0!==r.closest(d).length||r.addClass("redactor-split-item"),d=r}i===c.get(0)&&(h=!0)}),n.filter(".redactor-split-item").each(function(e,i){t(i).removeClass("redactor-split-item"),u.append(i)}),r.after(u),s.reverse();for(var p=0;p<s.length;p++){var f=t(s[p]),g=this.list._createUnformatContainer(f);r.after(g),g.find("ul, ol").remove(),f.remove()}return}}else for(var p=0;p<i.length;p++)3!==i[p].nodeType&&i[p].tagName.toLowerCase()===e&&this.list._unformatEntire(i[p])},_unformatEntire:function(e){var i=t(e);i.find("li").each(function(e,r){var n=t(r),s=this.list._createUnformatContainer(n);n.remove(),i.before(s)}.bind(this)),i.remove()},_unformatAtSide:function(e,i,r){for(var n=0;n<i.length;n++){var s=t(i[n]),o=this.list._createUnformatContainer(s);r[e](o);var a=o.find("ul, ol").first();s.append(a),a.each(function(e,r){var n=t(r),s=n.closest("li");s.get(0)===i[e]&&(n.unwrap(),s.addClass("r-unwrapped"))}),this.utils.isEmpty(s.html())&&s.remove()}r.find(".r-unwrapped").each(function(e){var i=t(e);""===i.html().trim()?i.remove():i.removeClass("r-unwrapped")})},_getItemsPosition:function(t,e){var i="Middle",r=e[0],n=e[e.length-1],s=t.first().get(0),o=t.last().get(0);return s===r&&o!==n?i="Top":s!==r&&o===n&&(i="Bottom"),i},_createUnformatContainer:function(e){var i=t("<p>");return i.append(e.contents()),i}}},marker:function(){return{get:function(t){t=void 0===t?1:t;var e=document.createElement("span");return e.id="selection-marker-"+t,e.className="redactor-selection-marker",e.innerHTML=this.opts.invisibleSpace,e},html:function(t){return this.utils.getOuterHtml(this.marker.get(t))},find:function(t){return t=void 0===t?1:t,this.core.editor().find("span#selection-marker-"+t)},insert:function(){var t=this.selection.get(),e=this.selection.range(t);this.marker.insertNode(e,this.marker.get(1),!0),e&&!1===e.collapsed&&this.marker.insertNode(e,this.marker.get(2),!1)},remove:function(){this.core.editor().find(".redactor-selection-marker").each(this.marker.iterateRemove)},insertNode:function(e,i,r){var n=this.selection.parent();if(null!==e&&0!==t(n).closest(".redactor-in").length){e=e.cloneRange();try{e.collapse(r),e.insertNode(i)}catch(t){this.focus.start()}}},iterateRemove:function(e,i){var r=t(i),n=r.text().replace(/\u200B/g,"");r.parent()[0];""===n?r.remove():r.replaceWith(function(){return t(this).contents()})}}},modal:function(){return{callbacks:{},templates:function(){this.opts.modal={"image-edit":"",image:"",file:"",link:String()+'<div class="redactor-modal-tab" data-title="General"><section><label>URL</label><input type="url" id="redactor-link-url" aria-label="URL" /></section><section><label>'+this.lang.get("text")+'</label><input type="text" id="redactor-link-url-text" aria-label="'+this.lang.get("text")+'" /></section><section><label class="checkbox"><input type="checkbox" id="redactor-link-blank"> '+this.lang.get("link-in-new-tab")+'</label></section><section><button id="redactor-modal-button-action">'+this.lang.get("insert")+'</button><button id="redactor-modal-button-cancel">'+this.lang.get("cancel")+"</button></section></div>"},t.extend(this.opts,this.opts.modal)},addCallback:function(t,e){this.modal.callbacks[t]=e},addTemplate:function(t,e){this.opts.modal[t]=e},getTemplate:function(t){return this.opts.modal[t]},getModal:function(){return this.$modalBody},getActionButton:function(){return this.$modalBody.find("#redactor-modal-button-action")},getCancelButton:function(){return this.$modalBody.find("#redactor-modal-button-cancel")},getDeleteButton:function(){return this.$modalBody.find("#redactor-modal-button-delete")},load:function(){},show:function(){},buildWidth:function(){},buildTabber:function(){},showTab:function(){},setTitle:function(){},setContent:function(){this.$modalBody.html(this.modal.getTemplate(this.modal.templateName)),this.modal.getCancelButton().on("mousedown",t.proxy(this.modal.close,this))},setDraggable:function(){},setEnter:function(){},build:function(){this.modal.buildOverlay(),this.$modalBox=t('<div id="redactor-modal-box"/>').hide(),this.$modal=t('<div id="redactor-modal" role="dialog" />'),this.$modalHeader=t('<div id="redactor-modal-header" />'),this.$modalClose=t('<button type="button" id="redactor-modal-close" aria-label="'+this.lang.get("close")+'" />').html("&times;"),this.$modalBody=t('<div id="redactor-modal-body" />'),this.$modal.append(this.$modalHeader),this.$modal.append(this.$modalBody),this.$modal.append(this.$modalClose),this.$modalBox.append(this.$modal),this.$modalBox.appendTo(document.body)},buildOverlay:function(){this.$modalOverlay=t('<div id="redactor-modal-overlay">').hide(),t("body").prepend(this.$modalOverlay)},enableEvents:function(){},disableEvents:function(){},closeHandler:function(){},close:function(){}}},observe:function(){return{load:function(){void 0===this.opts.destroyed&&(this.observe.links(),this.observe.images())},isCurrent:function(e,i){return void 0===i&&(i=t(this.selection.current())),i.is(e)||i.parents(e).length>0},toolbar:function(){this.observe.buttons(),this.observe.dropdowns()},buttons:function(e,i){var r=this.selection.current(),n=this.selection.parent();if(!1!==e?this.button.setInactiveAll():this.button.setInactiveAll(i),!1===e&&"html"!==i)return void(-1!==t.inArray(i,this.opts.activeButtons)&&this.button.toggleActive(i));this.utils.isRedactorParent(r)&&("none"!==this.core.editor().css("display")&&(this.utils.isCurrentOrParentHeader()||this.utils.isCurrentOrParent(["table","pre","blockquote","li"])?this.button.disable("horizontalrule"):this.button.enable("horizontalrule")),t.each(this.opts.activeButtonsStates,t.proxy(function(e,i){var s=t(n).closest(e,this.$editor[0]),o=t(r).closest(e,this.$editor[0]);(0===s.length||this.utils.isRedactorParent(s))&&this.utils.isRedactorParent(o)&&(0===s.length&&0===o.closest(e,this.$editor[0]).length||this.button.setActive(i))},this)))},dropdowns:function(){var e=t("<div />").html(this.selection.html()).find("a").length,i=t(this.selection.current()),r=this.utils.isRedactorParent(i);t.each(this.opts.observe.dropdowns,t.proxy(function(t,n){var s=n.observe,o=s.element,a=n.item,l=void 0!==s.in&&s.in,c=void 0!==s.out&&s.out;i.closest(o).length>0&&r||"a"===o&&0!==e?this.observe.setDropdownProperties(a,l,c):this.observe.setDropdownProperties(a,c,l)},this))},setDropdownProperties:function(t,e,i){i&&void 0!==i.attr&&this.observe.setDropdownAttr(t,i.attr,!0),void 0!==e.attr&&this.observe.setDropdownAttr(t,e.attr),void 0!==e.title&&t.find("span").text(e.title)},setDropdownAttr:function(e,i,r){t.each(i,function(t,i){"class"===t?r?e.removeClass(i):e.addClass(i):r?e.removeAttr(t):e.attr(t,i)})},addDropdown:function(t,e,i){void 0!==i.observe&&(i.item=t,this.opts.observe.dropdowns.push(i))},images:function(){this.opts.imageEditable&&(this.core.editor().addClass("redactor-layer-img-edit"),this.core.editor().find("img").each(t.proxy(function(e,i){var r=t(i);r.closest("a",this.$editor[0]).on("click",function(t){t.preventDefault()}),this.image.setEditable(r)},this)))},links:function(){this.opts.linkTooltip&&this.core.editor().find("a").each(t.proxy(function(e,i){var r=t(i);!0!==r.data("cached")&&(r.data("cached",!0),r.on("touchstart.redactor."+this.uuid+" click.redactor."+this.uuid,t.proxy(this.observe.showTooltip,this)))},this))},getTooltipPosition:function(t){return t.offset()},showTooltip:function(e){var i=t(e.target);if("IMG"!==i[0].tagName&&("A"!==i[0].tagName&&(i=i.closest("a",this.$editor[0])),"A"===i[0].tagName)){var r=i,n=this.observe.getTooltipPosition(r),s=t('<span class="redactor-link-tooltip"></span>'),o=r.attr("href");void 0===o&&(o=""),o.length>24&&(o=o.substring(0,24)+"...");var a=t('<a href="'+r.attr("href")+'" target="_blank" />').html(o).addClass("redactor-link-tooltip-action"),l=t('<a href="#" />').html(this.lang.get("edit")).on("click",t.proxy(this.link.show,this)).addClass("redactor-link-tooltip-action"),c=t('<a href="#" />').html(this.lang.get("unlink")).on("click",t.proxy(this.link.unlink,this)).addClass("redactor-link-tooltip-action");s.append(a).append(" | ").append(l).append(" | ").append(c);var h=parseInt(r.css("line-height"),10),d=Math.ceil((e.pageY-n.top)/h),u=n.top+d*h;s.css({top:u+"px",left:n.left+"px"}),t(".redactor-link-tooltip").remove(),t("body").append(s),this.core.editor().on("touchstart.redactor."+this.uuid+" click.redactor."+this.uuid,t.proxy(this.observe.closeTooltip,this)),t(document).on("touchstart.redactor."+this.uuid+" click.redactor."+this.uuid,t.proxy(this.observe.closeTooltip,this))}},closeAllTooltip:function(){t(".redactor-link-tooltip").remove()},closeTooltip:function(e){e=e.originalEvent||e;var i=e.target,r=t(i).closest("a",this.$editor[0]);0!==r.length&&"A"===r[0].tagName&&"A"!==i.tagName||"A"===i.tagName&&this.utils.isRedactorParent(i)||t(i).hasClass("redactor-link-tooltip-action")||(this.observe.closeAllTooltip(),this.core.editor().off("touchstart.redactor."+this.uuid+" click.redactor."+this.uuid,t.proxy(this.observe.closeTooltip,this)),t(document).off("touchstart.redactor."+this.uuid+" click.redactor."+this.uuid,t.proxy(this.observe.closeTooltip,this)))}}},offset:function(){return{get:function(e){var i=this.offset.clone(e);if(!1===i)return 0;var r=document.createElement("div");return r.appendChild(i.cloneContents()),r.innerHTML=r.innerHTML.replace(/<img(.*?[^>])>$/gi,"i"),t.trim(t(r).text()).replace(/[\t\n\r\n]/g,"").replace(/\u200B/g,"").length},clone:function(t){var e=this.selection.get(),i=this.selection.range(e);if(null===i&&void 0===t)return!1;if(!1===(t=void 0===t?this.$editor:t))return!1;t=t[0]||t;var r=i.cloneRange();return r.selectNodeContents(t),r.setEnd(i.endContainer,i.endOffset),r},set:function(t,e){e=void 0===e?t:e,this.focus.is()||this.focus.start();for(var i,r=this.selection.get(),n=this.selection.range(r),s=0,o=document.createTreeWalker(this.$editor[0],NodeFilter.SHOW_TEXT,null,null);null!==(i=o.nextNode());)if(s+=i.nodeValue.length,s>t&&(n.setStart(i,i.nodeValue.length+t-s),t=1/0),s>=e){n.setEnd(i,i.nodeValue.length+e-s);break}n.collapse(!1),this.selection.update(r,n)}}},paragraphize:function(){return{load:function(e){return!1===this.opts.paragraphize||"inline"===this.opts.type||"pre"===this.opts.type?e:""===e||"<p></p>"===e?this.opts.emptyHtml:(e+="\n",this.paragraphize.safes=[],this.paragraphize.z=0,e=e.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi,"</blockquote>"),e=e.replace(/<\/pre>/gi,"</pre>\n\n"),e=e.replace(/<p>\s<br><\/p>/gi,"<p></p>"),e=this.paragraphize.getSafes(e),e=e.replace("<br>","\n"),e=this.paragraphize.convert(e),e=this.paragraphize.clear(e),e=this.paragraphize.restoreSafes(e),e=e.replace(new RegExp("<br\\s?/?>\n?<("+this.opts.paragraphizeBlocks.join("|")+")(.*?[^>])>","gi"),"<p><br /></p>\n<$1$2>"),t.trim(e))},getSafes:function(e){var i=t("<div />").append(e);return i.find("blockquote p").replaceWith(function(){return t(this).append("<br />").contents()}),i.find(this.opts.paragraphizeBlocks.join(", ")).each(t.proxy(function(e,i){return this.paragraphize.z++,this.paragraphize.safes[this.paragraphize.z]=i.outerHTML,t(i).replaceWith("\n#####replace"+this.paragraphize.z+"#####\n\n")},this)),i.find("span.redactor-selection-marker").each(t.proxy(function(e,i){return this.paragraphize.z++,this.paragraphize.safes[this.paragraphize.z]=i.outerHTML,t(i).replaceWith("\n#####replace"+this.paragraphize.z+"#####\n\n")},this)),i.html()},restoreSafes:function(e){return t.each(this.paragraphize.safes,function(t,i){i=void 0!==i?i.replace(/\$/g,"&#36;"):i,e=e.replace("#####replace"+t+"#####",i)}),e},convert:function(e){e=e.replace(/\r\n/g,"xparagraphmarkerz"),e=e.replace(/\n/g,"xparagraphmarkerz"),e=e.replace(/\r/g,"xparagraphmarkerz");var i=/\s+/g;e=e.replace(i," "),e=t.trim(e);var r=/xparagraphmarkerzxparagraphmarkerz/gi;e=e.replace(r,"</p><p>");var n=/xparagraphmarkerz/gi;return e=e.replace(n,"<br>"),e="<p>"+e+"</p>",e=e.replace("<p></p>",""),e=e.replace("\r\n\r\n",""),e=e.replace(/<\/p><p>/g,"</p>\r\n\r\n<p>"),e=e.replace(new RegExp("<br\\s?/?></p>","g"),"</p>"),e=e.replace(new RegExp("<p><br\\s?/?>","g"),"<p>"),e=e.replace(new RegExp("<p><br\\s?/?>","g"),"<p>"),e=e.replace(new RegExp("<br\\s?/?></p>","g"),"</p>"),e=e.replace(/<p>&nbsp;<\/p>/gi,""),e=e.replace(/<p>\s?<br>&nbsp;<\/p>/gi,""),e=e.replace(/<p>\s?<br>/gi,"<p>")},clear:function(t){return t=t.replace(/<p>(.*?)#####replace(.*?)#####\s?<\/p>/gi,"<p>$1</p>#####replace$2#####"),t=t.replace(/(<br\s?\/?>){2,}<\/p>/gi,"</p>"),t=t.replace(new RegExp("</blockquote></p>","gi"),"</blockquote>"),t=t.replace(new RegExp("<p></blockquote>","gi"),"</blockquote>"),t=t.replace(new RegExp("<p><blockquote>","gi"),"<blockquote>"),t=t.replace(new RegExp("<blockquote></p>","gi"),"<blockquote>"),t=t.replace(new RegExp("<p><p ","gi"),"<p "),t=t.replace(new RegExp("<p><p>","gi"),"<p>"),t=t.replace(new RegExp("</p></p>","gi"),"</p>"),t=t.replace(new RegExp("<p>\\s?</p>","gi"),""),t=t.replace(new RegExp("\n</p>","gi"),"</p>"),t=t.replace(new RegExp("<p>\t?\t?\n?<p>","gi"),"<p>"),t=t.replace(new RegExp("<p>\t*</p>","gi"),"")}}},paste:function(){return{init:function(e){this.rtePaste=!0;var i=!("pre"!==this.opts.type&&!this.utils.isCurrentOrParent("pre"));if(this.detect.isDesktop()&&!this.paste.pre&&this.opts.clipboardImageUpload&&this.opts.imageUpload&&this.paste.detectClipboardUpload(e))return void(this.detect.isIe()&&setTimeout(t.proxy(this.paste.clipboardUpload,this),100));this.utils.saveScroll(),this.selection.save(),this.paste.createPasteBox(i),t(window).on("scroll.redactor-freeze",t.proxy(function(){t(window).scrollTop(this.saveBodyScroll)},this)),setTimeout(t.proxy(function(){var e=this.paste.getPasteBoxCode(i);this.buffer.set(),this.selection.restore(),this.utils.restoreScroll();var r=this.clean.getCurrentType(e);e=this.clean.onPaste(e,r);var n=this.core.callback("paste",e);e=void 0===n?e:n,this.paste.insert(e,r),this.rtePaste=!1,i&&this.clean.cleanPre(),t(window).off("scroll.redactor-freeze")},this),1)},getPasteBoxCode:function(t){var e=t?this.$pasteBox.val():this.$pasteBox.html();return this.$pasteBox.remove(),e},createPasteBox:function(e){var i={position:"fixed",width:"1px",top:0,left:"-9999px"};this.$pasteBox=e?t("<textarea>").css(i):t("<div>").attr("contenteditable","true").css(i),this.paste.appendPasteBox(),this.$pasteBox.focus()},appendPasteBox:function(){if(this.detect.isIe())this.core.box().append(this.$pasteBox);else{var e=t(".modal-body:visible");e.length>0?e.append(this.$pasteBox):t("body").prepend(this.$pasteBox)}},detectClipboardUpload:function(e){e=e.originalEvent||e;var i=e.clipboardData;if(this.detect.isIe()||this.detect.isFirefox())return!1;if(-1!==i.types.indexOf("public.tiff"))return e.preventDefault(),!1;if(i.items&&i.items.length){var r=i.items[0].getAsFile();if(null===r)return!1;var n=new FileReader;return n.readAsDataURL(r),n.onload=t.proxy(this.paste.insertFromClipboard,this),!0}},clipboardUpload:function(){var e=this.$editor.find("img");t.each(e,t.proxy(function(e,i){if(-1!==i.src.search(/^data\:image/i)){var r=window.FormData?new FormData:null;if(window.FormData){this.upload.direct=!0,this.upload.type="image",this.upload.url=this.opts.imageUpload,this.upload.callback=t.proxy(function(e){if(this.detect.isIe())t(i).wrap(t("<figure />"));else{var r=t(i).parent();this.utils.replaceToTag(r,"figure")}i.src=e.url,this.core.callback("imageUpload",t(i),e)},this);var n=this.utils.dataURItoBlob(i.src);r.append("clipboard",1),r.append(this.opts.imageUploadParam,n),this.upload.send(r,!1),this.code.sync(),this.rtePaste=!1}}},this))},insertFromClipboard:function(t){var e=window.FormData?new FormData:null;if(window.FormData){this.upload.direct=!0,this.upload.type="image",this.upload.url=this.opts.imageUpload,this.upload.callback=this.image.insert;var i=this.utils.dataURItoBlob(t.target.result);e.append("clipboard",1),e.append(this.opts.imageUploadParam,i),this.upload.send(e,t),this.rtePaste=!1}},insert:function(e,i){i.pre?this.insert.raw(e):i.text?this.insert.text(e):this.insert.html(e,i),this.detect.isFirefox()&&this.opts.imageUpload&&this.opts.clipboardImageUpload&&setTimeout(t.proxy(this.paste.clipboardUpload,this),100)}}},placeholder:function(){return{enable:function(){},show:function(){},update:function(){},hide:function(){},is:function(){},init:function(){},enabled:function(){},enableEvents:function(){},disableEvents:function(){},build:function(){},buildPosition:function(){},getPosition:function(){},isEditorEmpty:function(){},isAttr:function(){},destroy:function(){}}},progress:function(){return{$box:null,$bar:null,target:document.body,show:function(){},hide:function(){},update:function(){},is:function(){},build:function(){},destroy:function(){}}},selection:function(){return{get:function(){return window.getSelection?window.getSelection():document.selection&&"Control"!==document.selection.type?document.selection:null},range:function(t){return void 0===t&&(t=this.selection.get()),t.getRangeAt&&t.rangeCount?t.getRangeAt(0):null},is:function(){return!this.selection.isCollapsed()},isRedactor:function(){var e=this.selection.range();if(null!==e){var i=e.startContainer.parentNode;if(t(i).hasClass("redactor-in")||0!==t(i).parents(".redactor-in").length)return!0}return!1},isCollapsed:function(){var t=this.selection.get();return null!==t&&t.isCollapsed},update:function(t,e){null!==e&&(t.removeAllRanges(),t.addRange(e))},current:function(){var t=this.selection.get();return null!==t&&t.anchorNode},parent:function(){var t=this.selection.current();return null!==t&&t.parentNode},block:function(e){for(e=e||this.selection.current();e;){if(this.utils.isBlockTag(e.tagName))return!t(e).hasClass("redactor-in")&&e;e=e.parentNode}return!1},inline:function(e){for(e=e||this.selection.current();e;){if(this.utils.isInlineTag(e.tagName))return!t(e).hasClass("redactor-in")&&e;e=e.parentNode}return!1},element:function(e){for(e||(e=this.selection.current());e;){if(1===e.nodeType)return!t(e).hasClass("redactor-in")&&e;e=e.parentNode}return!1},prev:function(){return null!==this.selection.current()&&this.selection.current().previousSibling},next:function(){return null!==this.selection.current()&&this.selection.current().nextSibling},blocks:function(e){var i=[],r=this.selection.nodes(e);t.each(r,t.proxy(function(t,e){this.utils.isBlock(e)&&i.push(e)},this));var n=this.selection.block();return 0===i.length&&!1===n?[]:0===i.length&&!1!==n?[n]:i},inlines:function(e){var i=[],r=this.selection.nodes(e);t.each(r,t.proxy(function(t,e){this.utils.isInline(e)&&i.push(e)},this));var n=this.selection.inline();return 0===i.length&&!1===n?[]:0===i.length&&!1!==n?[n]:i},nodes:function(e){var i=void 0===e?[]:t.isArray(e)?e:[e],r=this.selection.get(),n=this.selection.range(r),s=[],o=[];if(this.utils.isCollapsed())s=[this.selection.current()];else{var a=n.startContainer,l=n.endContainer;if(a===l)return[a];for(;a&&a!==l;)s.push(a=this.selection.nextNode(a));for(a=n.startContainer;a&&a!==n.commonAncestorContainer;)s.unshift(a),a=a.parentNode}return t.each(s,function(e,r){if(r){var n=1===r.nodeType&&r.tagName.toLowerCase();if(t(r).hasClass("redactor-script-tag")||t(r).hasClass("redactor-selection-marker"))return;if(n&&0!==i.length&&-1===t.inArray(n,i))return;o.push(r)}}),0===o.length?[]:o},nextNode:function(t){if(t.hasChildNodes())return t.firstChild;for(;t&&!t.nextSibling;)t=t.parentNode;return t?t.nextSibling:null},save:function(){this.marker.insert(),this.savedSel=this.core.editor().html()},restore:function(t){var e=this.marker.find(1),i=this.marker.find(2);this.detect.isFirefox()&&this.core.editor().focus(),0!==e.length&&0!==i.length?this.caret.set(e,i):0!==e.length?this.caret.start(e):this.core.editor().focus(),!1!==t&&(this.marker.remove(),this.savedSel=!1)},saveInstant:function(){var t=this.core.editor()[0],e=t.ownerDocument,i=e.defaultView,r=i.getSelection();if(r.getRangeAt&&r.rangeCount){var n=r.getRangeAt(0),s=n.cloneRange();s.selectNodeContents(t),s.setEnd(n.startContainer,n.startOffset);var o=s.toString().length;return this.saved={start:o,end:o+n.toString().length,node:n.startContainer},this.saved}},restoreInstant:function(t){if(void 0!==t||this.saved){this.saved=void 0!==t?t:this.saved;var e=this.core.editor().find(this.saved.node);if(0===e.length||0!==e.text().trim().replace(/\u200B/g,"").length){var i=this.core.editor()[0],r=i.ownerDocument,n=r.defaultView,s=0,o=r.createRange();o.setStart(i,0),o.collapse(!0);for(var a,l=[i],c=!1,h=!1;!h&&(a=l.pop());)if(3==a.nodeType){var d=s+a.length;!c&&this.saved.start>=s&&this.saved.start<=d&&(o.setStart(a,this.saved.start-s),c=!0),c&&this.saved.end>=s&&this.saved.end<=d&&(o.setEnd(a,this.saved.end-s),h=!0),s=d}else for(var u=a.childNodes.length;u--;)l.push(a.childNodes[u]);var p=n.getSelection();p.removeAllRanges(),p.addRange(o)}else try{var o=document.createRange();o.setStart(e[0],0);var p=window.getSelection();p.removeAllRanges(),p.addRange(o)}catch(t){}}},node:function(e){t(e).prepend(this.marker.get(1)),t(e).append(this.marker.get(2)),this.selection.restore()},all:function(){this.core.editor().focus();var t=this.selection.get(),e=this.selection.range(t);e.selectNodeContents(this.core.editor()[0]),this.selection.update(t,e)},remove:function(){this.selection.get().removeAllRanges()},replace:function(t){this.insert.html(t)},text:function(){return this.selection.get().toString()},html:function(){var t="",e=this.selection.get();if(e.rangeCount){for(var i=document.createElement("div"),r=e.rangeCount,n=0;n<r;++n)i.appendChild(e.getRangeAt(n).cloneContents());t=this.clean.onGet(i.innerHTML)}return t},extractEndOfNode:function(t){var e=this.selection.get(),i=this.selection.range(e),r=i.cloneRange();return r.selectNodeContents(t),r.setStart(i.endContainer,i.endOffset),r.extractContents()},removeMarkers:function(){this.marker.remove()},marker:function(t){return this.marker.get(t)},markerHtml:function(t){return this.marker.html(t)}}},shortcuts:function(){return{hotkeysSpecialKeys:{8:"backspace",9:"tab",10:"return",13:"return",16:"shift",17:"ctrl",18:"alt",19:"pause",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"del",59:";",61:"=",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scroll",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},hotkeysShiftNums:{"`":"~",1:"!",2:"@",3:"#",4:"$",5:"%",6:"^",7:"&",8:"*",9:"(",0:")","-":"_","=":"+",";":": ","'":'"',",":"<",".":">","/":"?","\\":"|"},init:function(e,i){if(!1===this.opts.shortcuts)return!e.ctrlKey&&!e.metaKey||66!==i&&73!==i||e.preventDefault(),!1;t.each(this.opts.shortcuts,t.proxy(function(t,i){this.shortcuts.build(e,t,i)},this))},build:function(e,i,r){for(var n=t.proxy(function(){this.shortcuts.buildHandler(r)},this),s=i.split(","),o=s.length,a=0;a<o;a++)"string"==typeof s[a]&&this.shortcuts.handler(e,t.trim(s[a]),n)},buildHandler:function(t){var e;"-1"!==t.func.search(/\./)?(e=t.func.split("."),
+;if(this.keyup.block&&this.keydown.block&&"FIGURE"===this.keyup.block.tagName&&this.utils.isStartOfElement(this.keydown.block)){e.preventDefault(),this.selection.save(),t(this.keyup.block).find("figcaption").remove(),t(this.keyup.block).find("img").first().remove(),this.utils.replaceToTag(this.keyup.block,"p");var s=this.marker.find();return t("html, body").animate({scrollTop:s.position().top+20},500),void this.selection.restore()}if(this.keyup.block&&"P"===this.keyup.block.tagName){var o=t(this.keyup.block).find("img").length;""===t(this.keyup.block).text().replace(/\u200B/g,"")&&0!==o&&this.utils.replaceToTag(this.keyup.block,"figure")}this.keyup.block&&"FIGURE"===this.keyup.block.tagName&&0===t(this.keyup.block).find("img").length&&(this.selection.save(),this.utils.replaceToTag(this.keyup.block,"p"),this.selection.restore())}}}}},lang:function(){return{load:function(){this.opts.curLang=this.opts.langs[this.opts.lang]},get:function(t){return void 0!==this.opts.curLang[t]?this.opts.curLang[t]:""}}},line:function(){return{insert:function(){this.buffer.set(),this.insert.html(this.line.getLineHtml());var t=this.core.editor().find("#redactor-hr-tmp-id");return t.removeAttr("id"),this.core.callback("insertedLine",t),t},getLineHtml:function(){var t='<hr id="redactor-hr-tmp-id" />';return!this.detect.isFirefox()&&this.utils.isEmpty()&&(t+="<p>"+this.opts.emptyHtml+"</p>"),t},removeOnBackspace:function(e){if(this.utils.isCollapsed()){var i=t(this.selection.block());if(0!==i.length&&this.utils.isStartOfElement(i)){var r=i.prev();r&&0!==r.length&&"HR"===r[0].tagName&&(e.preventDefault(),r.remove())}}}}},link:function(){return{get:function(){return t(this.selection.inlines("a"))},is:function(){var e=this.selection.nodes(),i=t(this.selection.current()).closest("a",this.core.editor()[0]);return!(0===i.length||e.length>1)&&i},unlink:function(t){void 0!==t&&t.preventDefault&&t.preventDefault(),this.buffer.set();var e=this.selection.inlines("a");if(0!==e.length){var i=this.link.replaceLinksToText(e);this.observe.closeAllTooltip(),this.core.callback("deletedLink",i)}},insert:function(e,i){var r=this.link.is();if(!0!==i&&!1===(e=this.link.buildLinkFromObject(r,e)))return!1;if(this.buffer.set(),e=this.core.callback("beforeInsertingLink",e),!1===r){r=t("<a />"),r=this.link.update(r,e),r=t(this.insert.node(r));var n=r.parent();!1===this.utils.isRedactorParent(n)&&r.wrap("<p>"),n.hasClass("redactor-unlink")&&n.replaceWith(function(){return t(this).contents()}),this.caret.after(r),this.core.callback("insertedLink",r)}else r=this.link.update(r,e),this.caret.after(r);return r},update:function(t,e){return t.text(e.text),t.attr("href",e.url),this.link.target(t,e.target),t},target:function(t,e){return e?t.attr("target","_blank"):t.removeAttr("target")},show:function(e){void 0!==e&&e.preventDefault&&e.preventDefault(),this.observe.closeAllTooltip();var i=this.link.is();this.link.buildModal(i);var r=this.link.buildLinkFromElement(i);r.url=this.link.removeSelfHostFromUrl(r.url),this.opts.linkNewTab&&!i&&(r.target=!0),this.link.setModalValues(r),this.modal.show(),this.detect.isDesktop()&&t("#redactor-link-url").focus()},setModalValues:function(e){t("#redactor-link-blank").prop("checked",e.target),t("#redactor-link-url").val(e.url),t("#redactor-link-url-text").val(e.text)},buildModal:function(e){this.modal.load("link",this.lang.get(!1===e?"link-insert":"link-edit"),600),this.modal.getActionButton().text(this.lang.get(!1===e?"insert":"save")).on("click",t.proxy(this.link.callback,this))},callback:function(){var t=this.link.buildLinkFromModal();if(!1===t)return!1;this.modal.close(),this.link.insert(t,!0)},cleanUrl:function(e){return void 0===e?"":t.trim(e.replace(/[^\W\w\D\d+&\'@#/%?=~_|!:,.;\(\)]/gi,""))},cleanText:function(e){return void 0===e?"":t.trim(e.replace(/(<([^>]+)>)/gi,""))},getText:function(t){return""===t.text&&""!==t.url?this.link.truncateUrl(t.url.replace(/<|>/g,"")):t.text},isUrl:function(t){return!!new RegExp("^((https?|ftp):\\/\\/)?(([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$","i").test(t)&&t},isMailto:function(t){return-1!==t.search("@")&&!1===/(http|ftp|https):\/\//i.test(t)},isEmpty:function(t){return""===t.url||""===t.text&&""===t.url},truncateUrl:function(t){return t.length>this.opts.linkSize?t.substring(0,this.opts.linkSize)+"...":t},parse:function(t){return this.link.isMailto(t.url)?t.url="mailto:"+t.url.replace("mailto:",""):0!==t.url.search("#")&&this.opts.linkValidation&&(t.url=this.link.isUrl(t.url)?"http://"+t.url.replace(/(ftp|https?):\/\//gi,""):t.url),!this.link.isEmpty(t)&&!1!==t.url&&t},buildLinkFromModal:function(){var e={};return e.url=this.link.cleanUrl(t("#redactor-link-url").val()),e.text=this.link.cleanText(t("#redactor-link-url-text").val()),e.text=this.link.getText(e),e.target=!!t("#redactor-link-blank").prop("checked"),this.link.parse(e)},buildLinkFromObject:function(t,e){return e.url=this.link.cleanUrl(e.url),e.text=void 0===e.text&&this.selection.is()?this.selection.text():this.link.cleanText(e.text),e.text=this.link.getText(e),e.target=!1===t?e.target:this.link.buildTarget(t),this.link.parse(e)},buildLinkFromElement:function(t){var e={url:"",text:this.selection.is()?this.selection.text():"",target:!1};return!1!==t&&(e.url=t.attr("href"),e.text=t.text(),e.target=this.link.buildTarget(t)),e},buildTarget:function(t){return void 0!==t.attr("target")&&"_blank"===t.attr("target")},removeSelfHostFromUrl:function(t){var e=self.location.href.replace("#","").replace(/\/$/i,"");return t.replace(/^\/\#/,"#").replace(e,"").replace("mailto:","")},replaceLinksToText:function(e){var i,r=t.each(e,function(e,r){var n=t(r),s=t('<span class="redactor-unlink" />').append(n.contents());return n.replaceWith(s),0===e&&(i=s),n});return 1===e.length&&this.selection.isCollapsed()&&this.caret.after(i),r}}},linkify:function(){return{isKey:function(){},isLink:function(){},isFiltered:function(){},handler:function(){},format:function(){},convertVideoLinks:function(){},convertImages:function(){},convertLinks:function(){}}},list:function(){return{toggle:function(e){if(!this.utils.inBlocks(["table","td","th","tr"])){e="orderedlist"===e?"ol":e,e="unorderedlist"===e?"ul":e,e=e.toLowerCase(),this.buffer.set(),this.selection.save();var i=this.list._getBlocks(),r=this.selection.block(),n=t(r).parents("ul, ol").last();return 0===i.length&&0!==n.length&&(i=[n.get(0)]),i=this.list._isUnformat(e,i)?this.list._unformat(e,i):this.list._format(e,i),this.selection.restore(),i}},get:function(){var e=this.selection.current(),i=t(e).closest("ul, ol",this.core.editor()[0]);return 0!==i.length&&i},combineAfterAndBefore:function(e){var i=t(e).prev(),r=t(e).next(),n=e&&"P"===e.tagName&&("<br>"===e.innerHTML||""===e.innerHTML),s=1===i.closest("ol, ul",this.core.editor()[0]).length&&1===r.closest("ol, ul",this.core.editor()[0]).length;return!(!n||!s)&&(i.children("li").last().append(this.marker.get()),i.append(r.contents()),this.selection.restore(),!0)},_getBlocks:function(){for(var e=[],i=this.selection.blocks(),r=0;r<i.length;r++){t(i[r]).parent().hasClass("redactor-in")&&e.push(i[r])}return e},_isUnformat:function(t,e){for(var i=0,r=0;r<e.length;r++)if(3!==e[r].nodeType){var n=e[r].tagName.toLowerCase();n!==t&&"figure"!==n||i++}return i===e.length},_uniteBlocks:function(e,i){for(var r=0,n={0:[]},s=!1,o=0;o<e.length;o++){var a=t(e[o]),l=a.closest("th, td");0!==l.length?(l.get(0)!==s&&(r++,n[r]=[]),this.list._isUniteBlock(e[o],i)&&n[r].push(e[o])):this.list._isUniteBlock(e[o],i)?n[r].push(e[o]):(r++,n[r]=[]),s=l.get()}return n},_isUniteBlock:function(t,e){return 3===t.nodeType||-1!==e.indexOf(t.tagName.toLowerCase())},_createList:function(e,i,r){var n=i[i.length-1],s=t(n),o=t("<"+e+">");return s.after(o),o},_createListItem:function(e){var i=t("<li>");if(3===e.nodeType)i.append(e);else{var r=t(e);i.append(r.contents()),r.remove()}return i},_format:function(e,i){var r=["p","div","blockquote","pre","h1","h2","h3","h4","h5","h6","ul","ol"],n=this.list._uniteBlocks(i,r),s=[];for(var o in n){for(var a=n[o],l=this.list._createList(e,n[o]),c=0;c<a.length;c++){var h;3===a[c].nodeType||"UL"!==a[c].tagName&&"OL"!==a[c].tagName?(h=this.list._createListItem(a[c]),l.append(h)):(h=t(a[c]).contents(),l.append(h))}s.push(l.get(0))}return s},_unformat:function(e,i){if(1===i.length){var r=t(i[0]),n=r.find("li"),s=this.selection.blocks(["li"]),o=this.selection.block(),a=t(o).closest("li");if(0===s.length&&0!==a.length&&(s=[a.get(0)]),s.length===n.length)return this.list._unformatEntire(i[0]);var l=this.list._getItemsPosition(n,s);if("Top"===l)return this.list._unformatAtSide("before",s,r);if("Bottom"===l)return s.reverse(),this.list._unformatAtSide("after",s,r);if("Middle"===l){var c=t(s[s.length-1]),h=!1,d=!1,u=t("<"+r.get(0).tagName.toLowerCase()+">");n.each(function(e,i){if(h){var r=t(i);r.children("ul, ol").length;0!==r.closest(".redactor-split-item").length||!1!==d&&0!==r.closest(d).length||r.addClass("redactor-split-item"),d=r}i===c.get(0)&&(h=!0)}),n.filter(".redactor-split-item").each(function(e,i){t(i).removeClass("redactor-split-item"),u.append(i)}),r.after(u),s.reverse();for(var p=0;p<s.length;p++){var f=t(s[p]),g=this.list._createUnformatContainer(f);r.after(g),g.find("ul, ol").remove(),f.remove()}return}}else for(var p=0;p<i.length;p++)3!==i[p].nodeType&&i[p].tagName.toLowerCase()===e&&this.list._unformatEntire(i[p])},_unformatEntire:function(e){var i=t(e);i.find("li").each(function(e,r){var n=t(r),s=this.list._createUnformatContainer(n);n.remove(),i.before(s)}.bind(this)),i.remove()},_unformatAtSide:function(e,i,r){for(var n=0;n<i.length;n++){var s=t(i[n]),o=this.list._createUnformatContainer(s);r[e](o);var a=o.find("ul, ol").first();s.append(a),a.each(function(e,r){var n=t(r),s=n.closest("li");s.get(0)===i[e]&&(n.unwrap(),s.addClass("r-unwrapped"))}),this.utils.isEmpty(s.html())&&s.remove()}r.find(".r-unwrapped").each(function(e){var i=t(e);""===i.html().trim()?i.remove():i.removeClass("r-unwrapped")})},_getItemsPosition:function(t,e){var i="Middle",r=e[0],n=e[e.length-1],s=t.first().get(0),o=t.last().get(0);return s===r&&o!==n?i="Top":s!==r&&o===n&&(i="Bottom"),i},_createUnformatContainer:function(e){var i=t("<p>");return i.append(e.contents()),i}}},marker:function(){return{get:function(t){t=void 0===t?1:t;var e=document.createElement("span");return e.id="selection-marker-"+t,e.className="redactor-selection-marker",e.innerHTML=this.opts.invisibleSpace,e},html:function(t){return this.utils.getOuterHtml(this.marker.get(t))},find:function(t){return t=void 0===t?1:t,this.core.editor().find("span#selection-marker-"+t)},insert:function(){var t=this.selection.get(),e=this.selection.range(t);this.marker.insertNode(e,this.marker.get(1),!0),e&&!1===e.collapsed&&this.marker.insertNode(e,this.marker.get(2),!1)},remove:function(){this.core.editor().find(".redactor-selection-marker").each(this.marker.iterateRemove)},insertNode:function(e,i,r){var n=this.selection.parent();if(null!==e&&0!==t(n).closest(".redactor-in").length){e=e.cloneRange();try{e.collapse(r),e.insertNode(i)}catch(t){this.focus.start()}}},iterateRemove:function(e,i){var r=t(i),n=r.text().replace(/\u200B/g,"");r.parent()[0];""===n?r.remove():r.replaceWith(function(){return t(this).contents()})}}},modal:function(){return{callbacks:{},templates:function(){this.opts.modal={"image-edit":"",image:"",file:"",link:String()+'<div class="redactor-modal-tab" data-title="General"><section><label>URL</label><input type="url" id="redactor-link-url" aria-label="URL" /></section><section><label>'+this.lang.get("text")+'</label><input type="text" id="redactor-link-url-text" aria-label="'+this.lang.get("text")+'" /></section><section><label class="checkbox"><input type="checkbox" id="redactor-link-blank"> '+this.lang.get("link-in-new-tab")+'</label></section><section><button id="redactor-modal-button-action">'+this.lang.get("insert")+'</button><button id="redactor-modal-button-cancel">'+this.lang.get("cancel")+"</button></section></div>"},t.extend(this.opts,this.opts.modal)},addCallback:function(t,e){this.modal.callbacks[t]=e},addTemplate:function(t,e){this.opts.modal[t]=e},getTemplate:function(t){return this.opts.modal[t]},getModal:function(){return this.$modalBody},getActionButton:function(){return this.$modalBody.find("#redactor-modal-button-action")},getCancelButton:function(){return this.$modalBody.find("#redactor-modal-button-cancel")},getDeleteButton:function(){return this.$modalBody.find("#redactor-modal-button-delete")},load:function(){},show:function(){},buildWidth:function(){},buildTabber:function(){},showTab:function(){},setTitle:function(){},setContent:function(){this.$modalBody.html(this.modal.getTemplate(this.modal.templateName)),this.modal.getCancelButton().on("mousedown",t.proxy(this.modal.close,this))},setDraggable:function(){},setEnter:function(){},build:function(){this.modal.buildOverlay(),this.$modalBox=t('<div id="redactor-modal-box"/>').hide(),this.$modal=t('<div id="redactor-modal" role="dialog" />'),this.$modalHeader=t('<div id="redactor-modal-header" />'),this.$modalClose=t('<button type="button" id="redactor-modal-close" aria-label="'+this.lang.get("close")+'" />').html("&times;"),this.$modalBody=t('<div id="redactor-modal-body" />'),this.$modal.append(this.$modalHeader),this.$modal.append(this.$modalBody),this.$modal.append(this.$modalClose),this.$modalBox.append(this.$modal),this.$modalBox.appendTo(document.body)},buildOverlay:function(){this.$modalOverlay=t('<div id="redactor-modal-overlay">').hide(),t("body").prepend(this.$modalOverlay)},enableEvents:function(){},disableEvents:function(){},closeHandler:function(){},close:function(){}}},observe:function(){return{load:function(){void 0===this.opts.destroyed&&(this.observe.links(),this.observe.images())},isCurrent:function(e,i){return void 0===i&&(i=t(this.selection.current())),i.is(e)||i.parents(e).length>0},toolbar:function(){this.observe.buttons(),this.observe.dropdowns()},buttons:function(e,i){var r=this.selection.current(),n=this.selection.parent();if(!1!==e?this.button.setInactiveAll():this.button.setInactiveAll(i),!1===e&&"html"!==i)return void(-1!==t.inArray(i,this.opts.activeButtons)&&this.button.toggleActive(i));this.utils.isRedactorParent(r)&&("none"!==this.core.editor().css("display")&&(this.utils.isCurrentOrParentHeader()||this.utils.isCurrentOrParent(["table","pre","blockquote","li"])?this.button.disable("horizontalrule"):this.button.enable("horizontalrule")),t.each(this.opts.activeButtonsStates,t.proxy(function(e,i){var s=t(n).closest(e,this.$editor[0]),o=t(r).closest(e,this.$editor[0]);(0===s.length||this.utils.isRedactorParent(s))&&this.utils.isRedactorParent(o)&&(0===s.length&&0===o.closest(e,this.$editor[0]).length||this.button.setActive(i))},this)))},dropdowns:function(){var e=t("<div />").html(this.selection.html()).find("a").length,i=t(this.selection.current()),r=this.utils.isRedactorParent(i);t.each(this.opts.observe.dropdowns,t.proxy(function(t,n){var s=n.observe,o=s.element,a=n.item,l=void 0!==s.in&&s.in,c=void 0!==s.out&&s.out;i.closest(o).length>0&&r||"a"===o&&0!==e?this.observe.setDropdownProperties(a,l,c):this.observe.setDropdownProperties(a,c,l)},this))},setDropdownProperties:function(t,e,i){i&&void 0!==i.attr&&this.observe.setDropdownAttr(t,i.attr,!0),void 0!==e.attr&&this.observe.setDropdownAttr(t,e.attr),void 0!==e.title&&t.find("span").text(e.title)},setDropdownAttr:function(e,i,r){t.each(i,function(t,i){"class"===t?r?e.removeClass(i):e.addClass(i):r?e.removeAttr(t):e.attr(t,i)})},addDropdown:function(t,e,i){void 0!==i.observe&&(i.item=t,this.opts.observe.dropdowns.push(i))},images:function(){this.opts.imageEditable&&(this.core.editor().addClass("redactor-layer-img-edit"),this.core.editor().find("img").each(t.proxy(function(e,i){var r=t(i);r.closest("a",this.$editor[0]).on("click",function(t){t.preventDefault()}),this.image.setEditable(r)},this)))},links:function(){this.opts.linkTooltip&&this.core.editor().find("a").each(t.proxy(function(e,i){var r=t(i);!0!==r.data("cached")&&(r.data("cached",!0),r.on("touchstart.redactor."+this.uuid+" click.redactor."+this.uuid,t.proxy(this.observe.showTooltip,this)))},this))},getTooltipPosition:function(t){return t.offset()},showTooltip:function(e){var i=t(e.target);if("IMG"!==i[0].tagName&&("A"!==i[0].tagName&&(i=i.closest("a",this.$editor[0])),"A"===i[0].tagName)){var r=i,n=this.observe.getTooltipPosition(r),s=t('<span class="redactor-link-tooltip"></span>'),o=r.attr("href");void 0===o&&(o=""),o.length>24&&(o=o.substring(0,24)+"...");var a=t('<a href="'+r.attr("href")+'" target="_blank" />').html(o).addClass("redactor-link-tooltip-action"),l=t('<a href="#" />').html(this.lang.get("edit")).on("click",t.proxy(this.link.show,this)).addClass("redactor-link-tooltip-action"),c=t('<a href="#" />').html(this.lang.get("unlink")).on("click",t.proxy(this.link.unlink,this)).addClass("redactor-link-tooltip-action");s.append(a).append(" | ").append(l).append(" | ").append(c);var h=parseInt(r.css("line-height"),10),d=Math.ceil((e.pageY-n.top)/h),u=n.top+d*h;s.css({top:u+"px",left:n.left+"px"}),t(".redactor-link-tooltip").remove(),t("body").append(s),this.core.editor().on("touchstart.redactor."+this.uuid+" click.redactor."+this.uuid,t.proxy(this.observe.closeTooltip,this)),t(document).on("touchstart.redactor."+this.uuid+" click.redactor."+this.uuid,t.proxy(this.observe.closeTooltip,this))}},closeAllTooltip:function(){t(".redactor-link-tooltip").remove()},closeTooltip:function(e){e=e.originalEvent||e;var i=e.target,r=t(i).closest("a",this.$editor[0]);0!==r.length&&"A"===r[0].tagName&&"A"!==i.tagName||"A"===i.tagName&&this.utils.isRedactorParent(i)||t(i).hasClass("redactor-link-tooltip-action")||(this.observe.closeAllTooltip(),this.core.editor().off("touchstart.redactor."+this.uuid+" click.redactor."+this.uuid,t.proxy(this.observe.closeTooltip,this)),t(document).off("touchstart.redactor."+this.uuid+" click.redactor."+this.uuid,t.proxy(this.observe.closeTooltip,this)))}}},offset:function(){return{get:function(e){var i=this.offset.clone(e);if(!1===i)return 0;var r=document.createElement("div");return r.appendChild(i.cloneContents()),r.innerHTML=r.innerHTML.replace(/<img(.*?[^>])>$/gi,"i"),t.trim(t(r).text()).replace(/[\t\n\r\n]/g,"").replace(/\u200B/g,"").length},clone:function(t){var e=this.selection.get(),i=this.selection.range(e);if(null===i&&void 0===t)return!1;if(!1===(t=void 0===t?this.$editor:t))return!1;t=t[0]||t;var r=i.cloneRange();return r.selectNodeContents(t),r.setEnd(i.endContainer,i.endOffset),r},set:function(t,e){e=void 0===e?t:e,this.focus.is()||this.focus.start();for(var i,r=this.selection.get(),n=this.selection.range(r),s=0,o=document.createTreeWalker(this.$editor[0],NodeFilter.SHOW_TEXT,null,null);null!==(i=o.nextNode());)if(s+=i.nodeValue.length,s>t&&(n.setStart(i,i.nodeValue.length+t-s),t=1/0),s>=e){n.setEnd(i,i.nodeValue.length+e-s);break}n.collapse(!1),this.selection.update(r,n)}}},paragraphize:function(){return{load:function(e){return!1===this.opts.paragraphize||"inline"===this.opts.type||"pre"===this.opts.type?e:""===e||"<p></p>"===e?this.opts.emptyHtml:(e+="\n",this.paragraphize.safes=[],this.paragraphize.z=0,e=e.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi,"</blockquote>"),e=e.replace(/<\/pre>/gi,"</pre>\n\n"),e=e.replace(/<p>\s<br><\/p>/gi,"<p></p>"),e=this.paragraphize.getSafes(e),e=e.replace("<br>","\n"),e=this.paragraphize.convert(e),e=this.paragraphize.clear(e),e=this.paragraphize.restoreSafes(e),e=e.replace(new RegExp("<br\\s?/?>\n?<("+this.opts.paragraphizeBlocks.join("|")+")(.*?[^>])>","gi"),"<p><br /></p>\n<$1$2>"),t.trim(e))},getSafes:function(e){var i=t("<div />").append(e);return i.find("blockquote p").replaceWith(function(){return t(this).append("<br />").contents()}),i.find(this.opts.paragraphizeBlocks.join(", ")).each(t.proxy(function(e,i){return this.paragraphize.z++,this.paragraphize.safes[this.paragraphize.z]=i.outerHTML,t(i).replaceWith("\n#####replace"+this.paragraphize.z+"#####\n\n")},this)),i.find("span.redactor-selection-marker").each(t.proxy(function(e,i){return this.paragraphize.z++,this.paragraphize.safes[this.paragraphize.z]=i.outerHTML,t(i).replaceWith("\n#####replace"+this.paragraphize.z+"#####\n\n")},this)),i.html()},restoreSafes:function(e){return t.each(this.paragraphize.safes,function(t,i){i=void 0!==i?i.replace(/\$/g,"&#36;"):i,e=e.replace("#####replace"+t+"#####",i)}),e},convert:function(e){e=e.replace(/\r\n/g,"xparagraphmarkerz"),e=e.replace(/\n/g,"xparagraphmarkerz"),e=e.replace(/\r/g,"xparagraphmarkerz");var i=/\s+/g;e=e.replace(i," "),e=t.trim(e);var r=/xparagraphmarkerzxparagraphmarkerz/gi;e=e.replace(r,"</p><p>");var n=/xparagraphmarkerz/gi;return e=e.replace(n,"<br>"),e="<p>"+e+"</p>",e=e.replace("<p></p>",""),e=e.replace("\r\n\r\n",""),e=e.replace(/<\/p><p>/g,"</p>\r\n\r\n<p>"),e=e.replace(new RegExp("<br\\s?/?></p>","g"),"</p>"),e=e.replace(new RegExp("<p><br\\s?/?>","g"),"<p>"),e=e.replace(new RegExp("<p><br\\s?/?>","g"),"<p>"),e=e.replace(new RegExp("<br\\s?/?></p>","g"),"</p>"),e=e.replace(/<p>&nbsp;<\/p>/gi,""),e=e.replace(/<p>\s?<br>&nbsp;<\/p>/gi,""),e=e.replace(/<p>\s?<br>/gi,"<p>")},clear:function(t){return t=t.replace(/<p>(.*?)#####replace(.*?)#####\s?<\/p>/gi,"<p>$1</p>#####replace$2#####"),t=t.replace(/(<br\s?\/?>){2,}<\/p>/gi,"</p>"),t=t.replace(new RegExp("</blockquote></p>","gi"),"</blockquote>"),t=t.replace(new RegExp("<p></blockquote>","gi"),"</blockquote>"),t=t.replace(new RegExp("<p><blockquote>","gi"),"<blockquote>"),t=t.replace(new RegExp("<blockquote></p>","gi"),"<blockquote>"),t=t.replace(new RegExp("<p><p ","gi"),"<p "),t=t.replace(new RegExp("<p><p>","gi"),"<p>"),t=t.replace(new RegExp("</p></p>","gi"),"</p>"),t=t.replace(new RegExp("<p>\\s?</p>","gi"),""),t=t.replace(new RegExp("\n</p>","gi"),"</p>"),t=t.replace(new RegExp("<p>\t?\t?\n?<p>","gi"),"<p>"),t=t.replace(new RegExp("<p>\t*</p>","gi"),"")}}},paste:function(){return{init:function(e){this.rtePaste=!0;var i=!("pre"!==this.opts.type&&!this.utils.isCurrentOrParent("pre"));if(this.detect.isDesktop()&&!this.paste.pre&&this.opts.clipboardImageUpload&&this.opts.imageUpload&&this.paste.detectClipboardUpload(e))return void(this.detect.isIe()&&setTimeout(t.proxy(this.paste.clipboardUpload,this),100));this.utils.saveScroll(),this.selection.save(),this.paste.createPasteBox(i),t(window).on("scroll.redactor-freeze",t.proxy(function(){t(window).scrollTop(this.saveBodyScroll)},this)),setTimeout(t.proxy(function(){var e=this.paste.getPasteBoxCode(i);this.buffer.set(),this.selection.restore(),this.utils.restoreScroll();var r=this.clean.getCurrentType(e);e=this.clean.onPaste(e,r);var n=this.core.callback("paste",e);e=void 0===n?e:n,this.paste.insert(e,r),this.rtePaste=!1,i&&this.clean.cleanPre(),t(window).off("scroll.redactor-freeze")},this),1)},getPasteBoxCode:function(t){var e=t?this.$pasteBox.val():this.$pasteBox.html();return this.$pasteBox.remove(),e},createPasteBox:function(e){var i={position:"fixed",width:"1px",top:0,left:"-9999px"};this.$pasteBox=e?t("<textarea>").css(i):t("<div>").attr("contenteditable","true").css(i),this.paste.appendPasteBox(),this.$pasteBox.focus()},appendPasteBox:function(){if(this.detect.isIe())this.core.box().append(this.$pasteBox);else{var e=t(".modal-body:visible");e.length>0?e.append(this.$pasteBox):t("body").prepend(this.$pasteBox)}},detectClipboardUpload:function(e){e=e.originalEvent||e;var i=e.clipboardData;if(this.detect.isIe()||this.detect.isFirefox())return!1;if(-1!==i.types.indexOf("public.tiff"))return e.preventDefault(),!1;if(i.items&&i.items.length){var r=i.items[0].getAsFile();if(null===r)return!1;var n=new FileReader;return n.readAsDataURL(r),n.onload=t.proxy(this.paste.insertFromClipboard,this),!0}},clipboardUpload:function(){var e=this.$editor.find("img");t.each(e,t.proxy(function(e,i){if(-1!==i.src.search(/^data\:image/i)){var r=window.FormData?new FormData:null;if(window.FormData){this.upload.direct=!0,this.upload.type="image",this.upload.url=this.opts.imageUpload,this.upload.callback=t.proxy(function(e){if(this.detect.isIe())t(i).wrap(t("<figure />"));else{var r=t(i).parent();this.utils.replaceToTag(r,"figure")}i.src=e.url,this.core.callback("imageUpload",t(i),e)},this);var n=this.utils.dataURItoBlob(i.src);r.append("clipboard",1),r.append(this.opts.imageUploadParam,n),this.upload.send(r,!1),this.code.sync(),this.rtePaste=!1}}},this))},insertFromClipboard:function(t){var e=window.FormData?new FormData:null;if(window.FormData){this.upload.direct=!0,this.upload.type="image",this.upload.url=this.opts.imageUpload,this.upload.callback=this.image.insert;var i=this.utils.dataURItoBlob(t.target.result);e.append("clipboard",1),e.append(this.opts.imageUploadParam,i),this.upload.send(e,t),this.rtePaste=!1}},insert:function(e,i){i.pre?this.insert.raw(e):i.text?this.insert.text(e):this.insert.html(e,i),this.detect.isFirefox()&&this.opts.imageUpload&&this.opts.clipboardImageUpload&&setTimeout(t.proxy(this.paste.clipboardUpload,this),100)}}},placeholder:function(){return{enable:function(){},show:function(){},update:function(){},hide:function(){},is:function(){},init:function(){},enabled:function(){},enableEvents:function(){},disableEvents:function(){},build:function(){},buildPosition:function(){},getPosition:function(){},isEditorEmpty:function(){},isAttr:function(){},destroy:function(){}}},progress:function(){return{$box:null,$bar:null,target:document.body,show:function(){},hide:function(){},update:function(){},is:function(){},build:function(){},destroy:function(){}}},selection:function(){return{get:function(){return window.getSelection?window.getSelection():document.selection&&"Control"!==document.selection.type?document.selection:null},range:function(t){return void 0===t&&(t=this.selection.get()),t.getRangeAt&&t.rangeCount?t.getRangeAt(0):null},is:function(){return!this.selection.isCollapsed()},isRedactor:function(){var e=this.selection.range();if(null!==e){var i=e.startContainer.parentNode;if(t(i).hasClass("redactor-in")||0!==t(i).parents(".redactor-in").length)return!0}return!1},isCollapsed:function(){var t=this.selection.get();return null!==t&&t.isCollapsed},update:function(t,e){null!==e&&(t.removeAllRanges(),t.addRange(e))},current:function(){var t=this.selection.get();return null!==t&&t.anchorNode},parent:function(){var t=this.selection.current();return null!==t&&t.parentNode},block:function(e){for(e=e||this.selection.current();e;){if(this.utils.isBlockTag(e.tagName))return!t(e).hasClass("redactor-in")&&e;e=e.parentNode}return!1},inline:function(e){for(e=e||this.selection.current();e;){if(this.utils.isInlineTag(e.tagName))return!t(e).hasClass("redactor-in")&&e;e=e.parentNode}return!1},element:function(e){for(e||(e=this.selection.current());e;){if(1===e.nodeType)return!t(e).hasClass("redactor-in")&&e;e=e.parentNode}return!1},prev:function(){return null!==this.selection.current()&&this.selection.current().previousSibling},next:function(){return null!==this.selection.current()&&this.selection.current().nextSibling},blocks:function(e){var i=[],r=this.selection.nodes(e);t.each(r,t.proxy(function(t,e){this.utils.isBlock(e)&&i.push(e)},this));var n=this.selection.block();return 0===i.length&&!1===n?[]:0===i.length&&!1!==n?[n]:i},inlines:function(e){var i=[],r=this.selection.nodes(e);t.each(r,t.proxy(function(t,e){this.utils.isInline(e)&&i.push(e)},this));var n=this.selection.inline();return 0===i.length&&!1===n?[]:0===i.length&&!1!==n?[n]:i},nodes:function(e){var i=void 0===e?[]:t.isArray(e)?e:[e],r=this.selection.get(),n=this.selection.range(r),s=[],o=[];if(this.utils.isCollapsed())s=[this.selection.current()];else{var a=n.startContainer,l=n.endContainer;if(a===l)return[a];for(;a&&a!==l;)s.push(a=this.selection.nextNode(a));for(a=n.startContainer;a&&a!==n.commonAncestorContainer;)s.unshift(a),a=a.parentNode}return t.each(s,function(e,r){if(r){var n=1===r.nodeType&&r.tagName.toLowerCase();if(t(r).hasClass("redactor-script-tag")||t(r).hasClass("redactor-selection-marker"))return;if(n&&0!==i.length&&-1===t.inArray(n,i))return;o.push(r)}}),0===o.length?[]:o},nextNode:function(t){if(t.hasChildNodes())return t.firstChild;for(;t&&!t.nextSibling;)t=t.parentNode;return t?t.nextSibling:null},save:function(){this.marker.insert(),this.savedSel=this.core.editor().html()},restore:function(t){var e=this.marker.find(1),i=this.marker.find(2);this.detect.isFirefox()&&this.core.editor().focus(),0!==e.length&&0!==i.length?this.caret.set(e,i):0!==e.length?this.caret.start(e):this.core.editor().focus(),!1!==t&&(this.marker.remove(),this.savedSel=!1)},saveInstant:function(){var t=this.core.editor()[0],e=t.ownerDocument,i=e.defaultView,r=i.getSelection();if(r.getRangeAt&&r.rangeCount){var n=r.getRangeAt(0),s=n.cloneRange();s.selectNodeContents(t),s.setEnd(n.startContainer,n.startOffset);var o=s.toString().length;return this.saved={start:o,end:o+n.toString().length,node:n.startContainer},this.saved}},restoreInstant:function(t){if(void 0!==t||this.saved){this.saved=void 0!==t?t:this.saved;var e=this.core.editor().find(this.saved.node);if(0===e.length||0!==e.text().trim().replace(/\u200B/g,"").length){var i=this.core.editor()[0],r=i.ownerDocument,n=r.defaultView,s=0,o=r.createRange();o.setStart(i,0),o.collapse(!0);for(var a,l=[i],c=!1,h=!1;!h&&(a=l.pop());)if(3==a.nodeType){var d=s+a.length;!c&&this.saved.start>=s&&this.saved.start<=d&&(o.setStart(a,this.saved.start-s),c=!0),c&&this.saved.end>=s&&this.saved.end<=d&&(o.setEnd(a,this.saved.end-s),h=!0),s=d}else for(var u=a.childNodes.length;u--;)l.push(a.childNodes[u]);var p=n.getSelection();p.removeAllRanges(),p.addRange(o)}else try{var o=document.createRange();o.setStart(e[0],0);var p=window.getSelection();p.removeAllRanges(),p.addRange(o)}catch(t){}}},node:function(e){t(e).prepend(this.marker.get(1)),t(e).append(this.marker.get(2)),this.selection.restore()},all:function(){this.core.editor().focus();var t=this.selection.get(),e=this.selection.range(t);e.selectNodeContents(this.core.editor()[0]),this.selection.update(t,e)},remove:function(){this.selection.get().removeAllRanges()},replace:function(t){this.insert.html(t)},text:function(){return this.selection.get().toString()},html:function(){var t="",e=this.selection.get();if(e.rangeCount){for(var i=document.createElement("div"),r=e.rangeCount,n=0;n<r;++n)i.appendChild(e.getRangeAt(n).cloneContents());t=this.clean.onGet(i.innerHTML)}return t},extractEndOfNode:function(t){var e=this.selection.get(),i=this.selection.range(e),r=i.cloneRange();return r.selectNodeContents(t),r.setStart(i.endContainer,i.endOffset),r.extractContents()},removeMarkers:function(){this.marker.remove()},marker:function(t){return this.marker.get(t)},markerHtml:function(t){return this.marker.html(t)}}},shortcuts:function(){return{hotkeysSpecialKeys:{8:"backspace",9:"tab",10:"return",13:"return",16:"shift",17:"ctrl",18:"alt",19:"pause",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"del",59:";",61:"=",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scroll",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},hotkeysShiftNums:{"`":"~",1:"!",2:"@",3:"#",4:"$",5:"%",6:"^",7:"&",8:"*",9:"(",0:")","-":"_","=":"+",";":": ","'":'"',",":"<",".":">","/":"?","\\":"|"},init:function(e,i){if(!1===this.opts.shortcuts)return!e.ctrlKey&&!e.metaKey||66!==i&&73!==i||e.preventDefault(),!1;t.each(this.opts.shortcuts,t.proxy(function(t,i){this.shortcuts.build(e,t,i)},this))},build:function(e,i,r){for(var n=t.proxy(function(){this.shortcuts.buildHandler(r)},this),s=i.split(","),o=s.length,a=0;a<o;a++)"string"==typeof s[a]&&this.shortcuts.handler(e,t.trim(s[a]),n)},buildHandler:function(t){var e;"-1"!==t.func.search(/\./)?(e=t.func.split("."),
 void 0!==this[e[0]]&&this[e[0]][e[1]].apply(this,t.params)):this[t.func].apply(this,t.params)},handler:function(e,i,r){i=i.toLowerCase().split(" ");var n=this.shortcuts.hotkeysSpecialKeys[e.keyCode],s=String.fromCharCode(e.which).toLowerCase(),o="",a={};t.each(["alt","ctrl","meta","shift"],function(t,i){e[i+"Key"]&&n!==i&&(o+=i+"+")}),n&&(a[o+n]=!0),s&&(a[o+s]=!0,a[o+this.shortcuts.hotkeysShiftNums[s]]=!0,"shift+"===o&&(a[this.shortcuts.hotkeysShiftNums[s]]=!0));for(var l=i.length,c=0;c<l;c++)if(a[i[c]])return e.preventDefault(),r.apply(this,arguments)}}},storage:function(){return{data:[],add:function(){},status:function(){},observe:function(){},changes:function(){}}},toolbar:function(){return{build:function(){this.button.hideButtons(),this.button.hideButtonsOnMobile(),this.$toolbarBox=t('<div class="redactor-toolbar-box" />'),this.$toolbarBox[0].innerHTML='<ul class="redactor-toolbar" id="redactor-toolbar-'+this.uuid+'" role="toolbar"></ul>',this.$toolbar=t(this.$toolbarBox[0].children[0]),this.$box[0].insertBefore(this.$toolbarBox[0],this.$box[0].firstChild),this.button.$toolbar=this.$toolbar,this.button.setFormatting(),this.button.load(this.$toolbar),require(["Core"],function(t){this.$toolbar[0].addEventListener("keydown",this.toolbar.keydown.bind(this,t))}.bind(this))},createContainer:function(){},append:function(){},setOverflow:function(){},setFixed:function(){},setUnfixed:function(){},getBoxTop:function(){},observeScroll:function(){},observeScrollResize:function(){},observeScrollEnable:function(){},observeScrollDisable:function(){},setDropdownsFixed:function(){},unsetDropdownsFixed:function(){},setDropdownPosition:function(){},keydown:function(e,i){var r=document.activeElement;if(r.classList.contains("re-button")){if(-1!==[13,32,35,36,37,39,40].indexOf(i.which)){if(13===i.which||32===i.which)return i.preventDefault(),void require(["Core"],function(t){t.triggerEvent(r,"mousedown")});if(40===i.which){if("true"!==elAttr(r,"aria-haspopup"))return;i.preventDefault(),e.triggerEvent(r,"mousedown");var n=t(r).data("dropdown"),s=elBySel("li",n[0]);return void(s&&s.focus())}i.preventDefault();var o=Array.prototype.slice.call(elBySelAll(".re-button",this.$toolbar[0])),a=null;if(35===i.which)a=o[o.length-1];else if(36===i.which)a=o[0];else{var l=o.indexOf(r);37===i.which?-1===--l&&(l=o.length-1):39===i.which&&++l===o.length&&(l=0),a=o[l]}null!==a&&a.focus()}}}}},upload:function(){return{init:function(){},directUpload:function(){},onDrop:function(){},traverseFile:function(){},setConfig:function(){},getType:function(){},getHiddenFields:function(){},send:function(){},onDrag:function(){},onDragLeave:function(){},clearImageFields:function(){},addImageFields:function(){},removeImageFields:function(){},clearFileFields:function(){},addFileFields:function(){},removeFileFields:function(){}}},uploads3:function(){return{send:function(){},executeOnSignedUrl:function(){},createCORSRequest:function(){},sendToS3:function(){}}},utils:function(){return{isEmpty:function(e){return e=void 0===e?this.core.editor().html():e,e=e.replace(/[\u200B-\u200D\uFEFF]/g,""),e=e.replace(/&nbsp;/gi,""),e=e.replace(/<\/?br\s?\/?>/g,""),e=e.replace(/\s/g,""),e=e.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i,""),e=e.replace(/<iframe(.*?[^>])>$/i,"iframe"),e=e.replace(/<source(.*?[^>])>$/i,"source"),e=e.replace(/<[^\/>][^>]*><\/[^>]+>/gi,""),e=e.replace(/<[^\/>][^>]*><\/[^>]+>/gi,""),""===(e=t.trim(e))},isElement:function(t){try{return t instanceof HTMLElement}catch(e){return"object"==typeof t&&1===t.nodeType&&"object"==typeof t.style&&"object"==typeof t.ownerDocument}},strpos:function(t,e,i){var r=t.indexOf(e,i);return r>=0&&r},dataURItoBlob:function(t){var e;e=t.split(",")[0].indexOf("base64")>=0?atob(t.split(",")[1]):unescape(t.split(",")[1]);for(var i=t.split(",")[0].split(":")[1].split(";")[0],r=new Uint8Array(e.length),n=0;n<e.length;n++)r[n]=e.charCodeAt(n);return new Blob([r],{type:i})},getOuterHtml:function(e){return t("<div>").append(t(e).eq(0).clone()).html()},cloneAttributes:function(e,i){e=e[0]||e,i=t(i);for(var r=e.attributes,n=r.length;n--;){var s=r[n];i.attr(s.name,s.value)}return i},breakBlockTag:function(){var e=this.selection.block();if(!e)return!1;var i=this.utils.isEmpty(e.innerHTML),r=e.tagName.toLowerCase();if("pre"===r||"li"===r||"td"===r||"th"===r)return!1;if(!i&&this.utils.isStartOfElement(e))return{$block:t(e),$next:t(e).next(),type:"start"};if(!i&&this.utils.isEndOfElement(e))return{$block:t(e),$next:t(e).next(),type:"end"};var n=this.selection.extractEndOfNode(e),s=t("<"+r+" />").append(n);return s=this.utils.cloneAttributes(e,s),t(e).after(s),{$block:t(e),$next:s,type:"break"}},inBlocks:function(e){e=t.isArray(e)?e:[e];for(var i=this.selection.blocks(),r=i.length,n=!1,s=0;s<r;s++)if(!1!==i[s]){var o=i[s].tagName.toLowerCase();-1!==t.inArray(o,e)&&(n=!0)}return n},inInlines:function(e){e=t.isArray(e)?e:[e];for(var i=this.selection.inlines(),r=i.length,n=!1,s=0;s<r;s++){var o=i[s].tagName.toLowerCase();-1!==t.inArray(o,e)&&(n=!0)}return n},isTag:function(e,i){var r=t(e).closest(i,this.core.editor()[0]);return 1===r.length&&r[0]},isBlock:function(t){return null!==t&&((t=t[0]||t)&&this.utils.isBlockTag(t.tagName))},isBlockTag:function(t){return void 0!==t&&this.reIsBlock.test(t)},isInline:function(t){return(t=t[0]||t)&&this.utils.isInlineTag(t.tagName)},isInlineTag:function(t){return void 0!==t&&this.reIsInline.test(t)},isRedactorParent:function(e){return!!e&&(0!==t(e).parents(".redactor-in").length&&!t(e).hasClass("redactor-in")&&e)},isCurrentOrParentHeader:function(){return this.utils.isCurrentOrParent(["H1","H2","H3","H4","H5","H6"])},isCurrentOrParent:function(e){var i=this.selection.parent(),r=this.selection.current();if(t.isArray(e)){var n=0;return t.each(e,t.proxy(function(t,e){this.utils.isCurrentOrParentOne(r,i,e)&&n++},this)),0!==n}return this.utils.isCurrentOrParentOne(r,i,e)},isCurrentOrParentOne:function(t,e,i){return i=i.toUpperCase(),e&&e.tagName===i?e:!(!t||t.tagName!==i)&&t},isEditorRelative:function(){var e=this.core.editor().css("position"),i=["absolute","fixed","relative"];return-1!==t.inArray(i,e)},setEditorRelative:function(){this.core.editor().addClass("redactor-relative")},getScrollTarget:function(){var e=t(this.opts.scrollTarget);return 0!==e.length?e:t(document)},freezeScroll:function(){this.freezeScrollTop=this.utils.getScrollTarget().scrollTop(),this.utils.getScrollTarget().scrollTop(this.freezeScrollTop)},unfreezeScroll:function(){void 0!==this.freezeScrollTop&&this.utils.getScrollTarget().scrollTop(this.freezeScrollTop)},saveScroll:function(){this.tmpScrollTop=this.utils.getScrollTarget().scrollTop()},restoreScroll:function(){void 0!==this.tmpScrollTop&&this.utils.getScrollTarget().scrollTop(this.tmpScrollTop)},isStartOfElement:function(t){return!(void 0===t&&!(t=this.selection.block()))&&0===this.offset.get(t)},isEndOfElement:function(e){if(void 0===e&&!(e=this.selection.block()))return!1;var i=t.trim(t(e).text()).replace(/[\t\n\r\n]/g,"").replace(/\u200B/g,"");return this.offset.get(e)===i.length},removeEmptyAttr:function(e,i){var r=t(e);return void 0===r.attr(i)||""===r.attr(i)&&(r.removeAttr(i),!0)},replaceToTag:function(e,i){var r;return t(e).replaceWith(function(){r=t("<"+i+" />").append(t(this).contents());for(var e=0;e<this.attributes.length;e++)r.attr(this.attributes[e].name,this.attributes[e].value);return r}),r},isSelectAll:function(){return this.selectAll},enableSelectAll:function(){this.selectAll=!0},disableSelectAll:function(){this.selectAll=!1},disableBodyScroll:function(){},measureScrollbar:function(){var e=t("body"),i=document.createElement("div");i.className="redactor-scrollbar-measure",e.append(i);var r=i.offsetWidth-i.clientWidth;return e[0].removeChild(i),r},enableBodyScroll:function(){},appendFields:function(e,i){if(!e)return i;if("object"==typeof e)return t.each(e,function(e,r){null!==r&&0===r.toString().indexOf("#")&&(r=t(r).val()),i.append(e,r)}),i;var r=t(e);if(0===r.length)return i;return r.each(function(){i.append(t(this).attr("name"),t(this).val())}),i},appendForms:function(e,i){if(!e)return i;var r=t(e);if(0===r.length)return i;var n=r.serializeArray();return t.each(n,function(t,e){i.append(e.name,e.value)}),i},isRgb:function(t){return 0===t.search(/^rgb/i)},rgb2hex:function(t){return t=t.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i),t&&4===t.length?"#"+("0"+parseInt(t[1],10).toString(16)).slice(-2)+("0"+parseInt(t[2],10).toString(16)).slice(-2)+("0"+parseInt(t[3],10).toString(16)).slice(-2):""},isCollapsed:function(){return this.selection.isCollapsed()},isMobile:function(){return this.detect.isMobile()},isDesktop:function(){return this.detect.isDesktop()},isPad:function(){return this.detect.isIpad()}}},browser:function(){return{webkit:function(){return this.detect.isWebkit()},ff:function(){return this.detect.isFirefox()},ie:function(){return this.detect.isIe()}}}},t(window).on("load.tools.redactor",function(){t('[data-tools="redactor"]').redactor()}),e.prototype.init.prototype=e.prototype}(jQuery),function(t){function e(e,i,r,n){var s={duration:.5,iterate:1,delay:0,prefix:"redactor-",timing:"linear"};this.animation=i,this.slide="slideDown"===this.animation||"slideUp"===this.animation,this.$element=t(e),this.prefixes=["","-moz-","-o-animation-","-webkit-"],this.queue=[],"function"==typeof r?(n=r,this.opts=s):this.opts=t.extend(s,r),this.slide&&this.$element.height(this.$element.height()),this.init(n)}t.fn.redactorAnimation=function(t,i,r){return this.each(function(){new e(this,t,i,r)})},e.prototype={init:function(t){this.queue.push(this.animation),this.clean(),"show"===this.animation?(this.opts.timing="linear",this.$element.removeClass("hide").show(),"function"==typeof t&&t(this)):"hide"===this.animation?(this.opts.timing="linear",this.$element.hide(),"function"==typeof t&&t(this)):this.animate(t)},animate:function(e){this.$element.addClass("redactor-animated").css("display","").removeClass("hide"),this.$element.addClass(this.opts.prefix+this.queue[0]),this.set(this.opts.duration+"s",this.opts.delay+"s",this.opts.iterate,this.opts.timing);var i=this.queue.length>1?null:e;this.complete("AnimationEnd",t.proxy(function(){this.$element.hasClass(this.opts.prefix+this.queue[0])&&(this.clean(),this.queue.shift(),this.queue.length&&this.animate(e))},this),i)},set:function(t,e,i,r){for(var n=this.prefixes.length;n--;)this.$element.css(this.prefixes[n]+"animation-duration",t),this.$element.css(this.prefixes[n]+"animation-delay",e),this.$element.css(this.prefixes[n]+"animation-iteration-count",i),this.$element.css(this.prefixes[n]+"animation-timing-function",r)},clean:function(){this.$element.removeClass("redactor-animated"),this.$element.removeClass(this.opts.prefix+this.queue[0]),this.set("","","","")},complete:function(e,i,r){this.$element.one(e.toLowerCase()+" webkit"+e+" o"+e+" MS"+e,t.proxy(function(){"function"==typeof i&&i(),"function"==typeof r&&r(this);var e=["fadeOut","slideUp","zoomOut","slideOutUp","slideOutRight","slideOutLeft"];-1!==t.inArray(this.animation,e)&&this.$element.css("display","none"),this.slide&&this.$element.css("height","")},this))}}}(jQuery); })(this);
 
 // plugins/WoltLabArticle.js
@@ -23,7 +23,7 @@ void 0!==this[e[0]]&&this[e[0]][e[1]].apply(this,t.params)):this[t.func].apply(t
 (function (window, undefined) { $.Redactor.prototype.WoltLabButton=function(){"use strict";var t;return{init:function(){var t,e,o,n;for(o=0,n=this.opts.woltlab.customButtons.length;o<n;o++)e=this.opts.woltlab.customButtons[o],t=this.button.add(e,""),this.button.addCallback(t,this.WoltLabButton._handleCustomButton);var s,i,a,r=this.core.toolbar()[0];for(o=0,n=this.opts.buttons.length;o<n;o++)if("wcfSeparator"!==(e=this.opts.buttons[o])){if(!this.opts.woltlab.buttons.hasOwnProperty(e))throw new Error("Missing button definition for '"+e+"'.");switch(s=this.opts.woltlab.buttons[e],"underline"===e&&(this.opts.activeButtonsStates.u="underline"),e){case"subscript":case"superscript":t=this.button.addAfter(this.opts.buttons[o-1],e,""),this.button.setEvent(t,e,{func:"inline.format"}),this.opts.activeButtonsStates["subscript"===e?"sub":"sup"]=e;break;case"redo":case"undo":t=this.button.addAfter(this.opts.buttons[o-1],e,""),this.button.addCallback(t,this.buffer[e]);break;default:t=this.button.get(e)}if(i=s.icon,a=!i.match(/^fa-/)&&i.match(/\.(gif|jpe?g|png|svg)$/),this.button.setIcon(t,'<span class="icon icon16 '+(a?"redactorButtonImage":i)+'"'+(a?" style=\"background-image: url('"+WCF_PATH+"icon/"+i+"')\"":"")+"></span>"),!t[0])throw new Error("Missing button element for '"+e+"'.");if(t[0].title=s.title,t[0].classList.add("jsTooltip"),"lists"===e){var l=t.data("dropdown");elBySel(".redactor-dropdown-outdent span",l[0]).textContent=WCF.Language.get("wcf.editor.list.outdent"),elBySel(".redactor-dropdown-indent span",l[0]).textContent=WCF.Language.get("wcf.editor.list.indent")}}for(var d,c={},u=[];r.childElementCount;)d=r.removeChild(r.children[0]),e=elAttr(d.children[0],"rel"),c[e]=d,u.push(e);var h=!1;for(o=0,n=this.opts.buttons.length;o<n;o++)e=this.opts.buttons[o],"wcfSeparator"!==e?(d=c[e],r.appendChild(d),u.splice(u.indexOf(e),1),h&&(d.classList.add("redactor-toolbar-separator"),h=!1)):h=!0;for(o=0,n=r.childElementCount;o<n;o++)d=r.children[o],t=d.children[0],elData(d,"show-on-mobile",-1!==this.opts.woltlab.buttonMobile.indexOf(t.rel));u.forEach(function(t){r.appendChild(c[t])}),WCF.DOMNodeInsertedHandler.execute(),require(["Ui/Screen"],function(t){t.on("screen-xs",{match:this.WoltLabButton._enableToggleButton.bind(this),unmatch:this.WoltLabButton._disableToggleButton.bind(this),setup:this.WoltLabButton._setupToggleButton.bind(this)})}.bind(this)),r.addEventListener("dragstart",function(t){t.preventDefault()}),elAttr(elBySel(".re-html",r),"tabindex",0)},_handleCustomButton:function(t){var e={cancel:!1};if(WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","bbcode_"+t+"_"+this.$element[0].id,e),!0!==e.cancel){this.buffer.set();var o=this.marker.get();o.classList.add("woltlab-bbcode-marker");var n="["+t+"]"+this.selection.html()+o.outerHTML+"[/"+t+"]";this.insert.html(n),this.selection.restore()}window.setTimeout(function(){document.activeElement!==this.$editor[0]&&this.$editor[0].focus()}.bind(this),10)},_enableToggleButton:function(){null===t.parentNode&&this.$toolbar[0].appendChild(t)},_disableToggleButton:function(){t&&null!==t.parentNode&&this.$toolbar[0].removeChild(t)},_setupToggleButton:function(){t=elCreate("li"),t.className="redactorToolbarToggle",t.innerHTML='<a href="#"><span class="icon icon16 fa-caret-down"></span></a>',elData(t,"show-on-mobile",!0);var e=t.children[0].children[0],o=function(t){t instanceof Event&&t.preventDefault(),this.$toolbar[0].classList.toggle("redactorToolbarOverride")&&document.activeElement&&document.activeElement!==this.$editor[0]&&document.activeElement.blur(),e.classList.toggle("fa-caret-down"),e.classList.toggle("fa-caret-up")}.bind(this);t.children[0].addEventListener("mousedown",o),this.$toolbar[0].appendChild(t),WCF.System.Event.addListener("com.woltlab.wcf.redactor2","reset_"+this.$element[0].id,function(){this.$toolbar[0].classList.contains("redactorToolbarOverride")&&o()}.bind(this))}}}; })(this);
 
 // plugins/WoltLabCaret.js
-(function (window, undefined) { $.Redactor.prototype.WoltLabCaret=function(){"use strict";var e,t=!1,n=!1;return{init:function(){var i=this.caret.after;this.caret.after=function(e){e=this.caret.prepare(e),this.utils.isBlockTag(e.tagName)&&this.WoltLabCaret._addParagraphAfterBlock(e),i.call(this,e)}.bind(this);var r=this.caret.start;this.caret.start=function(e){if(n&&t){if(!(e=this.caret.prepare(e)))return;"P"===e.nodeName&&"​"===e.innerHTML&&(e.innerHTML="<br>")}r.call(this,e)}.bind(this);var o=this.core.editor()[0];require(["Environment"],function(i){t="ios"===i.platform(),(n="safari"===i.browser())&&o.classList.add("jsSafariMarginClickTarget");var r=this.WoltLabCaret._handleEditorClick.bind(this),a=this.WoltLabCaret._handleEditorMouseUp.bind(this);n&&t?(o.addEventListener("touchstart",function(t){e=t.target},{passive:!0}),o.addEventListener("touchend",function(e){r(e),a(e)}.bind(this))):(o.addEventListener(WCF_CLICK_EVENT,function(e){this.WoltLabCaret._detectTripleClick(e),r(e)}.bind(this)),o.addEventListener("mouseup",a))}.bind(this));var a=this.caret.end;this.caret.end=function(e){e=this.caret.prepare(e),"OL"!==e.nodeName&&"UL"!==e.nodeName||null===(e=e.lastElementChild)&&(e=e.parentNode);var n=!1;if(e.nodeType===Node.ELEMENT_NODE&&e.lastChild&&"P"===e.lastChild.nodeName)n=!0;else if(t){var i=this.core.editor()[0];e.parentNode===i&&"<p><br></p>"===i.innerHTML&&(n=!0)}else"P"===e.nodeName&&0===e.childNodes.length&&(e.innerHTML="​",n=!0);if(n){var r=window.getSelection(),o=document.createRange();return o.selectNodeContents(e.lastChild),o.collapse(!1),r.removeAllRanges(),void r.addRange(o)}return"P"===e.nodeName&&1===e.childNodes.length&&"BR"===e.childNodes[0].nodeName?this.caret.before(e.childNodes[0]):a.call(this,e)}.bind(this);var s=this.selection.nodes;this.selection.nodes=function(e){var t=s.call(this,e);if(1===t.length&&t[0]===this.$editor[0]){var n=this.selection.range(this.selection.get());if(n.startContainer===n.endContainer)return[n.startContainer]}return t}.bind(this),this.WoltLabCaret._initInternalRange();var l=this.selection.saveInstant;this.selection.saveInstant=function(){var e=l.call(this);if(e){e.isAtNodeStart=!1;var t=window.getSelection();if(t.rangeCount&&!t.isCollapsed){var n=t.getRangeAt(0);n.startContainer.nodeType===Node.TEXT_NODE&&0===n.startOffset&&(e.isAtNodeStart=!0)}}return e}.bind(this);var d=this.selection.restoreInstant;this.selection.restoreInstant=function(e){if(void 0!==e||this.saved){var t=void 0!==e?e:this.saved;d.call(this,e);var n=window.getSelection();if(n.rangeCount)if(!0===t.isAtNodeStart){if(!n.isCollapsed){var i=n.getRangeAt(0),r=i.startContainer;if(t.node===r)return;for(;null!==r&&"P"!==r.nodeName;)r=r.parentNode;if(null!==r&&null!==(r=r.nextElementSibling)&&"P"===r.nodeName&&0===r.textContent.replace(/\u200B/g,"").length){r=r.nextElementSibling;for(var o=t.node;null!==o&&o!==r;)o=o.parentNode;o===r&&(i=i.cloneRange(),i.setStart(t.node,0),n.removeAllRanges(),n.addRange(i))}}}else if(n.isCollapsed){var a=n.anchorNode,s=this.core.editor()[0];if(a.nodeType===Node.TEXT_NODE&&a.parentNode===s&&n.anchorOffset===a.textContent.length){var l=a.nextElementSibling;l&&"P"===l.nodeName&&this.caret.start(l)}}}}.bind(this),this.selection.nodes=function(e){var t=void 0===e?[]:$.isArray(e)?e:[e],n=this.selection.get(),i=this.selection.range(n),r=[],o=[];if(this.utils.isCollapsed())r=[this.selection.current()];else{var a=i.startContainer,s=i.endContainer;if(a===s)return[a];for(var l=i.commonAncestorContainer;a&&a!==s;)r.push(a=this.selection.nextNode(a,l));for(a=i.startContainer;a&&a!==l;)r.unshift(a),a=a.parentNode}return $.each(r,function(e,n){if(n){var i=1===n.nodeType&&n.tagName.toLowerCase();if($(n).hasClass("redactor-script-tag")||$(n).hasClass("redactor-selection-marker"))return;if(i&&0!==t.length&&-1===$.inArray(i,t))return;o.push(n)}}),0===o.length?[]:o}.bind(this),this.selection.nextNode=function(e,t){if(e.hasChildNodes())return e.firstChild;for(;e&&!e.nextSibling;)if(e=e.parentNode,t&&e===t)return null;return e?e.nextSibling:null}},paragraphAfterBlock:function(e){var t=e.nextElementSibling;t&&"P"!==t.nodeName&&(t=elCreate("p"),"<p><br></p>"===this.opts.emptyHtml?t.innerHTML="<br>":t.textContent="​",e.parentNode.insertBefore(t,e.nextSibling)),this.caret.after(e)},endOfEditor:function(){var e=this.core.editor()[0];document.activeElement!==e&&e.focus();var t=e.lastElementChild;"P"===t.nodeName?this.caret.end(t):this.caret.after(t)},_initInternalRange:function(){var e=this.core.editor()[0],t=null,n=window.getSelection(),i=function(){t=n.rangeCount?n.getRangeAt(0).cloneRange():null};this.WoltLabCaret.forceSelectionSave=i;var r=function(){if(null!==t){if(document.activeElement===e){var i=n.getRangeAt(0);if(0!==i.startOffset)return;for(var r=i.startContainer;r;){if(r.parentNode===e){if(r.previousSibling)return;break}if(r.previousSibling)return;r=r.parentNode}if(!r)return}var o=e.scrollLeft,a=e.scrollTop;e.focus(),e.scrollLeft=o,e.scrollTop=a,n.removeAllRanges(),n.addRange(t),t=null}};e.addEventListener("keyup",i),e.addEventListener("mouseup",function(){n.rangeCount&&i()});var o=this.selection.save;this.selection.save=function(){t=null,o.call(this)}.bind(this);var a=this.selection.restore;this.selection.restore=function(){t&&null===elBySel(".redactor-selection-marker",this.$editor[0])&&(r(),n.rangeCount&&this.utils.isRedactorParent(n.getRangeAt(0).commonAncestorContainer))||a.call(this)}.bind(this);var s=this.buffer.set;this.buffer.set=function(t){if(document.activeElement!==e){var n=window.getSelection();n.rangeCount&&!1!==this.utils.isRedactorParent(n.anchorNode)?e.focus():r()}s.call(this,t),i()}.bind(this);var l=this.insert.html;this.insert.html=function(e,t){var n=elBySel(".redactor-selection-marker",this.$editor[0]);l.call(this,e,t),(n||null===elBySel(".redactor-selection-marker",this.$editor[0]))&&i()}.bind(this),require(["Environment"],function(t){"ios"===t.platform()&&(e.addEventListener("focus",function(){document.addEventListener("selectionchange",i)}),e.addEventListener("blur",function(){document.removeEventListener("selectionchange",i)}))}.bind(this))},_detectTripleClick:function(e){if(!(e.detail<3)){var t=window.getSelection();if(!t.isCollapsed){var n=t.getRangeAt(0);if("TR"===n.commonAncestorContainer.nodeName){var i=elClosest(n.startContainer,"td");n=document.createRange(),n.selectNodeContents(i),t.removeAllRanges(),t.addRange(n)}}}},_handleEditorClick:function(i){var r=i.clientY;if(!this.selection.get().isCollapsed){if(!(n&&t&&e===i.target&&this.utils.isBlockTag(e.nodeName)))return;r=i.changedTouches[0].clientY}var o=this.selection.block();if(!1===o){if(this.selection.current()===this.$editor[0]){var a=this.$editor[0].childNodes[this.selection.get().anchorOffset];a.nodeType===Node.ELEMENT_NODE&&"TABLE"===a.nodeName&&(o=a)}if(!1===o)return}var s=!1;n&&this.utils.isBlockTag(i.target.nodeName)&&r>i.target.getBoundingClientRect().bottom&&(o=i.target,s=!0);for(var l=i.target;l&&!this.utils.isBlockTag(l.nodeName);)l=l.parentNode;if(l&&(s||l!==o)&&("P"!==o.nodeName||(o=o.parentNode)!==this.$editor[0]&&this.utils.isBlockTag(o.nodeName))){if("TD"===o.nodeName)for(;"TABLE"!==o.nodeName;)o=o.parentNode;if(!o.nodeName.match(/^H\d$/)&&!$(o).closest("ol, ul",this.$editor[0]).length){for(var d,c,h=o;h;){if(c=h.getBoundingClientRect(),r<c.top)d=!0,o=h;else{if(!(r>c.bottom))break;d=!1,o=h}if(!h.parentNode||h.parentNode===this.$editor[0])break;h=h.parentNode}if(void 0!==d){var f=o[(d?"previous":"next")+"ElementSibling"];if(f&&"P"===f.nodeName)return void this.caret.end(f);this.buffer.set();var u=elCreate("p");u.textContent="​",o.parentNode.insertBefore(u,d?o:o.nextSibling),this.caret.end(u)}}}},_handleEditorMouseUp:function(i){var r,o,a=window.getSelection();if(a.isCollapsed||n&&t&&e===i.target&&this.utils.isBlockTag(e.nodeName))if(i.target===this.$editor[0])r=a.anchorNode,r.nodeType===Node.TEXT_NODE&&(r=r.parentNode),"KBD"===r.nodeName&&(o=r.previousSibling,null!==o&&"​"===o.textContent||(o=document.createTextNode("​"),r.parentNode.insertBefore(o,r)),this.caret.before(o));else if("KBD"===i.target.nodeName){var s=i.target;if(r=a.anchorNode,r.nodeType===Node.TEXT_NODE){for(o=r;(o=o.nextSibling)&&o.nodeType===Node.TEXT_NODE&&(""===o.textContent||"​"===o.textContent););if(o===s){if(0===s.childNodes.length||"​"!==s.childNodes[0].textContent){var l=document.createTextNode("​");s.insertBefore(l,s.firstChild)}var d=document.createRange();d.setStartAfter(s.childNodes[0]),d.setEndAfter(s.childNodes[0]),a.removeAllRanges(),a.addRange(d)}}}},_addParagraphAfterBlock:function(e){var t=e.nextElementSibling;t&&("P"===t.nodeName||this.utils.isBlockTag(t.nodeName))||(t=elCreate("p"),t.textContent="​",e.parentNode.insertBefore(t,e.nextSibling))}}}; })(this);
+(function (window, undefined) { $.Redactor.prototype.WoltLabCaret=function(){"use strict";var e,t=!1,n=!1;return{init:function(){var i=this.caret.after;this.caret.after=function(e){e=this.caret.prepare(e),this.utils.isBlockTag(e.tagName)&&this.WoltLabCaret._addParagraphAfterBlock(e),i.call(this,e)}.bind(this);var r=this.caret.start;this.caret.start=function(e){if(n&&t){if(!(e=this.caret.prepare(e)))return;"P"===e.nodeName&&"​"===e.innerHTML&&(e.innerHTML="<br>")}r.call(this,e)}.bind(this);var o=this.core.editor()[0];require(["Environment"],function(i){t="ios"===i.platform(),(n="safari"===i.browser())&&o.classList.add("jsSafariMarginClickTarget");var r=this.WoltLabCaret._handleEditorClick.bind(this),a=this.WoltLabCaret._handleEditorMouseUp.bind(this);n&&t?(o.addEventListener("touchstart",function(t){e=t.target},{passive:!0}),o.addEventListener("touchend",function(e){r(e),a(e)}.bind(this))):(o.addEventListener(WCF_CLICK_EVENT,function(e){this.WoltLabCaret._detectTripleClick(e),r(e)}.bind(this)),o.addEventListener("mouseup",a))}.bind(this));var a=this.caret.end;this.caret.end=function(e){e=this.caret.prepare(e),"OL"!==e.nodeName&&"UL"!==e.nodeName||null===(e=e.lastElementChild)&&(e=e.parentNode);var n=!1;if(e.nodeType===Node.ELEMENT_NODE&&e.lastChild&&"P"===e.lastChild.nodeName)n=!0;else if(t){var i=this.core.editor()[0];e.parentNode===i&&"<p><br></p>"===i.innerHTML&&(n=!0)}else"P"===e.nodeName&&0===e.childNodes.length&&(e.innerHTML="​",n=!0);if(n){var r=window.getSelection(),o=document.createRange();return o.selectNodeContents(e.lastChild),o.collapse(!1),r.removeAllRanges(),void r.addRange(o)}return"P"===e.nodeName&&1===e.childNodes.length&&"BR"===e.childNodes[0].nodeName?this.caret.before(e.childNodes[0]):a.call(this,e)}.bind(this);var s=this.selection.nodes;this.selection.nodes=function(e){var t=s.call(this,e);if(1===t.length&&t[0]===this.$editor[0]){var n=this.selection.range(this.selection.get());if(n.startContainer===n.endContainer)return[n.startContainer]}return t}.bind(this),this.WoltLabCaret._initInternalRange();var l=this.selection.saveInstant;this.selection.saveInstant=function(){var e=l.call(this);if(e){e.isAtNodeStart=!1;var t=window.getSelection();if(t.rangeCount&&!t.isCollapsed){var n=t.getRangeAt(0);n.startContainer.nodeType===Node.TEXT_NODE&&0===n.startOffset&&(e.isAtNodeStart=!0)}}return e}.bind(this);var d=this.selection.restoreInstant;this.selection.restoreInstant=function(e){if(void 0!==e||this.saved){var t=void 0!==e?e:this.saved;d.call(this,e);var n=window.getSelection();if(n.rangeCount)if(!0===t.isAtNodeStart){if(!n.isCollapsed){var i=n.getRangeAt(0),r=i.startContainer;if(t.node===r)return;for(;null!==r&&"P"!==r.nodeName;)r=r.parentNode;if(null!==r&&null!==(r=r.nextElementSibling)&&"P"===r.nodeName&&0===r.textContent.replace(/\u200B/g,"").length){r=r.nextElementSibling;for(var o=t.node;null!==o&&o!==r;)o=o.parentNode;o===r&&(i=i.cloneRange(),i.setStart(t.node,0),n.removeAllRanges(),n.addRange(i))}}}else if(n.isCollapsed){var a=n.anchorNode,s=this.core.editor()[0];if(a.nodeType===Node.TEXT_NODE&&a.parentNode===s&&n.anchorOffset===a.textContent.length){var l=a.nextElementSibling;l&&"P"===l.nodeName&&this.caret.start(l)}}}}.bind(this),this.selection.nodes=function(e){var t=void 0===e?[]:$.isArray(e)?e:[e],n=this.selection.get(),i=this.selection.range(n),r=[],o=[];if(this.utils.isCollapsed())r=[this.selection.current()];else{var a=i.startContainer,s=i.endContainer;if(a===s)return[a];for(var l=i.commonAncestorContainer;a&&a!==s;)r.push(a=this.selection.nextNode(a,l));for(a=i.startContainer;a&&a!==l;)r.unshift(a),a=a.parentNode}return $.each(r,function(e,n){if(n){var i=1===n.nodeType&&n.tagName.toLowerCase();if($(n).hasClass("redactor-script-tag")||$(n).hasClass("redactor-selection-marker"))return;if(i&&0!==t.length&&-1===$.inArray(i,t))return;o.push(n)}}),0===o.length?[]:o}.bind(this),this.selection.nextNode=function(e,t){if(e.hasChildNodes())return e.firstChild;for(;e&&!e.nextSibling;)if(e=e.parentNode,t&&e===t)return null;return e?e.nextSibling:null}},paragraphAfterBlock:function(e){var t=e.nextElementSibling;t&&"P"!==t.nodeName&&(t=elCreate("p"),t.textContent="​",e.parentNode.insertBefore(t,e.nextSibling)),this.caret.after(e)},endOfEditor:function(){var e=this.core.editor()[0];document.activeElement!==e&&e.focus();var t=e.lastElementChild;"P"===t.nodeName?this.caret.end(t):this.caret.after(t)},_initInternalRange:function(){var e=this.core.editor()[0],t=null,n=window.getSelection(),i=function(){t=n.rangeCount?n.getRangeAt(0).cloneRange():null};this.WoltLabCaret.forceSelectionSave=i;var r=function(){if(null!==t){if(document.activeElement===e){var i=n.getRangeAt(0);if(0!==i.startOffset)return;for(var r=i.startContainer;r;){if(r.parentNode===e){if(r.previousSibling)return;break}if(r.previousSibling)return;r=r.parentNode}if(!r)return}var o=e.scrollLeft,a=e.scrollTop;e.focus(),e.scrollLeft=o,e.scrollTop=a,n.removeAllRanges(),n.addRange(t),t=null}};e.addEventListener("keyup",i),e.addEventListener("mouseup",function(){n.rangeCount&&i()});var o=this.selection.save;this.selection.save=function(){t=null,o.call(this)}.bind(this);var a=this.selection.restore;this.selection.restore=function(){t&&null===elBySel(".redactor-selection-marker",this.$editor[0])&&(r(),n.rangeCount&&this.utils.isRedactorParent(n.getRangeAt(0).commonAncestorContainer))||a.call(this)}.bind(this);var s=this.buffer.set;this.buffer.set=function(t){if(document.activeElement!==e){var n=window.getSelection();n.rangeCount&&!1!==this.utils.isRedactorParent(n.anchorNode)?e.focus():r()}s.call(this,t),i()}.bind(this);var l=this.insert.html;this.insert.html=function(e,t){var n=elBySel(".redactor-selection-marker",this.$editor[0]);l.call(this,e,t),(n||null===elBySel(".redactor-selection-marker",this.$editor[0]))&&i()}.bind(this),require(["Environment"],function(t){"ios"===t.platform()&&(e.addEventListener("focus",function(){document.addEventListener("selectionchange",i)}),e.addEventListener("blur",function(){document.removeEventListener("selectionchange",i)}))}.bind(this))},_detectTripleClick:function(e){if(!(e.detail<3)){var t=window.getSelection();if(!t.isCollapsed){var n=t.getRangeAt(0);if("TR"===n.commonAncestorContainer.nodeName){var i=elClosest(n.startContainer,"td");n=document.createRange(),n.selectNodeContents(i),t.removeAllRanges(),t.addRange(n)}}}},_handleEditorClick:function(i){var r=i.clientY;if(!this.selection.get().isCollapsed){if(!(n&&t&&e===i.target&&this.utils.isBlockTag(e.nodeName)))return;r=i.changedTouches[0].clientY}var o=this.selection.block();if(!1===o){if(this.selection.current()===this.$editor[0]){var a=this.$editor[0].childNodes[this.selection.get().anchorOffset];a.nodeType===Node.ELEMENT_NODE&&"TABLE"===a.nodeName&&(o=a)}if(!1===o)return}var s=!1;n&&this.utils.isBlockTag(i.target.nodeName)&&r>i.target.getBoundingClientRect().bottom&&(o=i.target,s=!0);for(var l=i.target;l&&!this.utils.isBlockTag(l.nodeName);)l=l.parentNode;if(l&&(s||l!==o)&&("P"!==o.nodeName||(o=o.parentNode)!==this.$editor[0]&&this.utils.isBlockTag(o.nodeName))){if("TD"===o.nodeName)for(;"TABLE"!==o.nodeName;)o=o.parentNode;if(!o.nodeName.match(/^H\d$/)&&!$(o).closest("ol, ul",this.$editor[0]).length){for(var d,c,h=o;h;){if(c=h.getBoundingClientRect(),r<c.top)d=!0,o=h;else{if(!(r>c.bottom))break;d=!1,o=h}if(!h.parentNode||h.parentNode===this.$editor[0])break;h=h.parentNode}if(void 0!==d){var f=o[(d?"previous":"next")+"ElementSibling"];if(f&&"P"===f.nodeName)return void this.caret.end(f);this.buffer.set();var u=elCreate("p");u.textContent="​",o.parentNode.insertBefore(u,d?o:o.nextSibling),this.caret.end(u)}}}},_handleEditorMouseUp:function(i){var r,o,a=window.getSelection();if(a.isCollapsed||n&&t&&e===i.target&&this.utils.isBlockTag(e.nodeName))if(i.target===this.$editor[0])r=a.anchorNode,r.nodeType===Node.TEXT_NODE&&(r=r.parentNode),"KBD"===r.nodeName&&(o=r.previousSibling,null!==o&&"​"===o.textContent||(o=document.createTextNode("​"),r.parentNode.insertBefore(o,r)),this.caret.before(o));else if("KBD"===i.target.nodeName){var s=i.target;if(r=a.anchorNode,r.nodeType===Node.TEXT_NODE){for(o=r;(o=o.nextSibling)&&o.nodeType===Node.TEXT_NODE&&(""===o.textContent||"​"===o.textContent););if(o===s){if(0===s.childNodes.length||"​"!==s.childNodes[0].textContent){var l=document.createTextNode("​");s.insertBefore(l,s.firstChild)}var d=document.createRange();d.setStartAfter(s.childNodes[0]),d.setEndAfter(s.childNodes[0]),a.removeAllRanges(),a.addRange(d)}}}},_addParagraphAfterBlock:function(e){var t=e.nextElementSibling;t&&("P"===t.nodeName||this.utils.isBlockTag(t.nodeName))||(t=elCreate("p"),t.textContent="​",e.parentNode.insertBefore(t,e.nextSibling))}}}; })(this);
 
 // plugins/WoltLabClean.js
 (function (window, undefined) { $.Redactor.prototype.WoltLabClean=function(){"use strict";return{init:function(){var e=this.clean.onSet;this.clean.onSet=function(t){t=t.replace(/\u200B/g,""),t=t.replace(/&amp;amp;/g,"@@@WCF_LITERAL_AMP@@@"),t=t.replace(/&amp;/g,"&amp;WCF_AMPERSAND&amp;"),t=e.call(this,t),t=t.replace(/&amp;WCF_AMPERSAND&(amp;)?/g,"&amp;"),t=t.replace(/@@@WCF_LITERAL_AMP@@@/g,"&amp;amp;");var n=elCreate("div");return n.innerHTML=t,elBySelAll("*",n,function(e){for(var t,n=[],r=0,l=e.attributes.length;r<l;r++)t=e.attributes[r],0===t.name.indexOf("on")&&n.push(t.name);n.forEach(e.removeAttribute.bind(e))}),elBySelAll("iframe",n,elRemove),elBySelAll("pre",n,function(e){e.classList.contains("redactor-script-tag")&&elRemove(e)}),elBySelAll("td",n,function(e){0===e.childNodes.length&&(e.innerHTML="​")}),elBySelAll("pre, woltlab-quote, woltlab-spoiler",n,function(e){0!==e.childElementCount||0!==e.textContent.length&&!e.textContent.match(/^\r?\n$/)||(e.textContent="​")}),t=n.innerHTML}.bind(this);var t=this.clean.onSync;this.clean.onSync=function(e){var n=elCreate("div");n.innerHTML=e;var r={};return elBySelAll("pre",n,function(e){var t=WCF.getUUID();r[t]=e.textContent,e.textContent=t}),elBySelAll("p",n,function(e){var t=e.lastElementChild;if(t&&"BR"===t.nodeName)if(t.nextSibling){if(t.nextSibling.textContent.replace(/[\r\n\t]/g,"").match(/^\u200B+$/)){var n=elCreate("p");n.innerHTML="<br>",e.parentNode.insertBefore(n,e.nextSibling),e.removeChild(t.nextSibling),e.removeChild(t)}}else(t.previousElementSibling||t.previousSibling&&""!==t.previousSibling.textContent.replace(/\u200B/g,"").trim())&&e.removeChild(t)}),elBySelAll("span",n,function(e){if(e.childNodes.length>0){var t=e.childNodes[e.childNodes.length-1];t.nodeType===Node.TEXT_NODE&&t.textContent.match(/\n$/)&&(t.textContent=t.textContent.replace(/\n+$/,e.parentNode.lastChild===e?"":" "))}}),e=n.innerHTML,e=e.replace(/<p>\u200B<\/p>/g,"<p><br></p>"),e=e.replace(/&amp;/g,"&amp;WCF_AMPERSAND&amp;"),e=t.call(this,e),e=e.replace(/&WCF_AMPERSAND&/g,"&amp;"),n.innerHTML=e,elBySelAll("pre",n,function(e){r.hasOwnProperty(e.textContent)&&(e.textContent=r[e.textContent])}),e=n.innerHTML}.bind(this);var n=this.clean.savePreFormatting;this.clean.savePreFormatting=function(e){var t=this.clean.encodeEntities;return this.clean.encodeEntities=function(e){return WCF.String.escapeHTML(e)},e=n.call(this,e),this.clean.encodeEntities=t,e}.bind(this);var r=this.clean.onPaste;this.clean.onPaste=function(e,t,n){if(t.pre||this.utils.isCurrentOrParent("kbd"))return t.pre&&this.opts.preSpaces&&(e=e.replace(/\t/g,new Array(this.opts.preSpaces+1).join(" "))),WCF.String.escapeHTML(e);this.clean.isHtmlMsWord(e)&&(e=this.clean.cleanMsWord(e));var l=elCreate("div");l.innerHTML=e.replace(/@@@WOLTLAB-P-ALIGN-(?:left|right|center|justify)@@@/g,"");var i,o,a,s=!0;for(o=0,a=l.childElementCount;o<a;o++){if(i=l.children[o],"DIV"!==i.nodeName||0===i.childNodes.length){s=!1;break}if(1===i.childNodes.length&&1===i.childElementCount){var c=i.children[0];if(0===c.childNodes.length&&"BR"!==c.nodeName){s=!1;break}}}if(s){var h=[];for(o=0,a=l.childElementCount;o<a;o++)h.push(l.children[o]);h.forEach(function(e){var t=elCreate("p");for(l.insertBefore(t,e);e.childNodes.length>0;)t.appendChild(e.childNodes[0]);l.removeChild(e)})}var d,p,f,u,m=null!==elBySel(".MsoNormal",l),g=elBySelAll("[style]",l);for(o=0,a=g.length;o<a;o++){i=g[o],p=[];for(var v=0,y=i.style.length;v<y;v++)if(d=i.style[v],-1===this.opts.woltlab.allowedInlineStyles.indexOf(d)){if("font-weight"===d&&"STRONG"!==i.nodeName)u=i.style.getPropertyValue(d),"bold"!==u&&"bolder"!==u||(u=600),(u=~~u)>500&&(f=elCreate("strong"),i.parentNode.insertBefore(f,i),f.appendChild(i));else if(m&&"margin-bottom"===d&&"P"===i.nodeName&&(u=i.style.getPropertyValue(d),u.match(/^12(?:\.0)?pt$/))){var b=elCreate("p");b.innerHTML="<br>",i.parentNode.insertBefore(b,i.nextSibling)}p.push(d)}p.forEach(function(e){i.style.removeProperty(e)})}return elBySelAll("span",l,function(e){if(!e.classList.contains("redactor-selection-marker"))if(e.hasAttribute("style")&&e.style.length)for(var t=e.style.getPropertyValue("color"),n=e.style.getPropertyValue("font-family"),r=e.style.getPropertyValue("font-size"),l=(t?1:0)+(n?1:0)+(r?1:0);l>1;){if(this.opts.pastePlainText)return e.style.removeProperty("color"),e.style.removeProperty("font-family"),void e.style.removeProperty("font-size");var i=elCreate("span");t?(i.style.setProperty("color",t,""),e.style.removeProperty("color"),t="",l--):n?(i.style.setProperty("font-family",n,""),e.style.removeProperty("font-family"),n="",l--):r&&(i.style.setProperty("font-size",r,""),e.style.removeProperty("font-size"),r="",l--),e.parentNode.insertBefore(i,e),i.appendChild(e)}else{for(;e.childNodes.length;)e.parentNode.insertBefore(e.childNodes[0],e);elRemove(e)}}.bind(this)),elBySelAll("p",l,function(e){e.classList.contains("MsoNormal")?1===e.childElementCount&&"O:P"===e.children[0].nodeName&&" "===e.textContent&&(e.innerHTML="<br>"):e.className.match(/\btext-(left|right|center|justify)\b/)&&e.insertBefore(document.createTextNode("@@@WOLTLAB-P-ALIGN-"+RegExp.$1+"@@@"),e.firstChild),e.removeAttribute("class"),e.removeAttribute("style")}),elBySelAll("img",l,function(e){e.removeAttribute("style")}),elBySelAll("br",l,function(e){e.parentNode.insertBefore(document.createTextNode("@@@WOLTLAB-BR-MARKER@@@"),e.nextSibling)}),elBySelAll("kbd",l,function(e){for(e.insertBefore(document.createTextNode("[tt]"),e.firstChild),e.appendChild(document.createTextNode("[/tt]"));e.childNodes.length;)e.parentNode.insertBefore(e.childNodes[0],e);elRemove(e)}),e=r.call(this,l.innerHTML,t,n),e=e.replace(/\n*@@@WOLTLAB-BR-MARKER@@@\n*/g,"<woltlab-br-marker></woltlab-br-marker>"),e=e.replace(/(<p>)?\s*@@@WOLTLAB-P-ALIGN-(left|right|center|justify)@@@/g,function(e,t,n){return t?'<p class="text-'+n+'">':""}),l.innerHTML=e.replace(/&amp;quot;/g,"&quot;"),elBySelAll("woltlab-br-marker",l,function(e){var t=e.parentNode;if(null!==t){if("P"===t.nodeName){var n=elCreate("p");n.innerHTML="<br>";var r=!1,l=e.nextSibling;l&&"WOLTLAB-BR-MARKER"===l.nodeName&&(r=!0);for(var i=!r;e.nextSibling;)i&&0!==e.nextSibling.textContent.replace(/\u200B/g,"").trim().length&&(i=!1),n.appendChild(e.nextSibling);i||elRemove(n.firstElementChild);var o=e.previousSibling;o&&"BR"===o.nodeName&&elRemove(o),t.parentNode.insertBefore(n,t.nextSibling),r&&(n=elCreate("p"),n.innerHTML="<br>",t.parentNode.insertBefore(n,t.nextSibling))}else t.insertBefore(elCreate("br"),e);elRemove(e)}}),elBySelAll("p",l,function(e){var t=!1;0===e.childNodes.length?t=!0:""===e.textContent?(t=!0,elBySelAll("*",e,function(e){"SPAN"!==e.nodeName&&(t=!1)})):0===e.textContent.trim().length&&(elBySelAll("span",e,function(e){if(!e.hasAttribute("style")||!e.style.length){for(;e.childNodes.length;)e.parentNode.insertBefore(e.childNodes[0],e);elRemove(e)}}),0===e.children.length&&(e.innerHTML="<br>")),t&&elRemove(e)}),l.innerHTML}.bind(this);var l=[],i=function(e,t){for(var n,r,i={},o=0,a=t.length;o<a;o++)n=t[o],r=elAttr(e,n),"style"===n&&0===e.style.length&&0===r.indexOf("font-family")&&(r=r.replace(/&quot;/g,"")),i[n]=r;l.push({element:e,attributes:i})},o=this.clean.convertTags;this.clean.convertTags=function(e,t){var n=elCreate("div");n.innerHTML=e,l=[],WCF.System.Event.fireEvent("com.woltlab.wcf.redactor2","convertTags_"+this.$element[0].id,{addToStorage:i,div:n}),elBySelAll("span",n,function(e){i(e,["style"])}),l.forEach(function(e,t){var n=e.element,r=n.parentNode;for(r.insertBefore(document.createTextNode("###custom"+t+"###"),n),r.insertBefore(document.createTextNode("###/custom"+t+"###"),n.nextSibling);n.childNodes.length;)r.insertBefore(n.childNodes[0],n);r.removeChild(n)});var r=!1;t.links&&this.opts.pasteLinks&&(elBySelAll("a",n,function(e){e.href&&(e.outerHTML='#@###[a href="'+e.href+'"]###@#'+e.innerHTML+"#@###[/a]###@#")}),r=!0,t.links=!1);var a=!1;return t.images&&this.opts.pasteImages&&(elBySelAll("img",n,function(e){if(e.src){for(var t,n='#####[img src="'+e.src+'"',r=0,l=e.attributes.length;r<l;r++)t=e.attributes.item(r),"src"!==t.name&&(n+=" "+t.name+'="'+t.value+'"');e.outerHTML=n+"]#####"}}),a=!0,t.images=!1),e=o.call(this,n.innerHTML,t),a&&(t.images=!0),r&&(t.links=!0),e}.bind(this);var a=this.clean.reconvertTags;this.clean.reconvertTags=function(e,t){if(l.length){e=e.replace(/###(\/?)custom(\d+)###/g,'<$1woltlab-custom-tag data-index="$2">');var n=elCreate("div");n.innerHTML=e,elBySelAll("woltlab-custom-tag",n,function(e){var t=~~elData(e,"index");if(l[t]){var n=l[t],r=elCreate(n.element.nodeName);for(var i in n.attributes)n.attributes.hasOwnProperty(i)&&elAttr(r,i,n.attributes[i]);for(e.parentNode.insertBefore(r,e);e.childNodes.length;)r.appendChild(e.childNodes[0])}elRemove(e)}),e=n.innerHTML}return(t.links&&this.opts.pasteLinks||t.images&&this.opts.pasteImages)&&(e=e.replace(new RegExp("#@###\\[","gi"),"<"),e=e.replace(new RegExp("\\]###@#","gi"),">")),a.call(this,e,t)}.bind(this),this.clean.removeSpans=function(e){return e};var s=this.clean.getCurrentType;this.clean.getCurrentType=function(e,t){var n=s.call(this,e,t);return this.utils.isCurrentOrParent(["kbd"])&&(n.inline=!1,n.block=!1,n.encode=!0,n.pre=!0,n.paragraphize=!1,n.images=!1,n.links=!1),n}.bind(this);this.clean.removeEmptyInlineTags;this.clean.removeEmptyInlineTags=function(e){var t=this.opts.inlineTags,n=$("<div/>").html($.parseHTML(e,document,!0)),r=this,l=n.find("span"),i=n.find(t.join(","));return i.filter(":not(span)").removeAttr("style"),i.each(function(){var e=$(this).html();0===this.attributes.length&&r.utils.isEmpty(e)&&$(this).replaceWith(function(){return $(this).contents()})}),l.each(function(){$(this).html();0===this.attributes.length&&$(this).replaceWith(function(){return $(this).contents()})}),e=n.html(),e=e.replace("\x3c!--?php","<?php"),e=e.replace("\x3c!--?","<?"),e=e.replace("?--\x3e","?>"),n.remove(),e}.bind(this)},removeRedundantStyles:function(){var e=[],t=["del","em","strong","sub","sup","u"];if(elBySelAll(t.join(","),this.$editor[0],function(t){elBySelAll(t.nodeName,t,function(t){e.push(t)})}),!this.opts.pastePlainText){elBySelAll("span[style]",this.$editor[0],function(t){["color","font-family","font-size"].forEach(function(n){var r=t.style.getPropertyValue(n);r&&window.getComputedStyle(t.parentNode).getPropertyValue(n)===r&&e.push(t)})});var n;e.forEach(function(e){for(n=e.parentNode;e.childNodes.length;)n.insertBefore(e.childNodes[0],e);n.removeChild(e)})}}}}; })(this);
index f586c004ce22802b10f05b006aeb0f5c606697ba..b7d91db394b2fe3b07373bd119dd58ffe4b558f0 100644 (file)
@@ -1833,7 +1833,9 @@ if (COMPILER_TARGET_DEFAULT) {
                                                UiPageAction.add(buttonName, button);
                                        }
                                        
-                                       button.textContent = WCF.Language.get('wcf.message.quote.showQuotes').replace(/#count#/, this._count);
+                                       button.textContent = WCF.Language.get('wcf.message.quote.showQuotes', {
+                                               count: this._count
+                                       });
                                        
                                        UiPageAction.show(buttonName);
                                }
index 975554177a9d91ae82b9eea62a2a51fb210d70bd..183305422dabf5ff0b25707ea4d063c2543823b4 100644 (file)
@@ -2165,6 +2165,7 @@ WCF.User.LikeLoader = Class.extend({
  * Loads user profile previews.
  * 
  * @see        WCF.Popover
+ * @deprecated since 5.3, taken care of by `WoltLabSuite/Core/BootstrapFrontend` via `WoltLabSuite/Core/Controller/Popover`
  */
 WCF.User.ProfilePreview = WCF.Popover.extend({
        /**
index 71fbb77898db91416eb3fe8096fc365e2ca5a818..47de858f879971c19850fcd76badbd39d5b03aee 100644 (file)
@@ -1,4 +1,4 @@
-define(['Ajax', 'Dictionary', 'Language', 'Ui/Dialog'], function (Ajax, Dictionary, Language, UiDialog) {
+define(['Ajax', 'Dictionary', 'Language', 'Ui/Dialog', 'Ui/Notification'], function (Ajax, Dictionary, Language, UiDialog, UiNotification) {
        "use strict";
        
        var _buttons = new Dictionary();
@@ -142,7 +142,8 @@ define(['Ajax', 'Dictionary', 'Language', 'Ui/Dialog'], function (Ajax, Dictiona
                        if (_queue.length === 0) {
                                _buttonSyncAll.classList.remove('disabled');
                                
-                               // TODO: do stuff
+                               UiNotification.show();
+                               
                                return;
                        }
                        
index 7b46625bdcdf93c668a5078bb2c4b81ec4c1f67e..0742b7b676cb5b1153c8cf0e4af14c74c2ac5c48 100644 (file)
@@ -58,21 +58,18 @@ define(
                 * Initializes user profile popover.
                 */
                _initUserPopover: function() {
+                       ControllerPopover.init({
+                               className: 'userLink',
+                               dboAction: 'wcf\\data\\user\\UserProfileAction',
+                               identifier: 'com.woltlab.wcf.user'
+                       });
+                       
+                       // @deprecated since 5.3
                        ControllerPopover.init({
                                attributeName: 'data-user-id',
                                className: 'userLink',
-                               identifier: 'com.woltlab.wcf.user',
-                               loadCallback: function(objectId, popover) {
-                                       var callback = function(data) {
-                                               popover.setContent('com.woltlab.wcf.user', objectId, data.returnValues.template);
-                                       };
-                                       
-                                       popover.ajaxApi({
-                                               actionName: 'getUserProfile',
-                                               className: 'wcf\\data\\user\\UserProfileAction',
-                                               objectIDs: [ objectId ]
-                                       }, callback, callback);
-                               }
+                               dboAction: 'wcf\\data\\user\\UserProfileAction',
+                               identifier: 'com.woltlab.wcf.user.deprecated'
                        });
                }
        };
index 82a4769de5e1aa7445c411a5a1b65d77e28e0b5d..4f56bbbd0e165ad69c04c0dddb759f84ade5f56a 100644 (file)
@@ -118,6 +118,7 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
                        
                        _handlers.set(options.identifier, {
                                attributeName: options.attributeName,
+                               dboAction: options.dboAction,
                                elements: options.legacy ? options.className : elByClass(options.className),
                                legacy: options.legacy,
                                loadCallback: options.loadCallback
@@ -336,7 +337,26 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
                        else if (data.state === STATE_NONE) {
                                data.state = STATE_LOADING;
                                
-                               _handlers.get(elementData.identifier).loadCallback(elementData.objectId, this);
+                               var handler = _handlers.get(elementData.identifier);
+                               if (handler.loadCallback) {
+                                       handler.loadCallback(elementData.objectId, this);
+                               }
+                               else if (handler.dboAction) {
+                                       var callback = function(data) {
+                                               this.setContent(
+                                                       elementData.identifier,
+                                                       elementData.objectId,
+                                                       data.returnValues.template
+                                               );
+                                       }.bind(this);
+                                       
+                                       this.ajaxApi({
+                                               actionName: 'getPopover',
+                                               className: handler.dboAction,
+                                               interfaceName: 'wcf\\data\\IPopoverAction',
+                                               objectIDs: [ elementData.objectId ]
+                                       }, callback, callback);
+                               }
                        }
                },
                
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/I18n/Plural.js b/wcfsetup/install/files/js/WoltLabSuite/Core/I18n/Plural.js
new file mode 100644 (file)
index 0000000..adf440f
--- /dev/null
@@ -0,0 +1,553 @@
+/**
+ * Generates plural phrases for the `plural` template plugin.
+ * 
+ * @author     Matthias Schmidt, Marcel Werk
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/I18n/Plural
+ */
+define(['StringUtil'], function(StringUtil) {
+       "use strict";
+       
+       var PLURAL_FEW = 'few';
+       var PLURAL_MANY = 'many';
+       var PLURAL_ONE = 'one';
+       var PLURAL_OTHER = 'other';
+       var PLURAL_TWO = 'two';
+       var PLURAL_ZERO = 'zero';
+       
+       return {
+               /**
+                * Returns the plural category for the given value.
+                *
+                * @param       {number}        value
+                * @param       {?string}       languageCode
+                * @return      string
+                */
+               getCategory: function(value, languageCode) {
+                       if (!languageCode) {
+                               languageCode = document.documentElement.lang;
+                       }
+                       
+                       // Fallback: handle unknown languages as English
+                       if (typeof this[languageCode] !== 'function') {
+                               languageCode = 'en';
+                       }
+                       
+                       var category = this[languageCode](value);
+                       if (category) {
+                               return category;
+                       }
+                       
+                       return PLURAL_OTHER;
+               },
+               
+               /**
+                * Returns the value for a `plural` element used in the template.
+                * 
+                * @param       {object}        parameters
+                * @see         wcf\system\template\plugin\PluralFunctionTemplatePlugin::execute()
+                */
+               getCategoryFromTemplateParameters: function(parameters) {
+                       if (!parameters['value'] ) {
+                               throw new Error('Missing parameter value');
+                       }
+                       if (!parameters['other']) {
+                               throw new Error('Missing parameter other');
+                       }
+                       
+                       var value = parameters['value'];
+                       if (Array.isArray(value)) {
+                               value = value.length;
+                       }
+                       
+                       // handle numeric attributes
+                       for (var key in parameters) {
+                               if (objOwns(parameters, key) && key == ~~key && key == value) {
+                                       return parameters[key];
+                               }
+                       }
+                       
+                       var category = this.getCategory(value);
+                       if (!parameters[category]) {
+                               category = PLURAL_OTHER;
+                       }
+                       
+                       var string = parameters[category];
+                       if (string.indexOf('#') !== -1) {
+                               return string.replace('#', StringUtil.formatNumeric(value));
+                       }
+                       
+                       return string;
+               },
+               
+               /**
+                * `f` is the fractional number as a whole number (1.234 yields 234)
+                * 
+                * @param       {number}        n
+                * @return      {integer}
+                */
+               getF: function(n) {
+                       n = n.toString();
+                       var pos = n.indexOf('.');
+                       if (pos === -1) {
+                               return 0;
+                       }
+                       
+                       return parseInt(n.substr(pos + 1), 10);
+               },
+               
+               /**
+                * `v` represents the number of digits of the fractional part (1.234 yields 3)
+                * 
+                * @param       {number}        n
+                * @return      {integer}
+                */
+               getV: function(n) {
+                       return n.toString().replace(/^[^.]*\.?/, '').length;
+               },
+               
+               // Afrikaans
+               af: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Amharic
+               am: function(n) {
+                       var i = Math.floor(Math.abs(n));
+                       if (n == 1 || i === 0) return PLURAL_ONE;
+               },
+               
+               // Arabic
+               ar: function(n) {
+                       if (n == 0) return PLURAL_ZERO;
+                       if (n == 1) return PLURAL_ONE;
+                       if (n == 2) return PLURAL_TWO;
+                       
+                       var mod100 = n % 100;
+                       if (mod100 >= 3 && mod100 <= 10) return PLURAL_FEW;
+                       if (mod100 >= 11 && mod100 <= 99) return PLURAL_MANY;
+               },
+               
+               // Assamese
+               as: function(n) {
+                       var i = Math.floor(Math.abs(n));
+                       if (n == 1 || i === 0) return PLURAL_ONE;
+               },
+               
+               // Azerbaijani
+               az: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Belarusian
+               be: function(n) {
+                       var mod10 = n % 10;
+                       var mod100 = n % 100;
+                       
+                       if (mod10 == 1 && mod100 != 11) return PLURAL_ONE;
+                       if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) return PLURAL_FEW;
+                       if (mod10 == 0 || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 11 && mod100 <= 14)) return PLURAL_MANY;
+               },
+               
+               // Bulgarian
+               bg: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Bengali
+               bn: function(n) {
+                       var i = Math.floor(Math.abs(n));
+                       if (n == 1 || i === 0) return PLURAL_ONE;
+               },
+               
+               // Tibetan
+               bo: function(n) {},
+               
+               // Bosnian
+               bs: function(n) {
+                       var v = this.getV(n);
+                       var f = this.getF(n);
+                       var mod10 = n % 10;
+                       var mod100 = n % 100;
+                       var fMod10 = f % 10;
+                       var fMod100 = f % 100;
+                       
+                       if ((v == 0 && mod10 == 1 && mod100 != 11) || (fMod10 == 1 && fMod100 != 11)) return PLURAL_ONE;
+                       if ((v == 0 && mod10 >= 2 && mod10 <= 4 && mod100 >= 12 && mod100 <= 14)
+                               || (fMod10 >= 2 && fMod10 <= 4 && fMod100 >= 12 && fMod100 <= 14)) return PLURAL_FEW;
+               },
+               
+               // Czech
+               cs: function(n) {
+                       var v = this.getV(n);
+                       
+                       if (n == 1 && v === 0) return PLURAL_ONE;
+                       if (n >= 2 && n <= 4 && v === 0) return PLURAL_FEW;
+                       if (v === 0) return PLURAL_MANY;
+               },
+               
+               // Welsh
+               cy: function(n) {
+                       if (n == 0) return PLURAL_ZERO;
+                       if (n == 1) return PLURAL_ONE;
+                       if (n == 2) return PLURAL_TWO;
+                       if (n == 3) return PLURAL_FEW;
+                       if (n == 6) return PLURAL_MANY;
+               },
+               
+               // Danish
+               da: function(n) {
+                       if (n > 0 && n < 2) return PLURAL_ONE;
+               },
+               
+               // Greek
+               el: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Catalan (ca)
+               // German (de)
+               // English (en)
+               // Estonian (et)
+               // Finnish (fi)
+               // Italian (it)
+               // Dutch (nl)
+               // Swedish (sv)
+               // Swahili (sw)
+               // Urdu (ur)
+               en: function(n) {
+                       if (n == 1 && this.getV(n) === 0) return PLURAL_ONE;
+               },
+               
+               // Spanish
+               es: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Basque
+               eu: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Persian
+               fa: function(n) {
+                       if (n >= 0 && n <= 1) return PLURAL_ONE;
+               },
+               
+               // French
+               fr: function(n) {
+                       if (n >= 0 && n < 2) return PLURAL_ONE;
+               },
+               
+               // Irish
+               ga: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+                       if (n == 2) return PLURAL_TWO;
+                       if (n == 3 || n == 4 || n == 5 || n == 6) return PLURAL_FEW;
+                       if (n == 7 || n == 8 || n == 9 || n == 10) return PLURAL_MANY;
+               },
+               
+               // Gujarati
+               gu: function(n) {
+                       if (n >= 0 && n <= 1) return PLURAL_ONE;
+               },
+               
+               // Hebrew
+               he: function(n) {
+                       var v = this.getV(n);
+       
+                       if (n == 1 && v === 0) return PLURAL_ONE;
+                       if (n == 2 && v === 0) return PLURAL_TWO;
+                       if (n > 10 && v === 0 && n % 10 == 0) return PLURAL_MANY;
+               },
+               
+               // Hindi
+               hi: function(n) {
+                       if (n >= 0 && n <= 1) return PLURAL_ONE;
+               },
+               
+               // Croatian
+               hr: function(n) {
+                       // same as Bosnian
+                       return this.bs(n);
+               },
+               
+               // Hungarian
+               hu: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Armenian
+               hy: function(n) {
+                       if (n >= 0 && n < 2) return PLURAL_ONE;
+               },
+               
+               // Indonesian
+               id: function(n) {},
+               
+               // Icelandic
+               is: function(n) {
+                       var f = this.getF(n);
+                       
+                       if (f === 0 && n % 10 === 1 && !(n % 100 === 11) || !(f === 0)) return PLURAL_ONE;
+               },
+               
+               // Japanese
+               ja: function(n) {},
+               
+               // Javanese
+               jv: function(n) {},
+               
+               // Georgian
+               ka: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Kazakh
+               kk: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Khmer
+               km: function(n) {},
+               
+               // Kannada
+               kn: function(n) {
+                       if (n >= 0 && n <= 1) return PLURAL_ONE;
+               },
+               
+               // Korean
+               ko: function(n) {},
+               
+               // Kurdish
+               ku: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Kyrgyz
+               ky: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Luxembourgish
+               lb: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Lao
+               lo: function(n) {},
+               
+               // Lithuanian
+               lt: function(n) {
+                       var mod10 = n % 10;
+                       var mod100 = n % 100;
+                       
+                       if (mod10 == 1 && !(mod100 >= 11 && mod100 <= 19)) return PLURAL_ONE;
+                       if (mod10 >= 2 && mod10 <= 9 && !(mod100 >= 11 && mod100 <= 19)) return PLURAL_FEW;
+                       if (this.getF(n) != 0) return PLURAL_MANY;
+               },
+               
+               // Latvian
+               lv: function(n) {
+                       var mod10 = n % 10;
+                       var mod100 = n % 100;
+                       var v = this.getV(n);
+                       var f = this.getF(n);
+                       var fMod10 = f % 10;
+                       var fMod100 = f % 100;
+                       
+                       if (mod10 == 0 || (mod100 >= 11 && mod100 <= 19) || (v == 2 && fMod100 >= 11 && fMod100 <= 19)) return PLURAL_ZERO;
+                       if ((mod10 == 1 && mod100 != 11) || (v == 2 && fMod10 == 1 && fMod100 != 11) || (v != 2 && fMod10 == 1)) return PLURAL_ONE;
+               },
+               
+               // Macedonian
+               mk: function(n) {
+                       var v = this.getV(n);
+                       var f = this.getF(n);
+                       var mod10 = n % 10;
+                       var mod100 = n % 100;
+                       var fMod10 = f % 10;
+                       var fMod100 = f % 100;
+                       
+                       if ((v == 0 && mod10 == 1 && mod100 != 11) || (fMod10 == 1 && fMod100 != 11)) return PLURAL_ONE;
+               },
+               
+               // Malayalam
+               ml: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Mongolian 
+               mn: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Marathi 
+               mr: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Malay 
+               ms: function(n) {},
+               
+               // Maltese 
+               mt: function(n) {
+                       var mod100 = n % 100;
+                       
+                       if (n == 1) return PLURAL_ONE;
+                       if (n == 0 || (mod100 >= 2 && mod100 <= 10)) return PLURAL_FEW;
+                       if (mod100 >= 11 && mod100 <= 19) return PLURAL_MANY;
+               },
+               
+               // Burmese
+               my: function(n) {},
+               
+               // Norwegian
+               no: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Nepali
+               ne: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Odia
+               or: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Punjabi
+               pa: function(n) {
+                       if (n == 1 || n == 0) return PLURAL_ONE;
+               },
+               
+               // Polish
+               pl: function(n) {
+                       var v = this.getV(n);
+                       var mod10 = n % 10;
+                       var mod100 = n % 100;
+       
+                       if (n == 1 && v == 0) return PLURAL_ONE;
+                       if (v == 0 && mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) return PLURAL_FEW;
+                       if (v == 0 && ((n != 1 && mod10 >= 0 && mod10 <= 1) || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 12 && mod100 <= 14))) return PLURAL_MANY;
+               },
+               
+               // Pashto
+               ps: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Portuguese
+               pt: function(n) {
+                       if (n >= 0 && n < 2) return PLURAL_ONE;
+               },
+               
+               // Romanian
+               ro: function(n) {
+                       var v = this.getV(n);
+                       var mod100 = n % 100;
+                       
+                       if (n == 1 && v === 0) return PLURAL_ONE;
+                       if (v != 0 || n == 0 || (mod100 >= 2 && mod100 <= 19)) return PLURAL_FEW;
+               },
+               
+               // Russian
+               ru: function(n) {
+                       var mod10 = n % 10;
+                       var mod100 = n % 100;
+                       
+                       if (this.getV(n) == 0) {
+                               if (mod10 == 1 && mod100 != 11) return PLURAL_ONE;
+                               if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) return PLURAL_FEW;
+                               if (mod10 == 0 || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 11 && mod100 <= 14)) return PLURAL_MANY;
+                       }
+               },
+               
+               // Sindhi
+               sd: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Sinhala
+               si: function(n) {
+                       if (n == 0 || n == 1 || (Math.floor(n) == 0 && this.getF(n) == 1)) return PLURAL_ONE;
+               },
+               
+               // Slovak
+               sk: function(n) {
+                       // same as Czech
+                       return this.cs(n);
+               },
+               
+               // Slovenian
+               sl: function(n) {
+                       var v = this.getV(n);
+                       var mod100 = n % 100;
+                       
+                       if (v == 0 && mod100 == 1) return PLURAL_ONE;
+                       if (v == 0 && mod100 == 2) return PLURAL_TWO;
+                       if ((v == 0 && (mod100 == 3 || mod100 == 4)) || v != 0) return PLURAL_FEW;
+               },
+               
+               // Albanian
+               sq: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Serbian
+               sr: function(n) {
+                       // same as Bosnian
+                       return this.bs(n);
+               },
+               
+               // Tamil
+               ta: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Telugu
+               te: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Tajik
+               tg: function(n) {},
+               
+               // Thai
+               th: function(n) {},
+               
+               // Turkmen
+               tk: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Turkish
+               tr: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Uyghur
+               ug: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Ukrainian
+               uk: function(n) {
+                       // same as Russian
+                       return this.ru(n);
+               },
+               
+               // Uzbek
+               uz: function(n) {
+                       if (n == 1) return PLURAL_ONE;
+               },
+               
+               // Vietnamese
+               vi: function(n) {},
+               
+               // Chinese
+               zh: function(n) {}
+       };
+});
index accb016437b5718de83a425a3f183ac707871944..2087181326faf8c28d0c2a923ac91f0774ef1a7e 100644 (file)
@@ -20,6 +20,7 @@
 <command>\"([^"]|\\\.)*\" return 'T_QUOTED_STRING';
 <command>\'([^']|\\\.)*\' return 'T_QUOTED_STRING';
 <command>\$ return 'T_VARIABLE';
+<command>[0-9]+ { return 'T_DIGITS'; }
 <command>[_a-zA-Z][_a-zA-Z0-9]* { return 'T_VARIABLE_NAME'; }
 <command>"."    return '.';
 <command>"["    return '[';
@@ -40,6 +41,7 @@
 "{/lang}"   return '{/lang}';
 "{include " { this.begin('command'); return '{include'; }
 "{implode " { this.begin('command'); return '{implode'; }
+"{plural " { this.begin('command'); return '{plural'; }
 "{/implode}" return '{/implode}';
 "{foreach "  { this.begin('command'); return '{foreach'; }
 "{foreachelse}"  return '{foreachelse}';
@@ -122,6 +124,17 @@ COMMAND:
                + "}"
                + "return (looped ? result : " + ($5 || "''") + "); })()"
        }
+|      '{plural' PLURAL_PARAMETER_LIST '}' {
+               $$ = "I18nPlural.getCategoryFromTemplateParameters({"
+               var needsComma = false;
+               for (var key in $2) {
+                       if (objOwns($2, key)) {
+                               $$ += (needsComma ? ',' : '') + key + ': ' + $2[key];
+                               needsComma = true;
+                       }
+               }
+               $$ += "})";
+       }
 |      '{lang}' CHUNK_STAR '{/lang}' -> "Language.get(" + $2 + ", v)"
 |      '{' VARIABLE '}'  -> "StringUtil.escapeHTML(" + $2 + ")"
 |      '{#' VARIABLE '}' -> "StringUtil.formatNumeric(" + $2 + ")"
@@ -154,11 +167,18 @@ COMMAND_PARAMETER_LIST:
 |      T_VARIABLE_NAME '=' COMMAND_PARAMETER_VALUE { $$ = {}; $$[$1] = $3; }
 ;
 
-COMMAND_PARAMETER_VALUE: T_QUOTED_STRING | VARIABLE;
+COMMAND_PARAMETER_VALUE: T_QUOTED_STRING | T_DIGITS | VARIABLE;
 
 // COMMAND_PARAMETERS parses anything that is valid between a command name and the closing brace
 COMMAND_PARAMETERS: COMMAND_PARAMETER+ -> $1.join('')
 ;
-COMMAND_PARAMETER: T_ANY | T_WS | '=' | T_QUOTED_STRING | VARIABLE | T_VARIABLE_NAME
+COMMAND_PARAMETER: T_ANY | T_DIGITS | T_WS | '=' | T_QUOTED_STRING | VARIABLE | T_VARIABLE_NAME
 |      '(' COMMAND_PARAMETERS ')' -> $1 + ($2 || '') + $3
 ;
+
+PLURAL_PARAMETER_LIST:
+       T_PLURAL_PARAMETER_NAME '=' COMMAND_PARAMETER_VALUE T_WS PLURAL_PARAMETER_LIST { $$ = $5; $$[$1] = $3; }
+|      T_PLURAL_PARAMETER_NAME '=' COMMAND_PARAMETER_VALUE { $$ = {}; $$[$1] = $3; }
+;
+
+T_PLURAL_PARAMETER_NAME: T_DIGITS | T_VARIABLE_NAME;
index 5ccb6e73f204f08fbee18c1dbbed7b80a43b5acd..876e292cc83a6d28cd1ce8291ed5c70373936aff 100644 (file)
@@ -1,12 +1,12 @@
 
 
 define(function(require){
-var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[2,37],$V1=[5,9,11,12,13,18,19,21,22,23,25,26,27,28,30,31,32,33,35,37,39],$V2=[1,24],$V3=[1,25],$V4=[1,31],$V5=[1,29],$V6=[1,30],$V7=[1,26],$V8=[1,27],$V9=[1,33],$Va=[11,12,15,40,41,45,47,49,50,52],$Vb=[9,11,12,13,18,19,21,23,26,28,30,31,32,33,35,37],$Vc=[11,12,15,40,41,44,45,46,47,49,50,52],$Vd=[18,35,37],$Ve=[12,15];
-var parser = {trace: function trace() { },
+var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[2,44],$V1=[5,9,11,12,13,18,19,21,22,23,25,26,28,29,30,32,33,34,35,37,39,41],$V2=[1,25],$V3=[1,27],$V4=[1,33],$V5=[1,31],$V6=[1,32],$V7=[1,28],$V8=[1,29],$V9=[1,26],$Va=[1,35],$Vb=[1,41],$Vc=[1,40],$Vd=[11,12,15,42,43,47,49,51,52,54,55],$Ve=[9,11,12,13,18,19,21,23,26,28,30,32,33,34,35,37,39],$Vf=[11,12,15,42,43,46,47,48,49,51,52,54,55],$Vg=[1,64],$Vh=[1,65],$Vi=[18,37,39],$Vj=[12,15];
+var parser = {trace: function trace () { },
 yy: {},
-symbols_: {"error":2,"TEMPLATE":3,"CHUNK_STAR":4,"EOF":5,"CHUNK_STAR_repetition0":6,"CHUNK":7,"PLAIN_ANY":8,"T_LITERAL":9,"COMMAND":10,"T_ANY":11,"T_WS":12,"{if":13,"COMMAND_PARAMETERS":14,"}":15,"COMMAND_repetition0":16,"COMMAND_option0":17,"{/if}":18,"{include":19,"COMMAND_PARAMETER_LIST":20,"{implode":21,"{/implode}":22,"{foreach":23,"COMMAND_option1":24,"{/foreach}":25,"{lang}":26,"{/lang}":27,"{":28,"VARIABLE":29,"{#":30,"{@":31,"{ldelim}":32,"{rdelim}":33,"ELSE":34,"{else}":35,"ELSE_IF":36,"{elseif":37,"FOREACH_ELSE":38,"{foreachelse}":39,"T_VARIABLE":40,"T_VARIABLE_NAME":41,"VARIABLE_repetition0":42,"VARIABLE_SUFFIX":43,"[":44,"]":45,".":46,"(":47,"VARIABLE_SUFFIX_option0":48,")":49,"=":50,"COMMAND_PARAMETER_VALUE":51,"T_QUOTED_STRING":52,"COMMAND_PARAMETERS_repetition_plus0":53,"COMMAND_PARAMETER":54,"$accept":0,"$end":1},
-terminals_: {2:"error",5:"EOF",9:"T_LITERAL",11:"T_ANY",12:"T_WS",13:"{if",15:"}",18:"{/if}",19:"{include",21:"{implode",22:"{/implode}",23:"{foreach",25:"{/foreach}",26:"{lang}",27:"{/lang}",28:"{",30:"{#",31:"{@",32:"{ldelim}",33:"{rdelim}",35:"{else}",37:"{elseif",39:"{foreachelse}",40:"T_VARIABLE",41:"T_VARIABLE_NAME",44:"[",45:"]",46:".",47:"(",49:")",50:"=",52:"T_QUOTED_STRING"},
-productions_: [0,[3,2],[4,1],[7,1],[7,1],[7,1],[8,1],[8,1],[10,7],[10,3],[10,5],[10,6],[10,3],[10,3],[10,3],[10,3],[10,1],[10,1],[34,2],[36,4],[38,2],[29,3],[43,3],[43,2],[43,3],[20,5],[20,3],[51,1],[51,1],[14,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,1],[54,3],[6,0],[6,2],[16,0],[16,2],[17,0],[17,1],[24,0],[24,1],[42,0],[42,2],[48,0],[48,1],[53,1],[53,2]],
+symbols_: {"error":2,"TEMPLATE":3,"CHUNK_STAR":4,"EOF":5,"CHUNK_STAR_repetition0":6,"CHUNK":7,"PLAIN_ANY":8,"T_LITERAL":9,"COMMAND":10,"T_ANY":11,"T_WS":12,"{if":13,"COMMAND_PARAMETERS":14,"}":15,"COMMAND_repetition0":16,"COMMAND_option0":17,"{/if}":18,"{include":19,"COMMAND_PARAMETER_LIST":20,"{implode":21,"{/implode}":22,"{foreach":23,"COMMAND_option1":24,"{/foreach}":25,"{plural":26,"PLURAL_PARAMETER_LIST":27,"{lang}":28,"{/lang}":29,"{":30,"VARIABLE":31,"{#":32,"{@":33,"{ldelim}":34,"{rdelim}":35,"ELSE":36,"{else}":37,"ELSE_IF":38,"{elseif":39,"FOREACH_ELSE":40,"{foreachelse}":41,"T_VARIABLE":42,"T_VARIABLE_NAME":43,"VARIABLE_repetition0":44,"VARIABLE_SUFFIX":45,"[":46,"]":47,".":48,"(":49,"VARIABLE_SUFFIX_option0":50,")":51,"=":52,"COMMAND_PARAMETER_VALUE":53,"T_QUOTED_STRING":54,"T_DIGITS":55,"COMMAND_PARAMETERS_repetition_plus0":56,"COMMAND_PARAMETER":57,"T_PLURAL_PARAMETER_NAME":58,"$accept":0,"$end":1},
+terminals_: {2:"error",5:"EOF",9:"T_LITERAL",11:"T_ANY",12:"T_WS",13:"{if",15:"}",18:"{/if}",19:"{include",21:"{implode",22:"{/implode}",23:"{foreach",25:"{/foreach}",26:"{plural",28:"{lang}",29:"{/lang}",30:"{",32:"{#",33:"{@",34:"{ldelim}",35:"{rdelim}",37:"{else}",39:"{elseif",41:"{foreachelse}",42:"T_VARIABLE",43:"T_VARIABLE_NAME",46:"[",47:"]",48:".",49:"(",51:")",52:"=",54:"T_QUOTED_STRING",55:"T_DIGITS"},
+productions_: [0,[3,2],[4,1],[7,1],[7,1],[7,1],[8,1],[8,1],[10,7],[10,3],[10,5],[10,6],[10,3],[10,3],[10,3],[10,3],[10,3],[10,1],[10,1],[36,2],[38,4],[40,2],[31,3],[45,3],[45,2],[45,3],[20,5],[20,3],[53,1],[53,1],[53,1],[14,1],[57,1],[57,1],[57,1],[57,1],[57,1],[57,1],[57,1],[57,3],[27,5],[27,3],[58,1],[58,1],[6,0],[6,2],[16,0],[16,2],[17,0],[17,1],[24,0],[24,1],[44,0],[44,2],[50,0],[50,1],[56,1],[56,2]],
 performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
 /* this == yyval */
 
@@ -84,77 +84,86 @@ case 11:
        
 break;
 case 12:
-this.$ = "Language.get(" + $$[$0-1] + ", v)";
+
+               this.$ = "I18nPlural.getCategoryFromTemplateParameters({"
+               var needsComma = false;
+               for (var key in $$[$0-1]) {
+                       if (objOwns($$[$0-1], key)) {
+                               this.$ += (needsComma ? ',' : '') + key + ': ' + $$[$0-1][key];
+                               needsComma = true;
+                       }
+               }
+               this.$ += "})";
+       
 break;
 case 13:
-this.$ = "StringUtil.escapeHTML(" + $$[$0-1] + ")";
+this.$ = "Language.get(" + $$[$0-1] + ", v)";
 break;
 case 14:
-this.$ = "StringUtil.formatNumeric(" + $$[$0-1] + ")";
+this.$ = "StringUtil.escapeHTML(" + $$[$0-1] + ")";
 break;
 case 15:
-this.$ = $$[$0-1];
+this.$ = "StringUtil.formatNumeric(" + $$[$0-1] + ")";
 break;
 case 16:
-this.$ = "'{'";
+this.$ = $$[$0-1];
 break;
 case 17:
-this.$ = "'}'";
+this.$ = "'{'";
 break;
 case 18:
-this.$ = "else { return " + $$[$0] + "; }";
+this.$ = "'}'";
 break;
 case 19:
-this.$ = "else if (" + $$[$0-2] + ") { return " + $$[$0] + "; }";
+this.$ = "else { return " + $$[$0] + "; }";
 break;
 case 20:
-this.$ = $$[$0];
+this.$ = "else if (" + $$[$0-2] + ") { return " + $$[$0] + "; }";
 break;
 case 21:
-this.$ = "v['" + $$[$0-1] + "']" + $$[$0].join('');;
+this.$ = $$[$0];
 break;
 case 22:
-this.$ = $$[$0-2] + $$[$0-1] + $$[$0];
+this.$ = "v['" + $$[$0-1] + "']" + $$[$0].join('');;
 break;
 case 23:
+this.$ = $$[$0-2] + $$[$0-1] + $$[$0];
+break;
+case 24:
 this.$ = "['" + $$[$0] + "']";
 break;
-case 24: case 36:
+case 25: case 39:
 this.$ = $$[$0-2] + ($$[$0-1] || '') + $$[$0];
 break;
-case 25:
+case 26: case 40:
  this.$ = $$[$0]; this.$[$$[$0-4]] = $$[$0-2]; 
 break;
-case 26:
+case 27: case 41:
  this.$ = {}; this.$[$$[$0-2]] = $$[$0]; 
 break;
-case 29:
+case 31:
 this.$ = $$[$0].join('');
 break;
-case 37: case 39: case 45:
+case 44: case 46: case 52:
 this.$ = [];
 break;
-case 38: case 40: case 46: case 50:
+case 45: case 47: case 53: case 57:
 $$[$0-1].push($$[$0]);
 break;
-case 49:
+case 56:
 this.$ = [$$[$0]];
 break;
 }
 },
-table: [o([5,9,11,12,13,19,21,23,26,28,30,31,32,33],$V0,{3:1,4:2,6:3}),{1:[3]},{5:[1,4]},o([5,18,22,25,27,35,37,39],[2,2],{7:5,8:6,10:8,9:[1,7],11:[1,9],12:[1,10],13:[1,11],19:[1,12],21:[1,13],23:[1,14],26:[1,15],28:[1,16],30:[1,17],31:[1,18],32:[1,19],33:[1,20]}),{1:[2,1]},o($V1,[2,38]),o($V1,[2,3]),o($V1,[2,4]),o($V1,[2,5]),o($V1,[2,6]),o($V1,[2,7]),{11:$V2,12:$V3,14:21,29:28,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8,53:22,54:23},{20:32,41:$V9},{20:34,41:$V9},{20:35,41:$V9},o([9,11,12,13,19,21,23,26,27,28,30,31,32,33],$V0,{6:3,4:36}),{29:37,40:$V4},{29:38,40:$V4},{29:39,40:$V4},o($V1,[2,16]),o($V1,[2,17]),{15:[1,40]},o([15,45,49],[2,29],{29:28,54:41,11:$V2,12:$V3,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8}),o($Va,[2,49]),o($Va,[2,30]),o($Va,[2,31]),o($Va,[2,32]),o($Va,[2,33]),o($Va,[2,34]),o($Va,[2,35]),{11:$V2,12:$V3,14:42,29:28,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8,53:22,54:23},{41:[1,43]},{15:[1,44]},{50:[1,45]},{15:[1,46]},{15:[1,47]},{27:[1,48]},{15:[1,49]},{15:[1,50]},{15:[1,51]},o($Vb,$V0,{6:3,4:52}),o($Va,[2,50]),{49:[1,53]},o($Vc,[2,45],{42:54}),o($V1,[2,9]),{29:57,40:$V4,51:55,52:[1,56]},o([9,11,12,13,19,21,22,23,26,28,30,31,32,33],$V0,{6:3,4:58}),o([9,11,12,13,19,21,23,25,26,28,30,31,32,33,39],$V0,{6:3,4:59}),o($V1,[2,12]),o($V1,[2,13]),o($V1,[2,14]),o($V1,[2,15]),o($Vd,[2,39],{16:60}),o($Va,[2,36]),o([11,12,15,40,41,45,49,50,52],[2,21],{43:61,44:[1,62],46:[1,63],47:[1,64]}),{12:[1,65],15:[2,26]},o($Ve,[2,27]),o($Ve,[2,28]),{22:[1,66]},{24:67,25:[2,43],38:68,39:[1,69]},{17:70,18:[2,41],34:72,35:[1,74],36:71,37:[1,73]},o($Vc,[2,46]),{11:$V2,12:$V3,14:75,29:28,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8,53:22,54:23},{41:[1,76]},{11:$V2,12:$V3,14:78,29:28,40:$V4,41:$V5,47:$V6,48:77,49:[2,47],50:$V7,52:$V8,53:22,54:23},{20:79,41:$V9},o($V1,[2,10]),{25:[1,80]},{25:[2,44]},o([9,11,12,13,19,21,23,25,26,28,30,31,32,33],$V0,{6:3,4:81}),{18:[1,82]},o($Vd,[2,40]),{18:[2,42]},{11:$V2,12:$V3,14:83,29:28,40:$V4,41:$V5,47:$V6,50:$V7,52:$V8,53:22,54:23},o([9,11,12,13,18,19,21,23,26,28,30,31,32,33],$V0,{6:3,4:84}),{45:[1,85]},o($Vc,[2,23]),{49:[1,86]},{49:[2,48]},{15:[2,25]},o($V1,[2,11]),{25:[2,20]},o($V1,[2,8]),{15:[1,87]},{18:[2,18]},o($Vc,[2,22]),o($Vc,[2,24]),o($Vb,$V0,{6:3,4:88}),o($Vd,[2,19])],
-defaultActions: {4:[2,1],68:[2,44],72:[2,42],78:[2,48],79:[2,25],81:[2,20],84:[2,18]},
-parseError: function parseError(str, hash) {
+table: [o([5,9,11,12,13,19,21,23,26,28,30,32,33,34,35],$V0,{3:1,4:2,6:3}),{1:[3]},{5:[1,4]},o([5,18,22,25,29,37,39,41],[2,2],{7:5,8:6,10:8,9:[1,7],11:[1,9],12:[1,10],13:[1,11],19:[1,12],21:[1,13],23:[1,14],26:[1,15],28:[1,16],30:[1,17],32:[1,18],33:[1,19],34:[1,20],35:[1,21]}),{1:[2,1]},o($V1,[2,45]),o($V1,[2,3]),o($V1,[2,4]),o($V1,[2,5]),o($V1,[2,6]),o($V1,[2,7]),{11:$V2,12:$V3,14:22,31:30,42:$V4,43:$V5,49:$V6,52:$V7,54:$V8,55:$V9,56:23,57:24},{20:34,43:$Va},{20:36,43:$Va},{20:37,43:$Va},{27:38,43:$Vb,55:$Vc,58:39},o([9,11,12,13,19,21,23,26,28,29,30,32,33,34,35],$V0,{6:3,4:42}),{31:43,42:$V4},{31:44,42:$V4},{31:45,42:$V4},o($V1,[2,17]),o($V1,[2,18]),{15:[1,46]},o([15,47,51],[2,31],{31:30,57:47,11:$V2,12:$V3,42:$V4,43:$V5,49:$V6,52:$V7,54:$V8,55:$V9}),o($Vd,[2,56]),o($Vd,[2,32]),o($Vd,[2,33]),o($Vd,[2,34]),o($Vd,[2,35]),o($Vd,[2,36]),o($Vd,[2,37]),o($Vd,[2,38]),{11:$V2,12:$V3,14:48,31:30,42:$V4,43:$V5,49:$V6,52:$V7,54:$V8,55:$V9,56:23,57:24},{43:[1,49]},{15:[1,50]},{52:[1,51]},{15:[1,52]},{15:[1,53]},{15:[1,54]},{52:[1,55]},{52:[2,42]},{52:[2,43]},{29:[1,56]},{15:[1,57]},{15:[1,58]},{15:[1,59]},o($Ve,$V0,{6:3,4:60}),o($Vd,[2,57]),{51:[1,61]},o($Vf,[2,52],{44:62}),o($V1,[2,9]),{31:66,42:$V4,53:63,54:$Vg,55:$Vh},o([9,11,12,13,19,21,22,23,26,28,30,32,33,34,35],$V0,{6:3,4:67}),o([9,11,12,13,19,21,23,25,26,28,30,32,33,34,35,41],$V0,{6:3,4:68}),o($V1,[2,12]),{31:66,42:$V4,53:69,54:$Vg,55:$Vh},o($V1,[2,13]),o($V1,[2,14]),o($V1,[2,15]),o($V1,[2,16]),o($Vi,[2,46],{16:70}),o($Vd,[2,39]),o([11,12,15,42,43,47,51,52,54,55],[2,22],{45:71,46:[1,72],48:[1,73],49:[1,74]}),{12:[1,75],15:[2,27]},o($Vj,[2,28]),o($Vj,[2,29]),o($Vj,[2,30]),{22:[1,76]},{24:77,25:[2,50],40:78,41:[1,79]},{12:[1,80],15:[2,41]},{17:81,18:[2,48],36:83,37:[1,85],38:82,39:[1,84]},o($Vf,[2,53]),{11:$V2,12:$V3,14:86,31:30,42:$V4,43:$V5,49:$V6,52:$V7,54:$V8,55:$V9,56:23,57:24},{43:[1,87]},{11:$V2,12:$V3,14:89,31:30,42:$V4,43:$V5,49:$V6,50:88,51:[2,54],52:$V7,54:$V8,55:$V9,56:23,57:24},{20:90,43:$Va},o($V1,[2,10]),{25:[1,91]},{25:[2,51]},o([9,11,12,13,19,21,23,25,26,28,30,32,33,34,35],$V0,{6:3,4:92}),{27:93,43:$Vb,55:$Vc,58:39},{18:[1,94]},o($Vi,[2,47]),{18:[2,49]},{11:$V2,12:$V3,14:95,31:30,42:$V4,43:$V5,49:$V6,52:$V7,54:$V8,55:$V9,56:23,57:24},o([9,11,12,13,18,19,21,23,26,28,30,32,33,34,35],$V0,{6:3,4:96}),{47:[1,97]},o($Vf,[2,24]),{51:[1,98]},{51:[2,55]},{15:[2,26]},o($V1,[2,11]),{25:[2,21]},{15:[2,40]},o($V1,[2,8]),{15:[1,99]},{18:[2,19]},o($Vf,[2,23]),o($Vf,[2,25]),o($Ve,$V0,{6:3,4:100}),o($Vi,[2,20])],
+defaultActions: {4:[2,1],40:[2,42],41:[2,43],78:[2,51],83:[2,49],89:[2,55],90:[2,26],92:[2,21],93:[2,40],96:[2,19]},
+parseError: function parseError (str, hash) {
     if (hash.recoverable) {
         this.trace(str);
     } else {
-        function _parseError (msg, hash) {
-            this.message = msg;
-            this.hash = hash;
-        }
-        _parseError.prototype = Error;
-
-        throw new _parseError(str, hash);
+        var error = new Error(str);
+        error.hash = hash;
+        throw error;
     }
 },
 parse: function parse(input) {
@@ -437,7 +446,7 @@ showPosition:function () {
     },
 
 // test the lexed token: return FALSE when not a match, otherwise return token
-test_match:function (match, indexed_rule) {
+test_match:function(match, indexed_rule) {
         var token,
             lines,
             backup;
@@ -567,7 +576,7 @@ next:function () {
     },
 
 // return next match that has a token
-lex:function lex() {
+lex:function lex () {
         var r = this.next();
         if (r) {
             return r;
@@ -577,12 +586,12 @@ lex:function lex() {
     },
 
 // activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
-begin:function begin(condition) {
+begin:function begin (condition) {
         this.conditionStack.push(condition);
     },
 
 // pop the previously active lexer condition state off the condition stack
-popState:function popState() {
+popState:function popState () {
         var n = this.conditionStack.length - 1;
         if (n > 0) {
             return this.conditionStack.pop();
@@ -592,7 +601,7 @@ popState:function popState() {
     },
 
 // produce the lexer rule set which is active for the currently active lexer condition state
-_currentRules:function _currentRules() {
+_currentRules:function _currentRules () {
         if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
             return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
         } else {
@@ -601,7 +610,7 @@ _currentRules:function _currentRules() {
     },
 
 // return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
-topState:function topState(n) {
+topState:function topState (n) {
         n = this.conditionStack.length - 1 - Math.abs(n || 0);
         if (n >= 0) {
             return this.conditionStack[n];
@@ -611,7 +620,7 @@ topState:function topState(n) {
     },
 
 // alias for begin(condition)
-pushState:function pushState(condition) {
+pushState:function pushState (condition) {
         this.begin(condition);
     },
 
@@ -627,74 +636,78 @@ case 0:/* comment */
 break;
 case 1: yy_.yytext = yy_.yytext.substring(9, yy_.yytext.length - 10); return 9; 
 break;
-case 2:return 52;
+case 2:return 54;
 break;
-case 3:return 52;
+case 3:return 54;
 break;
-case 4:return 40;
+case 4:return 42;
 break;
-case 5: return 41
+case 5: return 55
 break;
-case 6:return 46;
+case 6: return 43; 
 break;
-case 7:return 44;
+case 7:return 48;
 break;
-case 8:return 45;
+case 8:return 46;
 break;
 case 9:return 47;
 break;
 case 10:return 49;
 break;
-case 11:return 50;
+case 11:return 51;
+break;
+case 12:return 52;
+break;
+case 13:return 34;
 break;
-case 12:return 32;
+case 14:return 35;
 break;
-case 13:return 33;
+case 15: this.begin('command'); return 32; 
 break;
-case 14: this.begin('command'); return 30
+case 16: this.begin('command'); return 33
 break;
-case 15: this.begin('command'); return 31
+case 17: this.begin('command'); return 13
 break;
-case 16: this.begin('command'); return 13
+case 18: this.begin('command'); return 39
 break;
-case 17: this.begin('command'); return 37
+case 19: this.begin('command'); return 39
 break;
-case 18: this.begin('command'); return 37; 
+case 20:return 37;
 break;
-case 19:return 35;
+case 21:return 18;
 break;
-case 20:return 18;
+case 22:return 28;
 break;
-case 21:return 26;
+case 23:return 29;
 break;
-case 22:return 27;
+case 24: this.begin('command'); return 19; 
 break;
-case 23: this.begin('command'); return 19
+case 25: this.begin('command'); return 21
 break;
-case 24: this.begin('command'); return 21
+case 26: this.begin('command'); return 26
 break;
-case 25:return 22;
+case 27:return 22;
 break;
-case 26: this.begin('command'); return 23; 
+case 28: this.begin('command'); return 23; 
 break;
-case 27:return 39;
+case 29:return 41;
 break;
-case 28:return 25;
+case 30:return 25;
 break;
-case 29: this.begin('command'); return 28
+case 31: this.begin('command'); return 30
 break;
-case 30: this.popState(); return 15;
+case 32: this.popState(); return 15;
 break;
-case 31:return 12;
+case 33:return 12;
 break;
-case 32:return 5;
+case 34:return 5;
 break;
-case 33:return 11;
+case 35:return 11;
 break;
 }
 },
-rules: [/^(?:\{\*[\s\S]*?\*\})/,/^(?:\{literal\}[\s\S]*?\{\/literal\})/,/^(?:"([^"]|\\\.)*")/,/^(?:'([^']|\\\.)*')/,/^(?:\$)/,/^(?:[_a-zA-Z][_a-zA-Z0-9]*)/,/^(?:\.)/,/^(?:\[)/,/^(?:\])/,/^(?:\()/,/^(?:\))/,/^(?:=)/,/^(?:\{ldelim\})/,/^(?:\{rdelim\})/,/^(?:\{#)/,/^(?:\{@)/,/^(?:\{if )/,/^(?:\{else if )/,/^(?:\{elseif )/,/^(?:\{else\})/,/^(?:\{\/if\})/,/^(?:\{lang\})/,/^(?:\{\/lang\})/,/^(?:\{include )/,/^(?:\{implode )/,/^(?:\{\/implode\})/,/^(?:\{foreach )/,/^(?:\{foreachelse\})/,/^(?:\{\/foreach\})/,/^(?:\{(?!\s))/,/^(?:\})/,/^(?:\s+)/,/^(?:$)/,/^(?:[^{])/],
-conditions: {"command":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33],"inclusive":true},"INITIAL":{"rules":[0,1,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,31,32,33],"inclusive":true}}
+rules: [/^(?:\{\*[\s\S]*?\*\})/,/^(?:\{literal\}[\s\S]*?\{\/literal\})/,/^(?:"([^"]|\\\.)*")/,/^(?:'([^']|\\\.)*')/,/^(?:\$)/,/^(?:[0-9]+)/,/^(?:[_a-zA-Z][_a-zA-Z0-9]*)/,/^(?:\.)/,/^(?:\[)/,/^(?:\])/,/^(?:\()/,/^(?:\))/,/^(?:=)/,/^(?:\{ldelim\})/,/^(?:\{rdelim\})/,/^(?:\{#)/,/^(?:\{@)/,/^(?:\{if )/,/^(?:\{else if )/,/^(?:\{elseif )/,/^(?:\{else\})/,/^(?:\{\/if\})/,/^(?:\{lang\})/,/^(?:\{\/lang\})/,/^(?:\{include )/,/^(?:\{implode )/,/^(?:\{plural )/,/^(?:\{\/implode\})/,/^(?:\{foreach )/,/^(?:\{foreachelse\})/,/^(?:\{\/foreach\})/,/^(?:\{(?!\s))/,/^(?:\})/,/^(?:\s+)/,/^(?:$)/,/^(?:[^{])/],
+conditions: {"command":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35],"inclusive":true},"INITIAL":{"rules":[0,1,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,33,34,35],"inclusive":true}}
 });
 return lexer;
 })();
index cefa552da9b128adc51159b152789d7d7bf99f7d..1efce33b5ec94a61e96785bf23522d7b5b003efb 100644 (file)
@@ -9,7 +9,7 @@
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Template
  */
-define(['./Template.grammar', './StringUtil', 'Language'], function(parser, StringUtil, Language) {
+define(['./Template.grammar', './StringUtil', 'Language', 'WoltLabSuite/Core/I18n/Plural'], function(parser, StringUtil, Language, I18nPlural) {
        "use strict";
        
        // work around bug in AMD module generation of Jison
@@ -39,7 +39,7 @@ define(['./Template.grammar', './StringUtil', 'Language'], function(parser, Stri
                        + "v.__wcf = window.WCF; v.__window = window;\n"
                        + "return " + template;
                        
-                       this.fetch = new Function("StringUtil", "Language", "v", template).bind(undefined, StringUtil, Language);
+                       this.fetch = new Function("StringUtil", "Language", "I18nPlural", "v", template).bind(undefined, StringUtil, Language, I18nPlural);
                }
                catch (e) {
                        console.debug(e.message);
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/TwitterEmbed.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Message/TwitterEmbed.js
new file mode 100644 (file)
index 0000000..3c45916
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * Wrapper around Twitter's createTweet API.
+ *
+ * @author     Tim Duesterhus
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Ui/Message/TwitterEmbed
+ */
+define(['https://platform.twitter.com/widgets.js'], function(Widgets) {
+       "use strict";
+       
+       var twitterReady = new Promise(function(resolve, reject) {
+               twttr.ready(resolve);
+       });
+       
+       /**
+        * @exports     WoltLabSuite/Core/Ui/Message/TwitterEmbed
+        */
+       return {
+               /**
+                * Embed the tweet identified by the given tweetId into the given container.
+                * 
+                * @param {HTMLElement} container
+                * @param {string} tweetId
+                * @param {boolean} removeChildren Whether to remove existing children of the given container after embedding the tweet.
+                * @return {HTMLElement} The Tweet element created by Twitter.
+                */
+               embedTweet: function(container, tweetId, removeChildren) {
+                       if (removeChildren === undefined) removeChildren = false;
+                       
+                       return twitterReady.then(function() {
+                               return twttr.widgets.createTweet(tweetId, container, {
+                                       dnt: true,
+                                       lang: document.documentElement.lang,
+                               });
+                       }).then(function(tweet) {
+                               if (tweet && removeChildren) {
+                                       while (container.lastChild) {
+                                               container.removeChild(container.lastChild);
+                                       }
+                                       container.appendChild(tweet);
+                               }
+                               
+                               return tweet;
+                       });
+               },
+               
+               /**
+                * Embeds tweets into all elements with a data-wsc-twitter-tweet attribute, removing
+                * existing children.
+                */
+               embedAll: function() {
+                       elBySelAll("[data-wsc-twitter-tweet]", undefined, function(container) {
+                               var tweetId = elData(container, "wsc-twitter-tweet");
+                               if (tweetId) {
+                                       this.embedTweet(container, tweetId, true);
+                                       elData(container, "wsc-twitter-tweet", "");
+                               }
+                       }.bind(this))
+               }
+       };
+});
index 0f8e20924b3c8ac373159584ef509417cf82b060..15ffa0d0494b1e106f329098bd7730355fd15f66 100644 (file)
@@ -5,7 +5,6 @@ use wcf\data\style\Style;
 use wcf\data\style\StyleAction;
 use wcf\data\style\StyleEditor;
 use wcf\data\template\group\TemplateGroup;
-use wcf\data\template\group\TemplateGroupList;
 use wcf\form\AbstractForm;
 use wcf\system\event\EventHandler;
 use wcf\system\exception\SystemException;
@@ -213,11 +212,7 @@ class StyleAddForm extends AbstractForm {
                        $this->readStyleVariables();
                }
                
-               $templateGroupList = new TemplateGroupList();
-               $templateGroupList->sqlOrderBy = "templateGroupName";
-               $templateGroupList->getConditionBuilder()->add('templateGroupFolderName <> ?', ['_wcf_email/']);
-               $templateGroupList->readObjects();
-               $this->availableTemplateGroups = $templateGroupList->getObjects();
+               $this->availableTemplateGroups = TemplateGroup::getSelectList([-1], 1);
                
                if (isset($_REQUEST['tmpHash'])) {
                        $this->tmpHash = StringUtil::trim($_REQUEST['tmpHash']);
index ec7b904ae84a5202060001a867fcd5fe7362a645..c1c5045b12dfa1a5998930216466899abba53adb 100755 (executable)
@@ -365,7 +365,12 @@ class UserEditForm extends UserAddForm {
                        'languageIDs' => $this->visibleLanguages,
                        'options' => $saveOptions
                ];
-
+               // handle changed username
+               if (mb_strtolower($this->username) != mb_strtolower($this->user->username)) {
+                       $data['data']['lastUsernameChange'] = TIME_NOW;
+                       $data['data']['oldUsername'] = $this->user->username;
+               }
+               
                // handle ban
                if (WCF::getSession()->getPermission('admin.user.canBanUser')) {
                        $data['data']['banned'] = $this->banned;
index 69a4f5a8bc381cf307936aeba361d70351ce1dfe..59a2f1e475b62f7802868d29c4d0b177e87c78d1 100644 (file)
@@ -14,7 +14,6 @@ use wcf\system\exception\SystemException;
 use wcf\system\exception\UserInputException;
 use wcf\system\option\user\group\IUserGroupGroupOptionType;
 use wcf\system\option\user\group\IUserGroupOptionType;
-use wcf\system\option\user\group\UserGroupOptionHandler;
 use wcf\system\WCF;
 use wcf\system\WCFACP;
 
@@ -22,7 +21,7 @@ use wcf\system\WCFACP;
  * Shows the user group option form to edit a single option.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
+ * @copyright  2001-2020 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Form
  */
index daa34c18964b7e9118f28559e126566a92869b2e..9f43ca65a73ffeb93b8f34b1269a8ae7b1196b57 100755 (executable)
@@ -7,13 +7,12 @@ use wcf\system\io\RemoteFile;
 use wcf\system\package\PackageInstallationDispatcher;
 use wcf\system\request\LinkHandler;
 use wcf\system\WCF;
-use wcf\util\StringUtil;
 
 /**
  * Shows the welcome page in admin control panel.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
+ * @copyright  2001-2020 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Page
  */
index 819327399331629d1098d3588b1f3f960997b821..1087cc8e7a97a8527cc5509a6701e7b2771025fe 100644 (file)
@@ -1,7 +1,13 @@
 <?php
 namespace wcf\acp\page;
 use wcf\data\label\group\LabelGroupList;
+use wcf\data\language\item\LanguageItemList;
 use wcf\page\SortablePage;
+use wcf\system\language\LanguageFactory;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\HeaderUtil;
+use wcf\util\StringUtil;
 
 /**
  * Lists available label groups.
@@ -27,7 +33,12 @@ class LabelGroupListPage extends SortablePage {
        /**
         * @inheritDoc
         */
-       public $validSortFields = ['groupID', 'groupName', 'groupDescription', 'showOrder'];
+       public $itemsPerPage = 50;
+       
+       /**
+        * @inheritDoc
+        */
+       public $validSortFields = ['groupID', 'groupName', 'groupDescription', 'showOrder', 'labels'];
        
        /**
         * @inheritDoc
@@ -38,4 +49,81 @@ class LabelGroupListPage extends SortablePage {
         * @inheritDoc
         */
        public $objectListClassName = LabelGroupList::class;
+       
+       /**
+        * @var string
+        */
+       public $groupName = '';
+       
+       /**
+        * @var string
+        */
+       public $groupDescription = '';
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (!empty($_POST)) {
+                       $parameters = [];
+                       if (!empty($_POST['groupName'])) $parameters['groupName'] = StringUtil::trim($_POST['groupName']);
+                       if (!empty($_POST['groupDescription'])) $parameters['groupDescription'] = StringUtil::trim($_POST['groupDescription']);
+                       
+                       if (!empty($parameters)) {
+                               HeaderUtil::redirect(LinkHandler::getInstance()->getLink('LabelGroupList', $parameters));
+                               exit;
+                       }
+               }
+               
+               if (isset($_REQUEST['groupName'])) $this->groupName = StringUtil::trim($_REQUEST['groupName']);
+               if (isset($_REQUEST['groupDescription'])) $this->groupDescription = StringUtil::trim($_REQUEST['groupDescription']);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected function initObjectList() {
+               parent::initObjectList();
+               
+               $this->objectList->sqlSelects .= '(SELECT COUNT(*) FROM wcf'.WCF_N.'_label WHERE groupID = label_group.groupID) AS labels';
+               
+               if ($this->groupName) {
+                       $languageItemList = new LanguageItemList();
+                       $languageItemList->getConditionBuilder()->add('languageCategoryID = ?', [LanguageFactory::getInstance()->getCategory('wcf.acp.label')->languageCategoryID]);
+                       $languageItemList->getConditionBuilder()->add('languageID = ?', [WCF::getLanguage()->languageID]);
+                       $languageItemList->getConditionBuilder()->add('languageItem LIKE ?', ['wcf.acp.label.group%']);
+                       $languageItemList->getConditionBuilder()->add('languageItemValue LIKE ?', ['%'.addcslashes($this->groupName, '_%').'%']);
+                       $languageItemList->readObjects();
+                       
+                       $labelIDs = [];
+                       foreach ($languageItemList as $languageItem) {
+                               $labelIDs[] = str_replace('wcf.acp.label.group', '', $languageItem->languageItem);
+                       }
+                       
+                       if (!empty($labelIDs)) {
+                               $this->objectList->getConditionBuilder()->add('(groupName LIKE ? OR groupID IN (?))', ['%'.addcslashes($this->groupName, '_%').'%', $labelIDs]);
+                       }
+                       else {
+                               $this->objectList->getConditionBuilder()->add('groupName LIKE ?', ['%'.addcslashes($this->groupName, '_%').'%']);
+                       }
+               }
+               
+               if ($this->groupDescription) {
+                       $this->objectList->getConditionBuilder()->add('label_group.groupDescription LIKE ?', ['%'.addcslashes($this->groupDescription, '_%').'%']);
+               }
+       }
+               
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'groupName' => $this->groupName,
+                       'groupDescription' => $this->groupDescription,
+               ]);
+       }
 }
index f6843a8df62e7674f990d238613ecadb0c07c292..7f23616d031f9303b349fe936fb64be9dd5c84b8 100644 (file)
@@ -33,6 +33,11 @@ class LabelListPage extends SortablePage {
         */
        public $defaultSortField = 'label';
        
+       /**
+        * @inheritDoc
+        */
+       public $itemsPerPage = 50;
+       
        /**
         * @inheritDoc
         */
index a25ce693bc063e31140d978af2dcfcda2739eed5..6e53aacb03669a823c0422fda7d25fe752e61823 100644 (file)
@@ -127,12 +127,12 @@ namespace wcf\functions\exception {
                        return str_replace("\n", ' ', $item);
                };
                
-               // don't forget to update ExceptionLogViewPage, when changing the log file format
+               // don't forget to update ExceptionLogUtil / ExceptionLogViewPage, when changing the log file format
                $message = gmdate('r', TIME_NOW)."\n".
                        'Message: '.$stripNewlines($e->getMessage())."\n".
                        'PHP version: '.phpversion()."\n".
                        'WoltLab Suite version: '.WCF_VERSION."\n".
-                       'Request URI: '.$stripNewlines($_SERVER['REQUEST_URI'] ?? '').(\wcf\getRequestId() ? ' ('.\wcf\getRequestId().')' : '')."\n".
+                       'Request URI: '.$stripNewlines(($_SERVER['REQUEST_METHOD'] ?? '').' '.($_SERVER['REQUEST_URI'] ?? '')).(\wcf\getRequestId() ? ' ('.\wcf\getRequestId().')' : '')."\n".
                        'Referrer: '.$stripNewlines($_SERVER['HTTP_REFERER'] ?? '')."\n".
                        'User Agent: '.$stripNewlines($_SERVER['HTTP_USER_AGENT'] ?? '')."\n".
                        'Peak Memory Usage: '.memory_get_peak_usage().'/'.FileUtil::getMemoryLimit()."\n";
@@ -457,7 +457,7 @@ EXPLANATION;
                                                        </li>
                                                        <li class="exceptionSystemInformation2">
                                                                <p class="exceptionFieldTitle">Request URI<span class="exceptionColon">:</span></p>
-                                                               <p class="exceptionFieldValue"><?php if (isset($_SERVER['REQUEST_URI'])) echo StringUtil::encodeHTML($_SERVER['REQUEST_URI']); ?></p>
+                                                               <p class="exceptionFieldValue"><?php if (isset($_SERVER['REQUEST_METHOD'])) echo StringUtil::encodeHTML($_SERVER['REQUEST_METHOD']); ?> <?php if (isset($_SERVER['REQUEST_URI'])) echo StringUtil::encodeHTML($_SERVER['REQUEST_URI']); ?></p>
                                                        </li>
                                                        <li class="exceptionSystemInformation4">
                                                                <p class="exceptionFieldTitle">Referrer<span class="exceptionColon">:</span></p>
index ab629363c2d73d0ac6d7e8d7421bda3622725ca2..510268aed87863d4a7437ed7ae3265a3454c7b6d 100644 (file)
@@ -1,6 +1,5 @@
 <?php
 namespace wcf\data;
-use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\event\EventHandler;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\SystemException;
@@ -15,7 +14,7 @@ use wcf\util\StringUtil;
  * Default implementation for DatabaseObject-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
+ * @copyright  2001-2020 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data
  */
index 3078a0caf20f1e98a27b9cded6056374e3f9e332..a2a297fd2721af14de1a40f01237b7dfd352b1f2 100644 (file)
@@ -359,4 +359,22 @@ abstract class DatabaseObjectList implements \Countable, ITraversableObject {
                        return null;
                }
        }
+       
+       /**
+        * Returns the only object in this list or `null` if the list is empty.
+        * 
+        * @return      DatabaseObject|null
+        * @throws      \BadMethodCallException         if list contains more than one object
+        */
+       public function getSingleObject() {
+               if (count($this->objects) > 1) {
+                       throw new \BadMethodCallException("Cannot get a single object when the list contains " . count($this->objects) . " objects.");
+               }
+               
+               if (empty($this->objects)) {
+                       return null;
+               }
+               
+               return reset($this->objects);
+       }
 }
diff --git a/wcfsetup/install/files/lib/data/IPopoverAction.class.php b/wcfsetup/install/files/lib/data/IPopoverAction.class.php
new file mode 100644 (file)
index 0000000..2992e0b
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+namespace wcf\data;
+
+/**
+ * Every object action that provides popover previews for database objects has to implement this interface.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data
+ * @since      5.3
+ */
+interface IPopoverAction {
+       /**
+        * Validates the `getPopover` action.
+        */
+       public function validateGetPopover();
+       
+       /**
+        * Returns the requested popover for a specific object.
+        * 
+        * Return value:
+        *      [
+        *              'template' => '...'
+        *      ]
+        * 
+        * @return      string[]
+        */
+       public function getPopover();
+}
diff --git a/wcfsetup/install/files/lib/data/IPopoverObject.class.php b/wcfsetup/install/files/lib/data/IPopoverObject.class.php
new file mode 100644 (file)
index 0000000..b3404a8
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\data;
+
+/**
+ * Database objects that support links with preview popovers via `WoltLabSuite/Core/Controller/Popover`
+ * can implement this interface so that when the `anchor` template plugin is used and the CSS class name
+ * returned by `getPopoverLinkClass()` is given, the `data-object-id` attribute is automatically added
+ * to support the preview popover.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data
+ * @since      5.3
+ */
+interface IPopoverObject extends IIDObject, ITitledLinkObject {
+       /**
+        * Returns the CSS class that objects of this type use for popover links.
+        * 
+        * @return      string
+        */
+       public function getPopoverLinkClass();
+}
index 1661cc337ba76d688ba1efc4c8797bce51ba5318..c0be84f5130afb808cfeaee718c815848f8e2903 100644 (file)
@@ -1,6 +1,11 @@
 <?php
 namespace wcf\data\article;
+use wcf\data\like\Like;
 use wcf\data\like\object\AbstractLikeObject;
+use wcf\data\reaction\object\IReactionObject;
+use wcf\system\user\notification\object\LikeUserNotificationObject;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\WCF;
 
 /**
  * Likeable object implementation for cms articles.
@@ -14,7 +19,7 @@ use wcf\data\like\object\AbstractLikeObject;
  * @method     Article getDecoratedObject()
  * @mixin      Article
  */
-class LikeableArticle extends AbstractLikeObject {
+class LikeableArticle extends AbstractLikeObject implements IReactionObject {
        /**
         * @inheritDoc
         */
@@ -65,4 +70,20 @@ class LikeableArticle extends AbstractLikeObject {
        public function getLanguageID() {
                return null;
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public function sendNotification(Like $like) {
+               if ($this->getDecoratedObject()->userID != WCF::getUser()->userID) {
+                       $notificationObject = new LikeUserNotificationObject($like);
+                       UserNotificationHandler::getInstance()->fireEvent(
+                               'like',
+                               'com.woltlab.wcf.likeableArticle.notification',
+                               $notificationObject,
+                               [$this->getDecoratedObject()->userID],
+                               ['objectID' => $this->getDecoratedObject()->entryID]
+                       );
+               }
+       }
 }
index 88e3bdfafb7b5b49ac631078ba737874edeb98e5..4eea05fccf490bc3a3dc2cc2d3e71fafa36f7cb8 100644 (file)
@@ -71,7 +71,9 @@ class LikeableArticleProvider extends AbstractObjectTypeProvider implements ILik
                                // short output
                                $text = WCF::getLanguage()->getDynamicVariable('wcf.like.title.com.woltlab.wcf.likeableArticle', [
                                        'article' => $article,
-                                       'like' => $like
+                                       'reaction' => $like,
+                                       // @deprecated 5.3 Use `$reaction` instead
+                                       'like' => $like,
                                ]);
                                $like->setTitle($text);
                                
index 448bf072d830d3e14266bb565fa1bddc08cc098a..1984151c1ca9fab8464dedd3c1c8ab49452bb1da 100644 (file)
@@ -84,9 +84,8 @@ class ViewableArticle extends DatabaseObjectDecorator {
                $list->enableContentLoading($enableContentLoading);
                $list->setObjectIDs([$articleID]);
                $list->readObjects();
-               $objects = $list->getObjects();
-               if (isset($objects[$articleID])) return $objects[$articleID];
-               return null;
+               
+               return $list->getSingleObject();
        }
        
        /**
index f7bae162cb77a8f1d1d2e3bf943dcbc33eee7b68..0cf22fb7ad602eaabb9a03b1802840567be47bac 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\data\attachment;
+use wcf\data\ILinkableObject;
 use wcf\data\object\type\ObjectTypeCache;
 use wcf\data\DatabaseObject;
 use wcf\data\IThumbnailFile;
@@ -42,7 +43,7 @@ use wcf\util\StringUtil;
  * @property-read      integer         $uploadTime             timestamp at which the attachment has been uploaded
  * @property-read      integer         $showOrder              position of the attachment in relation to the other attachment to the same message
  */
-class Attachment extends DatabaseObject implements IRouteController, IThumbnailFile {
+class Attachment extends DatabaseObject implements ILinkableObject, IRouteController, IThumbnailFile {
        /**
         * indicates if the attachment is embedded
         * @var boolean
@@ -55,6 +56,17 @@ class Attachment extends DatabaseObject implements IRouteController, IThumbnailF
         */
        protected $permissions = [];
        
+       /**
+        * @inheritDoc
+        */
+       public function getLink() {
+               // Do not use `LinkHandler::getControllerLink()` or `forceFrontend` as attachment
+               // links can be opened in the frontend and in the ACP.
+               return LinkHandler::getInstance()->getLink('Attachment', [
+                       'object' => $this,
+               ]);
+       }
+       
        /**
         * Returns true if a user has the permission to download this attachment.
         * 
@@ -197,6 +209,9 @@ class Attachment extends DatabaseObject implements IRouteController, IThumbnailF
                if ($size == 'tiny') {
                        $parameters['tiny'] = 1;
                }
+               else if ($size == 'thumbnail') {
+                       $parameters['thumbnail'] = 1;
+               }
                
                return LinkHandler::getInstance()->getLink('Attachment', $parameters);
        }
index ab45a063f3789268ed22cb88dae2698b916896c6..6ac70ce907e2cde2031a81de329c516b5790c338 100644 (file)
@@ -9,7 +9,6 @@ use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\event\EventHandler;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\UserInputException;
-use wcf\system\request\LinkHandler;
 use wcf\system\upload\DefaultUploadFileSaveStrategy;
 use wcf\system\upload\DefaultUploadFileValidationStrategy;
 use wcf\system\upload\UploadFile;
@@ -172,9 +171,9 @@ class AttachmentAction extends AbstractDatabaseObjectAction implements ISortable
                                        'formattedFilesize' => FileUtil::formatFilesize($attachment->filesize),
                                        'isImage' => $attachment->isImage,
                                        'attachmentID' => $attachment->attachmentID,
-                                       'tinyURL' => $attachment->tinyThumbnailType ? LinkHandler::getInstance()->getLink('Attachment', ['object' => $attachment], 'tiny=1') : '',
-                                       'thumbnailURL' => $attachment->thumbnailType ? LinkHandler::getInstance()->getLink('Attachment', ['object' => $attachment], 'thumbnail=1') : '',
-                                       'url' => LinkHandler::getInstance()->getLink('Attachment', ['object' => $attachment]),
+                                       'tinyURL' => $attachment->tinyThumbnailType ? $attachment->getThumbnailLink('tiny') : '',
+                                       'thumbnailURL' => $attachment->thumbnailType ? $attachment->getThumbnailLink('thumbnail') : '',
+                                       'url' => $attachment->getLink(),
                                        'height' => $attachment->height,
                                        'width' => $attachment->width,
                                        'iconName' => $attachment->getIconName()
index c253d224c26b488c3d06da8b1b1c88132fe8f5c9..062c8575e6f522e1b352e89759a53926a0600b42 100644 (file)
@@ -58,8 +58,7 @@ class ViewableComment extends DatabaseObjectDecorator {
                $list = new ViewableCommentList();
                $list->setObjectIDs([$commentID]);
                $list->readObjects();
-               $objects = $list->getObjects();
-               if (isset($objects[$commentID])) return $objects[$commentID];
-               return null;
+               
+               return $list->getSingleObject();
        }
 }
index dd803ade89ef7f2d4683ff9abbcd135cead819d7..9e98895d7f73fa26e1d1118fba3cce30c8204a05 100644 (file)
@@ -58,8 +58,7 @@ class ViewableCommentResponse extends DatabaseObjectDecorator {
                $list = new ViewableCommentResponseList();
                $list->setObjectIDs([$responseID]);
                $list->readObjects();
-               $objects = $list->getObjects();
-               if (isset($objects[$responseID])) return $objects[$responseID];
-               return null;
+               
+               return $list->getSingleObject();
        }
 }
index 448bac6df079523ef5702a77674503244b479f4a..ec8d2ea38ae2b8199ee7a5e7bc87578c0c336280 100644 (file)
@@ -46,6 +46,12 @@ class Language extends DatabaseObject {
         */
        public $packageID = PACKAGE_ID;
        
+       /**
+        * contains categories currently being loaded as array keys
+        * @var bool[]
+        */
+       protected $categoriesBeingLoaded = [];
+       
        /**
         * Returns the name of this language.
         * 
@@ -210,6 +216,10 @@ class Language extends DatabaseObject {
                // search language file
                $filename = WCF_DIR.'language/'.$this->languageID.'_'.$category.'.php';
                if (!@file_exists($filename)) {
+                       if (isset($this->categoriesBeingLoaded[$category])) {
+                               throw new \LogicException("Circular dependency detected! Cannot load category '{$category}' while it is already being loaded.");
+                       }
+                       
                        if ($this->editor === null) {
                                $this->editor = new LanguageEditor($this);
                        }
@@ -220,7 +230,11 @@ class Language extends DatabaseObject {
                                return false;
                        }
                        
+                       $this->categoriesBeingLoaded[$category] = true;
+                       
                        $this->editor->updateCategory($languageCategory);
+                       
+                       unset($this->categoriesBeingLoaded[$category]);
                }
                
                // include language file
index 1adcfc2a9b64fc0fe1cbe7f3d49c6bf4fab22104..7f7038467525d811808d357ff130fe3e69dba9a5 100644 (file)
@@ -4,6 +4,7 @@ use wcf\data\reaction\type\ReactionType;
 use wcf\data\reaction\type\ReactionTypeCache;
 use wcf\data\DatabaseObject;
 use wcf\system\WCF;
+use wcf\util\StringUtil;
 
 /**
  * Represents a like of an object.
@@ -35,6 +36,26 @@ class Like extends DatabaseObject {
         */
        const DISLIKE = -1;
        
+       /**
+        * Returns the title of the associated reaction type.
+        * 
+        * @return      string
+        * @since       5.3
+        */
+       public function __toString() {
+               return $this->getReactionType()->getTitle();
+       }
+       
+       /**
+        * Renders the like by showing the associated reaction type's icon.
+        * 
+        * @return      string
+        * @since       5.3
+        */
+       public function render() {
+               return '<span title="' . StringUtil::encodeHTML($this) . '" class="jsTooltip">' . $this->getReactionType()->renderIcon() . '</span>';
+       }
+       
        /**
         * Returns the like with given type, object id and user id.
         * 
index fa640ab77ef4fa60072daf01f4b0856756cd189d..b271d12e7e33931e93c0af6b01eb432bf371eb33 100644 (file)
@@ -7,7 +7,6 @@ use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\exception\UserInputException;
 use wcf\system\moderation\queue\ModerationQueueManager;
-use wcf\system\request\LinkHandler;
 use wcf\system\user\storage\UserStorageHandler;
 use wcf\system\visitTracker\VisitTracker;
 use wcf\system\WCF;
@@ -16,7 +15,7 @@ use wcf\system\WCF;
  * Executes moderation queue-related actions.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
+ * @copyright  2001-2020 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\Moderation\Queue
  * 
@@ -263,7 +262,7 @@ class ModerationQueueAction extends AbstractDatabaseObjectAction {
                $username = $this->user->userID ? $this->user->username : WCF::getLanguage()->get('wcf.moderation.assignedUser.nobody');
                $link = '';
                if ($this->user->userID) {
-                       $link = LinkHandler::getInstance()->getLink('User', ['object' => $this->user]);
+                       $link = $this->user->getLink();
                }
                
                $newStatus = '';
index 871cd590662ab7de9ca88adc3df1eb9127dbd373..b9618a0202dada6e1a93434728ecb1aa4240103f 100644 (file)
@@ -157,9 +157,8 @@ class ViewableModerationQueue extends DatabaseObjectDecorator implements ILinkab
                $queueList->getConditionBuilder()->add("moderation_queue.queueID = ?", [$queueID]);
                $queueList->sqlLimit = 1;
                $queueList->readObjects();
-               $queues = $queueList->getObjects();
                
-               return (isset($queues[$queueID]) ? $queues[$queueID] : null);
+               return $queueList->getSingleObject();
        }
        
        /**
index 9c5d9e0c42753617d782624e00c530466fbf9213..ebfc10b50d2ffd89956bf1d09ee48b57135bda75 100644 (file)
@@ -1,7 +1,11 @@
 <?php
 namespace wcf\data\package;
+use wcf\acp\page\PackagePage;
 use wcf\data\DatabaseObject;
+use wcf\data\ILinkableObject;
 use wcf\system\package\PackageInstallationDispatcher;
+use wcf\system\request\IRouteController;
+use wcf\system\request\LinkHandler;
 use wcf\system\WCF;
 use wcf\util\FileUtil;
 
@@ -27,7 +31,7 @@ use wcf\util\FileUtil;
  * @property-read      string          $author                 author of the package
  * @property-read      string          $authorURL              external url to the website of the package author
  */
-class Package extends DatabaseObject {
+class Package extends DatabaseObject implements ILinkableObject, IRouteController {
        /**
         * recursive list of packages that were given as required packages during installation
         * @var         Package[]
@@ -71,6 +75,22 @@ class Package extends DatabaseObject {
         */
        protected static $requirements = null;
        
+       /**
+        * @inheritDoc
+        */
+       public function getLink() {
+               return LinkHandler::getInstance()->getControllerLink(PackagePage::class, [
+                       'object' => $this,
+               ]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTitle() {
+               return $this->getName();
+       }
+       
        /**
         * Returns true if this package is required by other packages.
         * 
index 222e8aa1cffaba2d4db96f60d200efddd8c3d369..dbf70fb30fa22be8098abf812082a8f1d9013444 100644 (file)
@@ -109,11 +109,6 @@ class PackageInstallationPluginAction extends AbstractDatabaseObjectAction {
                        throw new \RuntimeException("PIP '{$this->packageInstallationPlugin->pluginName}' is not allowed to throw a 'SplitNodeException'.");
                }
                
-               // clear cache
-               
-               // TODO: use a central method instead!
-               
-               // create search index tables
                SearchIndexManager::getInstance()->createSearchIndices();
                
                VersionTracker::getInstance()->createStorageTables();
index 645a205d9ab14b3632403d46792966b98e27e4ba..46938316157eda7b235a6c2c6a7bde870dc8f61a 100644 (file)
@@ -54,6 +54,16 @@ class Trophy extends DatabaseObject implements ITitledLinkObject, IRouteControll
         */
        const DEFAULT_SIZE = 32;
        
+       /**
+        * Returns the title of the trophy.
+        * 
+        * @return      string
+        * @since       5.3
+        */
+       public function __toString() {
+               return $this->getTitle();
+       }
+       
        /**
         * @inheritDoc
         */
index 761ea0b50c8854a89e2bcc43831ceadf7348525f..e824acdeab0dcb7a327c75de40f78ceb03ae42e1 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\data\user;
+use wcf\data\IPopoverObject;
 use wcf\data\language\Language;
 use wcf\data\user\group\UserGroup;
 use wcf\data\DatabaseObject;
@@ -73,7 +74,7 @@ use wcf\util\UserUtil;
  * @property-read      integer         $articles                       number of articles written by the user
  * @property-read       string          $blacklistMatches               JSON string of an array with all matches in the blacklist, otherwise an empty string 
  */
-final class User extends DatabaseObject implements IRouteController, IUserContent {
+final class User extends DatabaseObject implements IPopoverObject, IRouteController, IUserContent {
        /**
         * list of group ids
         * @var integer[]
@@ -628,4 +629,11 @@ final class User extends DatabaseObject implements IRouteController, IUserConten
                        return WCF::getLanguage()->get('wcf.user.' . $field);
                }, $this->getBlacklistMatches());
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getPopoverLinkClass() {
+               return 'userLink';
+       }
 }
index 94fed670b1292c9bfff4164446968a1a70d4b408..da5c8b7474fc0439043ffd85d3e644a38e48d667 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\data\user;
+use wcf\data\IPopoverAction;
 use wcf\data\object\type\ObjectTypeCache;
 use wcf\data\user\group\UserGroup;
 use wcf\system\bbcode\BBCodeHandler;
@@ -30,11 +31,11 @@ use wcf\util\StringUtil;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Data\User
  */
-class UserProfileAction extends UserAction {
+class UserProfileAction extends UserAction implements IPopoverAction {
        /**
         * @inheritDoc
         */
-       protected $allowGuestAccess = ['getUserProfile', 'getDetailedActivityPointList'];
+       protected $allowGuestAccess = ['getUserProfile', 'getDetailedActivityPointList', 'getPopover'];
        
        /**
         * @var User
@@ -93,8 +94,28 @@ class UserProfileAction extends UserAction {
         * Validates user profile preview.
         * 
         * @throws      UserInputException
+        * @deprecated  since 5.3, use `validateGetPopover()`
         */
        public function validateGetUserProfile() {
+               $this->validateGetPopover();
+       }
+       
+       /**
+        * Returns user profile preview.
+        * 
+        * @return      array
+        * @deprecated  since 5.3, use `getPopover()`
+        */
+       public function getUserProfile() {
+               return array_merge($this->getPopover(), [
+                       'userID' => reset($this->objectIDs),
+               ]);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateGetPopover() {
                WCF::getSession()->checkPermissions(['user.profile.canViewUserProfile']);
                
                if (count($this->objectIDs) != 1) {
@@ -103,24 +124,21 @@ class UserProfileAction extends UserAction {
        }
        
        /**
-        * Returns user profile preview.
-        * 
-        * @return      array
+        * @inheritDoc
         */
-       public function getUserProfile() {
+       public function getPopover() {
                $userID = reset($this->objectIDs);
                
                if ($userID) {
                        $userProfileList = new UserProfileList();
                        $userProfileList->getConditionBuilder()->add("user_table.userID = ?", [$userID]);
                        $userProfileList->readObjects();
-                       $userProfiles = $userProfileList->getObjects();
                        
-                       if (empty($userProfiles)) {
-                               WCF::getTPL()->assign('unknownUser', true);
+                       if (count($userProfileList)) {
+                               WCF::getTPL()->assign('user', $userProfileList->getSingleObject());
                        }
                        else {
-                               WCF::getTPL()->assign('user', reset($userProfiles));
+                               WCF::getTPL()->assign('unknownUser', true);
                        }
                }
                else {
@@ -129,7 +147,6 @@ class UserProfileAction extends UserAction {
                
                return [
                        'template' => WCF::getTPL()->fetch('userProfilePreview'),
-                       'userID' => $userID
                ];
        }
        
index 910ee331570b86b671268445e4271f5ddb3d9e57..ed5f390283b95423de58a79adf99f25947b6869e 100644 (file)
@@ -99,7 +99,6 @@ class UserNotificationEventAction extends AbstractDatabaseObjectAction {
                $getRenderedException = function($e) {
                        \wcf\functions\exception\logThrowable($e);
                        
-                       // TODO: output could/should be improved in the future
                        return $e->getMessage();
                };
                
diff --git a/wcfsetup/install/files/lib/form/MailForm.class.php b/wcfsetup/install/files/lib/form/MailForm.class.php
deleted file mode 100644 (file)
index b78f626..0000000
+++ /dev/null
@@ -1,215 +0,0 @@
-<?php
-namespace wcf\form;
-use wcf\data\user\UserProfile;
-use wcf\system\cache\runtime\UserProfileRuntimeCache;
-use wcf\system\email\mime\MimePartFacade;
-use wcf\system\email\mime\RecipientAwareTextMimePart;
-use wcf\system\email\Email;
-use wcf\system\email\Mailbox;
-use wcf\system\email\UserMailbox;
-use wcf\system\exception\IllegalLinkException;
-use wcf\system\exception\PermissionDeniedException;
-use wcf\system\exception\UserInputException;
-use wcf\system\page\PageLocationManager;
-use wcf\system\request\LinkHandler;
-use wcf\system\WCF;
-use wcf\util\HeaderUtil;
-use wcf\util\StringUtil;
-use wcf\util\UserUtil;
-
-/**
- * Shows the user mail form.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Form
- */
-class MailForm extends AbstractCaptchaForm {
-       /**
-        * @inheritDoc
-        */
-       public $useCaptcha = PROFILE_MAIL_USE_CAPTCHA;
-       
-       /**
-        * @inheritDoc
-        */
-       public $forceCaptcha = true;
-       
-       /**
-        * recipient's user id
-        * @var integer
-        */
-       public $userID = 0;
-       
-       /**
-        * recipient's user object
-        * @var UserProfile
-        */
-       public $user = null;
-       
-       /**
-        * true to add the reply-to header
-        * @var boolean
-        */
-       public $showAddress = true;
-       
-       /**
-        * email subject
-        * @var string
-        */
-       public $subject = '';
-       
-       /**
-        * email message
-        * @var string
-        */
-       public $message = '';
-       
-       /**
-        * sender's email address
-        * @var string
-        */
-       public $email = '';
-       
-       /**
-        * @inheritDoc
-        */
-       public $neededPermissions = ['user.profile.canMail'];
-       
-       /**
-        * @inheritDoc
-        */
-       public function readParameters() {
-               parent::readParameters();
-               
-               if (isset($_REQUEST['id'])) $this->userID = intval($_REQUEST['id']);
-               $this->user = UserProfileRuntimeCache::getInstance()->getObject($this->userID);
-               if ($this->user === null) {
-                       throw new IllegalLinkException();
-               }
-               // validate ignore status
-               if (WCF::getUser()->userID && $this->user->isIgnoredUser(WCF::getUser()->userID)) {
-                       throw new PermissionDeniedException();
-               }
-               
-               $this->canonicalURL = LinkHandler::getInstance()->getLink('Mail', ['object' => $this->user->getDecoratedObject()]);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function readFormParameters() {
-               parent::readFormParameters();
-               
-               $this->showAddress = 0;
-               if (isset($_POST['message'])) $this->message = StringUtil::trim($_POST['message']);
-               if (isset($_POST['subject'])) $this->subject = StringUtil::trim($_POST['subject']);
-               if (isset($_POST['email'])) $this->email = StringUtil::trim($_POST['email']);
-               if (isset($_POST['showAddress'])) $this->showAddress = intval($_POST['showAddress']);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function validate() {
-               if (!WCF::getUser()->userID) {
-                       if (empty($this->email)) {
-                               throw new UserInputException('email');
-                       }
-                       
-                       if (!UserUtil::isValidEmail($this->email)) {
-                               throw new UserInputException('email', 'invalid');
-                       }
-               }
-               
-               if (empty($this->subject)) {
-                       throw new UserInputException('subject');
-               }
-               
-               if (empty($this->message)) {
-                       throw new UserInputException('message');
-               }
-               
-               parent::validate();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function save() {
-               parent::save();
-               
-               // build message data
-               $messageData = [
-                       'message' => $this->message,
-                       'username' => WCF::getUser()->userID ? WCF::getUser()->username : $this->email
-               ];
-               
-               // build mail
-               $email = new Email();
-               $email->addRecipient(new UserMailbox($this->user->getDecoratedObject()));
-               $email->setSubject($this->user->getLanguage()->getDynamicVariable('wcf.user.mail.mail.subject', [
-                       'username' => WCF::getUser()->userID ? WCF::getUser()->username : $this->email,
-                       'subject' => $this->subject
-               ]));
-               $email->setBody(new MimePartFacade([
-                       new RecipientAwareTextMimePart('text/html', 'email_mail', 'wcf', $messageData),
-                       new RecipientAwareTextMimePart('text/plain', 'email_mail', 'wcf', $messageData)
-               ]));
-               
-               // add reply-to tag
-               if (WCF::getUser()->userID) {
-                       if ($this->showAddress) {
-                               $email->setReplyTo(new UserMailbox(WCF::getUser()));
-                       }
-               }
-               else {
-                       $email->setReplyTo(new Mailbox($this->email));
-               }
-               
-               // send mail
-               $email->send();
-               $this->saved();
-               
-               // forward to profile page
-               HeaderUtil::delayedRedirect(LinkHandler::getInstance()->getLink('User', ['object' => $this->user]), WCF::getLanguage()->getDynamicVariable('wcf.user.mail.sent', ['user' => $this->user]));
-               exit;
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function readData() {
-               parent::readData();
-               
-               PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.User', $this->user->userID, $this->user);
-               if (MODULE_MEMBERS_LIST) PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.MembersList');
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function assignVariables() {
-               parent::assignVariables();
-               
-               WCF::getTPL()->assign([
-                       'user' => $this->user,
-                       'showAddress' => $this->showAddress,
-                       'message' => $this->message,
-                       'subject' => $this->subject,
-                       'email' => $this->email
-               ]);
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function show() {
-               if (!$this->user->isAccessible('canMail')) {
-                       throw new PermissionDeniedException();
-               }
-               
-               parent::show();
-       }
-}
index 966fb83e7282d333e43946dd7329e779545786b7..4d6c61e62fc60970e0f209c57c6ba7e00d8be850 100644 (file)
@@ -130,6 +130,9 @@ class SettingsForm extends AbstractForm {
                        
                        Trophy::sort($this->availableTrophies, 'showOrder');
                }
+               else if (empty($this->optionHandler->getOptionTree())) {
+                       throw new IllegalLinkException();
+               }
        }
        
        /**
index 86ec3056f45dd256c9e2df41b0337e9cafaefc6c..5c3b1dafc8ac0c1d9c5baddfb016fe4ef87b4937 100644 (file)
@@ -4,14 +4,13 @@ use wcf\data\user\User;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\session\SessionHandler;
 use wcf\system\WCF;
-use wcf\util\StringUtil;
 
 /**
  * Automatically authes the user for the current request via an access-token.
  * A missing token will be ignored, an invalid token results in a throw of a IllegalLinkException.
  * 
  * @author     Tim Duesterhus
- * @copyright  2001-2019 WoltLab GmbH
+ * @copyright  2001-2020 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Page
  */
index 05eca840ee2138482267ffdb5f0c2be6b813daea..093ca704f51ea26dc9b7cbf9f9f7634effe2c686 100644 (file)
@@ -20,6 +20,11 @@ class ArticleAmpPage extends AbstractArticlePage {
         */
        public $templateName = 'ampArticle';
        
+       /**
+        * @inheritDoc
+        */
+       public $neededModules = ['MODULE_ARTICLE', 'MODULE_AMP'];
+       
        /**
         * list of additional articles
         * @var ViewableArticle[]
index 4cf8bec2d91beca2cae8a76e1090f467821c275f..d8dfc6a384a79173e677909466eebb32cca8f635 100644 (file)
@@ -7,7 +7,6 @@ use wcf\data\like\object\LikeObject;
 use wcf\system\comment\manager\ICommentManager;
 use wcf\system\comment\CommentHandler;
 use wcf\system\reaction\ReactionHandler;
-use wcf\system\request\LinkHandler;
 use wcf\system\MetaTagHandler;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
@@ -105,7 +104,7 @@ class ArticlePage extends AbstractArticlePage {
                
                // add meta/og tags
                MetaTagHandler::getInstance()->addTag('og:title', 'og:title', $this->articleContent->getTitle() . ' - ' . WCF::getLanguage()->get(PAGE_TITLE), true);
-               MetaTagHandler::getInstance()->addTag('og:url', 'og:url', LinkHandler::getInstance()->getLink('Article', ['object' => $this->articleContent]), true);
+               MetaTagHandler::getInstance()->addTag('og:url', 'og:url', $this->articleContent->getLink(), true);
                MetaTagHandler::getInstance()->addTag('og:type', 'og:type', 'article', true);
                MetaTagHandler::getInstance()->addTag('og:description', 'og:description', ($this->articleContent->teaser ?: StringUtil::decodeHTML(StringUtil::stripHTML($this->articleContent->getFormattedTeaser()))), true);
                
index 1bc3162856ae172ee06ba7212fb673e85fb82ae1..1bf0d8b26c2c92d39801165e28b82dd2b3fbd8d2 100644 (file)
@@ -4,7 +4,9 @@ use wcf\data\moderation\queue\ModerationQueue;
 use wcf\data\moderation\queue\ViewableModerationQueueList;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\moderation\queue\ModerationQueueManager;
+use wcf\system\request\LinkHandler;
 use wcf\system\WCF;
+use wcf\util\HeaderUtil;
 
 /**
  * List of moderation queue entries.
@@ -69,7 +71,13 @@ class ModerationListPage extends SortablePage {
        /**
         * @inheritDoc
         */
-       public $validSortFields = ['assignedUsername', 'lastChangeTime', 'queueID', 'time', 'username', 'comments'];
+       public $validSortFields = ['username', 'time', 'comments', 'assignedUsername', 'lastChangeTime'];
+       
+       /**
+        * indicates if any filter is being used
+        * @var bool
+        */
+       public $hasActiveFilter = false;
        
        /**
         * @inheritDoc
@@ -87,6 +95,16 @@ class ModerationListPage extends SortablePage {
                                throw new IllegalLinkException();
                        }
                }
+               
+               if ($this->assignedUserID !== -1 || $this->status !== -1 || $this->definitionID !== 0) {
+                       $this->hasActiveFilter = true;
+               }
+               
+               if (!empty($_POST)) {
+                       $url = http_build_query($_POST, '', '&');
+                       HeaderUtil::redirect(LinkHandler::getInstance()->getControllerLink(static::class, [], $url));
+                       exit;
+               }
        }
        
        /**
@@ -128,7 +146,9 @@ class ModerationListPage extends SortablePage {
                        'assignedUserID' => $this->assignedUserID,
                        'availableDefinitions' => $this->availableDefinitions,
                        'definitionID' => $this->definitionID,
-                       'status' => $this->status
+                       'status' => $this->status,
+                       'validSortFields' => $this->validSortFields,
+                       'hasActiveFilter' => $this->hasActiveFilter,
                ]);
        }
 }
index 072232effe430070d70743348a59a5af03ac4974..a2074f57b7dea0c46d6e083ead0f970042947fa5 100644 (file)
@@ -94,7 +94,7 @@ class UserPage extends AbstractPage {
                
                if (isset($_REQUEST['editOnInit'])) $this->editOnInit = true;
                
-               $this->canonicalURL = LinkHandler::getInstance()->getLink('User', ['object' => $this->user]);
+               $this->canonicalURL = $this->user->getLink();
        }
        
        /**
index a60667e634aa45d76c122e6ece703699e8401b31..ebaa30128c611fd29c7158c94335c9bb8821fe70 100644 (file)
@@ -107,7 +107,7 @@ class AttachmentBBCode extends AbstractBBCode {
                                                $class = 'messageFloatObject'.ucfirst($alignment);
                                        }
                                        
-                                       $source = StringUtil::encodeHTML(LinkHandler::getInstance()->getLink('Attachment', ['object' => $attachment]));
+                                       $source = StringUtil::encodeHTML($attachment->getLink());
                                        $title = StringUtil::encodeHTML($attachment->filename);
                                        
                                        if ($parser instanceof HtmlBBCodeParser && $parser->getIsGoogleAmp()) {
@@ -165,9 +165,7 @@ class AttachmentBBCode extends AbstractBBCode {
                        }
                        else {
                                // file
-                               return StringUtil::getAnchorTag(LinkHandler::getInstance()->getLink('Attachment', [
-                                       'object' => $attachment
-                               ]), $attachment->filename);
+                               return StringUtil::getAnchorTag($attachment->getLink(), $attachment->filename);
                        }
                }
                
index 62c0e736e7e01278eebd480837d18f832e5c0598..cdd75f3b73bfb24da61cd2b4b173538cccdc5693 100644 (file)
@@ -13,7 +13,7 @@ use wcf\system\WCF;
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\Box
  */
-class TagCloudBoxController extends AbstractBoxController {
+abstract class TagCloudBoxController extends AbstractBoxController {
        /**
         * @inheritDoc
         */
index 0771c0c6a5d9d033f18994d86440ce6ebc75aba6..56797fa4d2643e77c2fdf46a0cebef3cf3ebeca2 100644 (file)
@@ -30,6 +30,11 @@ class UserListBoxController extends AbstractDatabaseObjectListBoxController {
                'registrationDate' => NewestMembersCacheBuilder::class
        ];
        
+       /**
+        * @inheritDoc
+        */
+       protected $conditionDefinition = 'com.woltlab.wcf.box.userList.condition';
+       
        /**
         * @inheritDoc
         */
index a1e0a4c31889f5c13b301df810fa16e68789ebd1..b6cf1cff55f23bccf1db3ff3fef0b19798210839 100644 (file)
@@ -22,6 +22,11 @@ class UserOnlineListBoxController extends AbstractDatabaseObjectListBoxControlle
         */
        protected static $supportedPositions = ['footerBoxes', 'sidebarLeft', 'sidebarRight'];
        
+       /**
+        * @inheritDoc
+        */
+       protected $conditionDefinition = 'com.woltlab.wcf.box.userOnlineList.condition';
+       
        /**
         * enables the display of the user online record
         * @var boolean
index b705dfe4888181ad174adf7891f9c32b1d7d44f9..f7b392793722f8a2858fa7e55054b8821f5abd92 100644 (file)
@@ -143,7 +143,7 @@ class PackageCLICommand implements IArgumentedCLICommand {
                                $this->error('noValidInstall');
                        }
                        else if ($archive->getPackageInfo('isApplication') && $archive->hasUniqueAbbreviation()) {
-                               $this->error('noUniqueAbbrevation');
+                               $this->error('noUniqueAbbreviation');
                        }
                        else if ($archive->isAlreadyInstalled()) {
                                $this->error('uniqueAlreadyInstalled');
index c91c5888f552005c7f41751406cb4a67713ddb84..de8632b0f4a64f9b705b15e0ef76b985d9b5c7b2 100644 (file)
@@ -169,7 +169,9 @@ class ArticleCommentManager extends AbstractCommentManager implements IViewableL
                                                        'commentAuthor' => $comment->userID ? $users[$comment->userID] : null,
                                                        'comment' => $comment,
                                                        'articleContent' => $articleContents[$comment->objectID],
-                                                       'like' => $like
+                                                       'reaction' => $like,
+                                                       // @deprecated 5.3 Use `$reaction` instead
+                                                       'like' => $like,
                                                ]);
                                                $like->setTitle($text);
                                                
@@ -192,8 +194,10 @@ class ArticleCommentManager extends AbstractCommentManager implements IViewableL
                                                        'responseAuthor' => $comment->userID ? $users[$response->userID] : null,
                                                        'commentAuthor' => $comment->userID ? $users[$comment->userID] : null,
                                                        'articleContent' => $articleContents[$comment->objectID],
+                                                       'reaction' => $like,
+                                                       // @deprecated 5.3 Use `$reaction` instead
                                                        'like' => $like,
-                                                       'response' => $response
+                                                       'response' => $response,
                                                ]);
                                                $like->setTitle($text);
                                                
index fdd42fc953f550aec94ecab137cd3d6c316f413e..1cfe306bc9c86b2f988bae325dc1904a506ad368 100644 (file)
@@ -163,7 +163,9 @@ class PageCommentManager extends AbstractCommentManager implements IViewableLike
                                                        'commentAuthor' => $comment->userID ? $users[$comment->userID] : null,
                                                        'comment' => $comment,
                                                        'page' => $pages[$comment->objectID],
-                                                       'like' => $like
+                                                       'reaction' => $like,
+                                                       // @deprecated 5.3 Use `$reaction` instead
+                                                       'like' => $like,
                                                ]);
                                                $like->setTitle($text);
                                                
@@ -186,6 +188,8 @@ class PageCommentManager extends AbstractCommentManager implements IViewableLike
                                                        'responseAuthor' => $comment->userID ? $users[$response->userID] : null,
                                                        'commentAuthor' => $comment->userID ? $users[$comment->userID] : null,
                                                        'page' => $pages[$comment->objectID],
+                                                       'reaction' => $like,
+                                                       // @deprecated 5.3 Use `$reaction` instead
                                                        'like' => $like,
                                                        'response' => $response
                                                ]);
index 77ebdce387bbac4f577d868e93ee981a3168870f..29dc8a061aab21ce292c890142edd1378ec236a1 100644 (file)
@@ -212,7 +212,9 @@ class UserProfileCommentManager extends AbstractCommentManager implements IViewa
                                                        'commentAuthor' => $comment->userID ? $users[$comment->userID] : null,
                                                        'comment' => $comment,
                                                        'user' => $users[$comment->objectID],
-                                                       'like' => $like
+                                                       'reaction' => $like,
+                                                       // @deprecated 5.3 Use `$reaction` instead
+                                                       'like' => $like,
                                                ]);
                                                $like->setTitle($text);
                                                
@@ -235,6 +237,8 @@ class UserProfileCommentManager extends AbstractCommentManager implements IViewa
                                                        'responseAuthor' => $response->userID ? $users[$response->userID] : null,
                                                        'commentAuthor' => $comment->userID ? $users[$comment->userID] : null,
                                                        'user' => $users[$comment->objectID],
+                                                       'reaction' => $like,
+                                                       // @deprecated 5.3 Use `$reaction` instead
                                                        'like' => $like,
                                                        'response' => $response
                                                ]);
diff --git a/wcfsetup/install/files/lib/system/condition/UserCoverPhotoCondition.class.php b/wcfsetup/install/files/lib/system/condition/UserCoverPhotoCondition.class.php
new file mode 100644 (file)
index 0000000..02eb0b0
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+namespace wcf\system\condition;
+use wcf\data\condition\Condition;
+use wcf\data\user\User;
+use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
+use wcf\system\WCF;
+
+/**
+ * Condition implementation for the cover photo of a user.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Condition
+ * @since      5.3
+ */
+class UserCoverPhotoCondition extends AbstractSelectCondition implements IContentCondition, IObjectListCondition, IUserCondition {
+       use TObjectListUserCondition;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $fieldName = 'userCoverPhoto';
+       
+       /**
+        * @inheritDoc
+        */
+       protected $label = 'wcf.user.condition.coverPhoto';
+       
+       /**
+        * value of the "user has no cover photo" option
+        * @var integer
+        */
+       const NO_COVER_PHOTO = 0;
+       
+       /**
+        * value of the "user has a cover photo" option
+        * @var integer
+        */
+       const COVER_PHOTO = 1;
+       
+       /**
+        * @inheritDoc
+        */
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!($objectList instanceof UserList)) {
+                       throw new \InvalidArgumentException("Object list is no instance of '".UserList::class."', instance of '".get_class($objectList)."' given.");
+               }
+               
+               switch ($conditionData['userCoverPhoto']) {
+                       case self::NO_COVER_PHOTO:
+                               $objectList->getConditionBuilder()->add('(user_table.coverPhotoHash = ? OR user_table.coverPhotoHash IS NULL)', ['']);
+                       break;
+                       
+                       case self::COVER_PHOTO:
+                               $objectList->getConditionBuilder()->add('(user_table.coverPhotoHash <> ? AND user_table.coverPhotoHash IS NOT NULL)', ['']);
+                       break;
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function checkUser(Condition $condition, User $user) {
+               switch ($condition->userCoverPhoto) {
+                       case self::NO_COVER_PHOTO:
+                               return $user->coverPhotoExtension === '' || $user->coverPhotoExtension === null;
+                       break;
+                       
+                       case self::COVER_PHOTO:
+                               return $user->coverPhotoExtension !== '' && $user->coverPhotoExtension !== null;
+                       break;
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected function getOptions() {
+               return [
+                       self::NO_SELECTION_VALUE => 'wcf.global.noSelection',
+                       self::NO_COVER_PHOTO => 'wcf.user.condition.coverPhoto.noCoverPhoto',
+                       self::COVER_PHOTO => 'wcf.user.condition.coverPhoto.coverPhoto'
+               ];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function showContent(Condition $condition) {
+               if (!WCF::getUser()->userID) return false;
+               
+               return $this->checkUser($condition, WCF::getUser());
+       }
+}
index 96e9cee1ba682197b3c36fe69e4d947cfc3cd696..f3a9383abafe4385b4b7f5bc4f6d1f61bf19db73 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\condition;
 use wcf\data\condition\Condition;
 use wcf\data\user\group\UserGroup;
+use wcf\data\user\online\UsersOnlineList;
 use wcf\data\user\User;
 use wcf\data\user\UserList;
 use wcf\data\DatabaseObjectList;
@@ -59,15 +60,20 @@ class UserGroupCondition extends AbstractMultipleFieldsCondition implements ICon
         * @inheritDoc
         */
        public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
-               if (!($objectList instanceof UserList)) {
-                       throw new \InvalidArgumentException("Object list is no instance of '".UserList::class."', instance of '".get_class($objectList)."' given.");
+               if (!($objectList instanceof UserList) && !($objectList instanceof UsersOnlineList)) {
+                       throw new \InvalidArgumentException("Object list is neither an instance of '".UserList::class."' nor of '".UsersOnlineList::class."', instance of '".get_class($objectList)."' given.");
+               }
+               
+               $tableName = 'user_table';
+               if ($objectList instanceof UsersOnlineList) {
+                       $tableName = 'session';
                }
                
                if (isset($conditionData['groupIDs'])) {
-                       $objectList->getConditionBuilder()->add('user_table.userID IN (SELECT userID FROM wcf'.WCF_N.'_user_to_group WHERE groupID IN (?) GROUP BY userID HAVING COUNT(userID) = ?)', [$conditionData['groupIDs'], count($conditionData['groupIDs'])]);
+                       $objectList->getConditionBuilder()->add($tableName . '.userID IN (SELECT userID FROM wcf'.WCF_N.'_user_to_group WHERE groupID IN (?) GROUP BY userID HAVING COUNT(userID) = ?)', [$conditionData['groupIDs'], count($conditionData['groupIDs'])]);
                }
                if (isset($conditionData['notGroupIDs'])) {
-                       $objectList->getConditionBuilder()->add('user_table.userID NOT IN (SELECT userID FROM wcf'.WCF_N.'_user_to_group WHERE groupID IN (?))', [$conditionData['notGroupIDs']]);
+                       $objectList->getConditionBuilder()->add($tableName . '.userID NOT IN (SELECT userID FROM wcf'.WCF_N.'_user_to_group WHERE groupID IN (?))', [$conditionData['notGroupIDs']]);
                }
        }
        
diff --git a/wcfsetup/install/files/lib/system/condition/UserSignatureCondition.class.php b/wcfsetup/install/files/lib/system/condition/UserSignatureCondition.class.php
new file mode 100644 (file)
index 0000000..e2baa48
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+namespace wcf\system\condition;
+use wcf\data\condition\Condition;
+use wcf\data\user\User;
+use wcf\data\user\UserList;
+use wcf\data\DatabaseObjectList;
+use wcf\system\WCF;
+
+/**
+ * Condition implementation for the signature of a user.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Condition
+ * @since      5.3
+ */
+class UserSignatureCondition extends AbstractSelectCondition implements IContentCondition, IObjectListCondition, IUserCondition {
+       use TObjectListUserCondition;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $fieldName = 'userSignature';
+       
+       /**
+        * @inheritDoc
+        */
+       protected $label = 'wcf.user.condition.signature';
+       
+       /**
+        * value of the "user has no signature" option
+        * @var integer
+        */
+       const NO_SIGNATURE = 0;
+       
+       /**
+        * value of the "user has a signature" option
+        * @var integer
+        */
+       const SIGNATURE = 1;
+       
+       /**
+        * @inheritDoc
+        */
+       public function addObjectListCondition(DatabaseObjectList $objectList, array $conditionData) {
+               if (!($objectList instanceof UserList)) {
+                       throw new \InvalidArgumentException("Object list is no instance of '".UserList::class."', instance of '".get_class($objectList)."' given.");
+               }
+               
+               switch ($conditionData['userSignature']) {
+                       case self::NO_SIGNATURE:
+                               $objectList->getConditionBuilder()->add('(user_table.signature = ? OR user_table.signature IS NULL)', ['']);
+                       break;
+                       
+                       case self::SIGNATURE:
+                               $objectList->getConditionBuilder()->add('(user_table.signature <> ? AND user_table.signature IS NOT NULL)', ['']);
+                       break;
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function checkUser(Condition $condition, User $user) {
+               switch ($condition->userSignature) {
+                       case self::NO_SIGNATURE:
+                               return $user->signature === '' || $user->signature === null;
+                       break;
+                       
+                       case self::SIGNATURE:
+                               return $user->signature !== '' && $user->signature !== null;
+                       break;
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected function getOptions() {
+               return [
+                       self::NO_SELECTION_VALUE => 'wcf.global.noSelection',
+                       self::NO_SIGNATURE => 'wcf.user.condition.signature.noSignature',
+                       self::SIGNATURE => 'wcf.user.condition.signature.signature'
+               ];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function showContent(Condition $condition) {
+               if (!WCF::getUser()->userID) return false;
+               
+               return $this->checkUser($condition, WCF::getUser());
+       }
+}
index ad21599de7e39e865ce710ccea784a802c806f78..f2589bf80ee669b7faa3b9e0bcda25f7a52b89a0 100644 (file)
@@ -15,7 +15,7 @@ interface IEventListener {
        /**
         * Executes this action.
         * 
-        * @param       object          $eventObj
+        * @param       mixed           $eventObj
         * @param       string          $className
         * @param       string          $eventName
         */
diff --git a/wcfsetup/install/files/lib/system/event/listener/AbstractEventListener.class.php b/wcfsetup/install/files/lib/system/event/listener/AbstractEventListener.class.php
new file mode 100644 (file)
index 0000000..b403b2d
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+namespace wcf\system\event\listener;
+use wcf\data\AbstractDatabaseObjectAction;
+
+/**
+ * Preferred implementation for event listeners that dynamically invokes methods based on a predictable
+ * naming schema derived from the event name. In addition, `AbstractDatabaseObjectAction` supports deep
+ * method invocations for the generic action events. However, when there is no specific method, it will
+ * attempt to invoke the regular method instead.
+ * 
+ * Examples:
+ * Regular classes
+ *   eventName: makeSnafucated
+ *   derived method: onMakeSnafucated()
+ * 
+ * Classes deriving from AbstractDatabaseObjectAction
+ *   eventName: initializeAction
+ *   actionName: makeSnafucated
+ *   derived method: onInitializeActionMakeSnafucated()
+ *   ATTENTION: If this method does not exist, it will attempt to invoke `onInitializeAction()` instead. 
+ *
+ * @author      Olaf Braun, Alexander Ebert
+ * @copyright   2001-2020 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package     WoltLabSuite\Core\System\Event\Listener
+ */
+abstract class AbstractEventListener implements IParameterizedEventListener {
+       /**
+        * @inheritDoc
+        */
+       public final function execute($eventObj, $className, $eventName, array &$parameters) {
+               static $genericDboActionNames = ['finalizeAction', 'initializeAction', 'validateAction'];
+               
+               $methodName = 'on' . ucfirst($eventName);
+               
+               if ($eventObj instanceof AbstractDatabaseObjectAction && in_array($eventName, $genericDboActionNames)) {
+                       $actionMethod = $methodName . ucfirst($eventObj->getActionName());
+                       if (method_exists($this, $actionMethod)) {
+                               $this->{$actionMethod}($eventObj, $parameters);
+                               return;
+                       }
+               }
+               
+               if (method_exists($this, $methodName)) {
+                       $this->{$methodName}($eventObj, $parameters);
+               }
+       }
+}
index 4ddae09cb1c0826df29bf1075424433affdf69c7..89cd8897694340071da983b6061775a8d76fabc9 100644 (file)
@@ -16,7 +16,7 @@ interface IParameterizedEventListener {
        /**
         * Executes this action.
         * 
-        * @param       object          $eventObj       Object firing the event
+        * @param       mixed           $eventObj       Object firing the event
         * @param       string          $className      class name of $eventObj
         * @param       string          $eventName      name of the event fired
         * @param       array           &$parameters    given parameters
index 8fe3d7a93d0a0063a54ab6822b87c6509759c6ed..88308ebbe7ea19bffaf395907926bf1d1318a0eb 100644 (file)
@@ -527,6 +527,29 @@ class FormDocument implements IFormDocument {
                return $this->invalid;
        }
        
+       /**
+        * @inheritDoc
+        * @since       5.3
+        */
+       public function needsRequiredFieldsInfo() {
+               /** @var IFormNode $node */
+               foreach ($this->getIterator() as $node) {
+                       if (
+                               $node->isAvailable()
+                               && $node instanceof IFormElement
+                               && $node->getLabel() !== null
+                               && (
+                                       ($node instanceof IFormContainer && $node->markAsRequired())
+                                       || ($node instanceof IFormField && $node->isRequired())
+                               )
+                       ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+       
        /**
         * @inheritDoc
         */
index 7025fc9495d3cef6b96627d9003c2c54d7d2edb4..6009d40b2d5d1b5fe1f7e0e52db96f206c928936 100644 (file)
@@ -287,6 +287,14 @@ interface IFormDocument extends IFormParentNode {
         */
        public function invalid($invalid = true);
        
+       /**
+        * Returns `true` if the information about required fields has to be shown below the form.
+        * 
+        * @return      bool
+        * @since       5.3
+        */
+       public function needsRequiredFieldsInfo();
+       
        /**
         * Sets the updated object (and loads the field values from the given object) and returns
         * this document.
index 008a3cda3b448a7c9fe825e9c01587aac2fa7f88..ef92e4140d400d63a4c63c64b1c159de89fdf5fe 100644 (file)
@@ -58,6 +58,14 @@ class FormContainer implements IFormContainer {
                );
        }
        
+       /**
+        * @inheritDoc
+        * @since       5.3
+        */
+       public function markAsRequired() {
+               return false;
+       }
+       
        /**
         * @inheritDoc
         */
index efdbbb942005547009438ebbe903122d96de2517..e654776f28d964340cd6fd23834ef74848de86af 100644 (file)
@@ -15,6 +15,14 @@ use wcf\system\form\builder\IFormParentNode;
  * @since      5.2
  */
 interface IFormContainer extends IFormChildNode, IFormElement, IFormParentNode {
+       /**
+        * Returns `true` if the whole container should be marked as required in the form output.
+        * 
+        * @return      bool
+        * @since       5.3
+        */
+       public function markAsRequired();
+       
        /**
         * Informs the form container of the updated object and this method is called by
         * `IFormDocument::updatedObject()` to inform the container that object data is being loaded.
index 51c645528c77ff0bc152f29a7580db180e2cd948..c90f89314d2b7b72607e23b4706a667f6c348b97 100644 (file)
@@ -316,6 +316,14 @@ class WysiwygFormContainer extends FormContainer {
                return $this->required;
        }
        
+       /**
+        * @since       5.3
+        * @inheritDoc
+        */
+       public function markAsRequired() {
+               return $this->getWysiwygField()->isRequired();
+       }
+       
        /**
         * Sets the message object type used by the wysiwyg form field.
         * 
index a97a65edeeff89303309f5dfb4c2726b1b68ec1a..56930848cefcf3cc52dfbda9cd3cbff71d0741b2 100644 (file)
@@ -99,12 +99,17 @@ class RemoteFile extends File {
                // PHP 5.6.8+ defines STREAM_CRYPTO_METHOD_TLS_CLIENT as STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT for BC reasons.
                // STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT was introduced in PHP 5.6.8, but is not exposed to userland. Try to use
                // it for forward compatibility.
-               if (defined('STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT')) $cryptoType = STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT;
+               // 
+               // As of PHP 7.2+ STREAM_CRYPTO_METHOD_TLS_CLIENT is equivalent to STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT.
+               // see: https://wiki.php.net/rfc/improved-tls-constants
+               // see: https://github.com/php/php-src/blob/197cac65fdf712effb19ad3e40688ceb7ebc7f7d/main/streams/php_stream_transport.h#L173-L175
+               if (defined('STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT')) $cryptoType |= STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT;
                
                // Add bits for all known TLS versions for the reasons above.
                if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT')) $cryptoType |= STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
                if (defined('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT')) $cryptoType |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
                if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) $cryptoType |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
+               if (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT')) $cryptoType |= STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
                
                return stream_socket_enable_crypto($this->resource, $enable, $cryptoType);
        }
diff --git a/wcfsetup/install/files/lib/system/language/I18nPlural.class.php b/wcfsetup/install/files/lib/system/language/I18nPlural.class.php
new file mode 100644 (file)
index 0000000..07c52e2
--- /dev/null
@@ -0,0 +1,515 @@
+<?php /** @noinspection ALL */
+namespace wcf\system\language;
+use wcf\system\WCF;
+
+/**
+ * Provides functions for pluralization rules.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Language
+ * @since      5.3
+ * @see https://unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
+ */
+class I18nPlural {
+       const PLURAL_FEW = 'few';
+       const PLURAL_MANY = 'many';
+       const PLURAL_ONE = 'one';
+       const PLURAL_OTHER = 'other';
+       const PLURAL_TWO = 'two';
+       const PLURAL_ZERO = 'zero';
+       
+       /**
+        * Returns the plural category for the given value.
+        * 
+        * @param       number         $value
+        * @param       string         $languageCode
+        * @return      string
+        */
+       public static function getCategory($value, $languageCode = null) {
+               if ($languageCode === null) {
+                       $languageCode = WCF::getLanguage()->getFixedLanguageCode();
+               }
+               
+               // Fallback: handle unknown languages as English
+               if (!method_exists(self::class, $languageCode)) {
+                       $languageCode = 'en';
+               }
+               
+               if ($category = self::$languageCode($value)) {
+                       return $category;
+               }
+               
+               return self::PLURAL_OTHER;
+       }
+       
+       /**
+        * `f` is the fractional number as a whole number (1.234 yields 234)
+        *
+        * @param       number          $n
+        * @return      integer
+        */
+       private static function getF($n) {
+               $n = (string)$n;
+               $pos = strpos($n, '.');
+               if ($pos === false) {
+                       return 0;
+               }
+               
+               return intval(substr($n, $pos + 1));
+       }
+       
+       /**
+        * `v` represents the number of digits of the fractional part (1.234 yields 3)
+        *
+        * @param       number          $n
+        * @return      integer
+        */
+       private static function getV($n) {
+               return strlen(preg_replace('/^[^.]*\.?/', '', (string)$n));
+       }
+       
+       // Afrikaans
+       private static function af($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Amharic
+       private static function am($n) {
+               $i = floor(abs($n));
+               if ($n == 1 || $i === 0) return self::PLURAL_ONE;
+       }
+       
+       // Arabic
+       private static function ar($n) {
+               if ($n == 0) return self::PLURAL_ZERO;
+               if ($n == 1) return self::PLURAL_ONE;
+               if ($n == 2) return self::PLURAL_TWO;
+               
+               $mod100 = $n % 100;
+               if ($mod100 >= 3 && $mod100 <= 10) return self::PLURAL_FEW;
+               if ($mod100 >= 11 && $mod100 <= 99) return self::PLURAL_MANY;
+       }
+       
+       // Assamese
+       private static function as($n) {
+               $i = floor(abs($n));
+               if ($n == 1 || $i === 0) return self::PLURAL_ONE;
+       }
+       
+       // Azerbaijani
+       private static function az($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Belarusian
+       private static function be($n) {
+               $mod10 = $n % 10;
+               $mod100 = $n % 100;
+               
+               if ($mod10 == 1 && $mod100 != 11) return self::PLURAL_ONE;
+               if ($mod10 >= 2 && $mod10 <= 4 && !($mod100 >= 12 && $mod100 <= 14)) return self::PLURAL_FEW;
+               if ($mod10 == 0 || ($mod10 >= 5 && $mod10 <= 9) || ($mod100 >= 11 && $mod100 <= 14)) return self::PLURAL_MANY;
+       }
+       
+       // Bulgarian
+       private static function bg($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Bengali
+       private static function bn($n) {
+               $i = floor(abs($n));
+               if ($n == 1 || $i === 0) return self::PLURAL_ONE;
+       }
+       
+       // Tibetan
+       private static function bo($n) {}
+       
+       // Bosnian
+       private static function bs($n) {
+               $v = self::getV($n);
+               $f = self::getF($n);
+               $mod10 = $n % 10;
+               $mod100 = $n % 100;
+               $fMod10 = $f % 10;
+               $fMod100 = $f % 100;
+               
+               if (($v == 0 && $mod10 == 1 && $mod100 != 11) || ($fMod10 == 1 && $fMod100 != 11)) return self::PLURAL_ONE;
+               if (($v == 0 && $mod10 >= 2 && $mod10 <= 4 && $mod100 >= 12 && $mod100 <= 14)
+                       || ($fMod10 >= 2 && $fMod10 <= 4 && $fMod100 >= 12 && $fMod100 <= 14)) return self::PLURAL_FEW;
+       }
+               
+       // Czech
+       private static function cs($n) {
+               $v = self::getV($n);
+               
+               if ($n == 1 && $v === 0) return self::PLURAL_ONE;
+               if ($n >= 2 && $n <= 4 && $v === 0) return self::PLURAL_FEW;
+               if ($v === 0) return self::PLURAL_MANY;
+       }
+       
+       // Welsh
+       private static function cy($n) {
+               if ($n == 0) return self::PLURAL_ZERO;
+               if ($n == 1) return self::PLURAL_ONE;
+               if ($n == 2) return self::PLURAL_TWO;
+               if ($n == 3) return self::PLURAL_FEW;
+               if ($n == 6) return self::PLURAL_MANY;
+       }
+       
+       // Danish
+       private static function da($n) {
+               if ($n > 0 && $n < 2) return self::PLURAL_ONE;
+       }
+       
+       // Greek
+       private static function el($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+
+       // Catalan (ca)
+       // German (de)
+       // English (en)
+       // Estonian (et)
+       // Finnish (fi)
+       // Italian (it)
+       // Dutch (nl)
+       // Swedish (sv)
+       // Swahili (sw)
+       // Urdu (ur)
+       private static function en($n) {
+               if ($n == 1 && self::getV($n) === 0) return self::PLURAL_ONE;
+       }
+       
+       // Spanish
+       private static function es($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Basque
+       private static function eu($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Persian
+       private static function fa($n) {
+               if ($n >= 0 && $n <= 1) return self::PLURAL_ONE;
+       }
+       
+       // French
+       private static function fr($n) {
+               if ($n >= 0 && $n < 2) return self::PLURAL_ONE;
+       }
+       
+       // Irish
+       private static function ga($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+               if ($n == 2) return self::PLURAL_TWO;
+               if ($n == 3 || $n == 4 || $n == 5 || $n == 6) return self::PLURAL_FEW;
+               if ($n == 7 || $n == 8 || $n == 9 || $n == 10) return self::PLURAL_MANY;
+       }
+       
+       // Gujarati
+       private static function gu($n) {
+               if ($n >= 0 && $n <= 1) return self::PLURAL_ONE;
+       }
+       
+       // Hebrew
+       private static function he($n) {
+               $v = self::getV($n);
+               
+               if ($n == 1 && $v === 0) return self::PLURAL_ONE;
+               if ($n == 2 && $v === 0) return self::PLURAL_TWO;
+               if ($n > 10 && $v === 0 && $n % 10 == 0) return self::PLURAL_MANY;
+       }
+       
+       // Hindi
+       private static function hi($n) {
+               if ($n >= 0 && $n <= 1) return self::PLURAL_ONE;
+       }
+       
+       // Croatian
+       private static function hr($n) {
+               // same as Bosnian
+               return self::bs($n);
+       }
+       
+       // Hungarian
+       private static function hu($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Armenian
+       private static function hy($n) {
+               if ($n >= 0 && $n < 2) return self::PLURAL_ONE;
+       }
+       
+       // Indonesian
+       private static function id($n) {}
+       
+       // Icelandic
+       private static function is($n) {
+               $f = self::getF($n);
+               
+               if ($f === 0 && $n % 10 === 1 && !($n % 100 === 11) || !($f === 0)) return self::PLURAL_ONE;
+       }
+       
+       // Japanese
+       private static function ja($n) {}
+       
+       // Javanese
+       private static function jv($n) {}
+       
+       // Georgian
+       private static function ka($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Kazakh
+       private static function kk($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Khmer
+       private static function km($n) {}
+       
+       // Kannada
+       private static function kn($n) {
+               if ($n >= 0 && $n <= 1) return self::PLURAL_ONE;
+       }
+       
+       // Korean
+       private static function ko($n) {}
+       
+       // Kurdish
+       private static function ku($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Kyrgyz
+       private static function ky($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Luxembourgish
+       private static function lb($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Lao
+       private static function lo($n) {}
+       
+       // Lithuanian
+       private static function lt($n) {
+               $mod10 = $n % 10;
+               $mod100 = $n % 100;
+               
+               if ($mod10 == 1 && !($mod100 >= 11 && $mod100 <= 19)) return self::PLURAL_ONE;
+               if ($mod10 >= 2 && $mod10 <= 9 && !($mod100 >= 11 && $mod100 <= 19)) return self::PLURAL_FEW;
+               if (self::getF($n) != 0) return self::PLURAL_MANY;
+       }
+       
+       // Latvian
+       private static function lv($n) {
+               $mod10 = $n % 10;
+               $mod100 = $n % 100;
+               $v = self::getV($n);
+               $f = self::getF($n);
+               $fMod10 = $f % 10;
+               $fMod100 = $f % 100;
+               
+               if ($mod10 == 0 || ($mod100 >= 11 && $mod100 <= 19) || ($v == 2 && $fMod100 >= 11 && $fMod100 <= 19)) return self::PLURAL_ZERO;
+               if (($mod10 == 1 && $mod100 != 11) || ($v == 2 && $fMod10 == 1 && $fMod100 != 11) || ($v != 2 && $fMod10 == 1)) return self::PLURAL_ONE;
+       }
+       
+       // Macedonian
+       private static function mk($n) {
+               $v = self::getV($n);
+               $f = self::getF($n);
+               $mod10 = $n % 10;
+               $mod100 = $n % 100;
+               $fMod10 = $f % 10;
+               $fMod100 = $f % 100;
+               
+               if (($v == 0 && $mod10 == 1 && $mod100 != 11) || ($fMod10 == 1 && $fMod100 != 11)) return self::PLURAL_ONE;
+       }
+       
+       // Malayalam
+       private static function ml($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Mongolian 
+       private static function mn($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Marathi 
+       private static function mr($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Malay 
+       private static function ms($n) {}
+       
+       // Maltese 
+       private static function mt($n) {
+               $mod100 = $n % 100;
+               
+               if ($n == 1) return self::PLURAL_ONE;
+               if ($n == 0 || ($mod100 >= 2 && $mod100 <= 10)) return self::PLURAL_FEW;
+               if ($mod100 >= 11 && $mod100 <= 19) return self::PLURAL_MANY;
+       }
+       
+       // Burmese
+       private static function my($n) {}
+       
+       // Norwegian
+       private static function no($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Nepali
+       private static function ne($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Odia
+       private static function or($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Punjabi
+       private static function pa($n) {
+               if ($n == 1 || $n == 0) return self::PLURAL_ONE;
+       }
+       
+       // Polish
+       private static function pl($n) {
+               $v = self::getV($n);
+               $mod10 = $n % 10;
+               $mod100 = $n % 100;
+               
+               if ($n == 1 && $v == 0) return self::PLURAL_ONE;
+               if ($v == 0 && $mod10 >= 2 && $mod10 <= 4 && !($mod100 >= 12 && $mod100 <= 14)) return self::PLURAL_FEW;
+               if ($v == 0 && (($n != 1 && $mod10 >= 0 && $mod10 <= 1) || ($mod10 >= 5 && $mod10 <= 9) || ($mod100 >= 12 && $mod100 <= 14))) return self::PLURAL_MANY;
+       }
+       
+       // Pashto
+       private static function ps($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Portuguese
+       private static function pt($n) {
+               if ($n >= 0 && $n < 2) return self::PLURAL_ONE;
+       }
+       
+       // Romanian
+       private static function ro($n) {
+               $v = self::getV($n);
+               $mod100 = $n % 100;
+               
+               if ($n == 1 && $v === 0) return self::PLURAL_ONE;
+               if ($v != 0 || $n == 0 || ($mod100 >= 2 && $mod100 <= 19)) return self::PLURAL_FEW;
+       }
+       
+       // Russian
+       private static function ru($n) {
+               $mod10 = $n % 10;
+               $mod100 = $n % 100;
+               
+               if (self::getV($n) == 0) {
+                       if ($mod10 == 1 && $mod100 != 11) return self::PLURAL_ONE;
+                       if ($mod10 >= 2 && $mod10 <= 4 && !($mod100 >= 12 && $mod100 <= 14)) return self::PLURAL_FEW;
+                       if ($mod10 == 0 || ($mod10 >= 5 && $mod10 <= 9) || ($mod100 >= 11 && $mod100 <= 14)) return self::PLURAL_MANY; 
+               }
+       }
+       
+       // Sindhi
+       private static function sd($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Sinhala
+       private static function si($n) {
+               if ($n == 0 || $n == 1 || (floor($n) == 0 && self::getF($n) == 1)) return self::PLURAL_ONE;
+       }
+       
+       // Slovak
+       private static function sk($n) {
+               // same as Czech
+               return self::cs($n);
+       }
+       
+       // Slovenian
+       private static function sl($n) {
+               $v = self::getV($n);
+               $mod100 = $n % 100;
+               
+               if ($v == 0 && $mod100 == 1) return self::PLURAL_ONE;
+               if ($v == 0 && $mod100 == 2) return self::PLURAL_TWO;
+               if (($v == 0 && ($mod100 == 3 || $mod100 == 4)) || $v != 0) return self::PLURAL_FEW;
+       }
+       
+       // Albanian
+       private static function sq($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Serbian
+       private static function sr($n) {
+               // same as Bosnian
+               return self::bs($n);
+       }
+       
+       // Tamil
+       private static function ta($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Telugu
+       private static function te($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Tajik
+       private static function tg($n) {}
+       
+       // Thai
+       private static function th($n) {}
+       
+       // Turkmen
+       private static function tk($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Turkish
+       private static function tr($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Uyghur
+       private static function ug($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Ukrainian
+       private static function uk($n) {
+               // same as Russian
+               return self::ru($n);
+       }
+       
+       // Uzbek
+       private static function uz($n) {
+               if ($n == 1) return self::PLURAL_ONE;
+       }
+       
+       // Vietnamese
+       private static function vi($n) {}
+       
+       // Chinese
+       private static function zh($n) {}
+}
index 49913a9a8c8f8ae91ebcd337dbbbdbfdbe10a460..4a49f30bf80024bf2f25c9ca583d26a3a4a9e834 100644 (file)
@@ -4,6 +4,8 @@ use wcf\data\user\menu\item\UserMenuItem;
 use wcf\system\cache\builder\UserMenuCacheBuilder;
 use wcf\system\menu\ITreeMenuItem;
 use wcf\system\menu\TreeMenu;
+use wcf\system\option\user\UserOptionHandler;
+use wcf\system\WCF;
 
 /**
  * Builds the user menu.
@@ -14,6 +16,12 @@ use wcf\system\menu\TreeMenu;
  * @package    WoltLabSuite\Core\System\Menu\User
  */
 class UserMenu extends TreeMenu {
+       /**
+        * user option handler for the `settings` category
+        * @var UserOptionHandler
+        */
+       protected $optionHandler;
+       
        /**
         * @inheritDoc
         */
@@ -21,6 +29,8 @@ class UserMenu extends TreeMenu {
                parent::loadCache();
                
                $this->menuItems = UserMenuCacheBuilder::getInstance()->getData();
+               $this->optionHandler = new UserOptionHandler(false, '', 'settings');
+               $this->optionHandler->setUser(WCF::getUser());
        }
        
        /**
@@ -31,6 +41,14 @@ class UserMenu extends TreeMenu {
                
                if (!parent::checkMenuItem($item)) return false;
                
+               // Hide links to user option categories without accessible options.
+               if (strpos($item->menuItem, 'wcf.user.option.category.') === 0) {
+                       $categoryName = str_replace('wcf.user.option.category.', '', $item->menuItem);
+                       if (empty($this->optionHandler->getOptionTree($categoryName))) {
+                               return false;
+                       }
+               }
+               
                return $item->getProcessor()->isVisible();
        }
 }
index d9973e82a8894c96a87858a919bc637e6afd3245..fee2f48b81785e5f0d0cb8e1bd06bd81deabe8a1 100644 (file)
@@ -12,6 +12,7 @@ use wcf\system\form\builder\container\FormContainer;
 use wcf\system\form\builder\field\BooleanFormField;
 use wcf\system\form\builder\field\ClassNameFormField;
 use wcf\system\form\builder\field\IntegerFormField;
+use wcf\system\form\builder\field\ItemListFormField;
 use wcf\system\form\builder\field\option\OptionFormField;
 use wcf\system\form\builder\field\SingleSelectionFormField;
 use wcf\system\form\builder\field\TextFormField;
@@ -232,7 +233,7 @@ class EventListenerPackageInstallationPlugin extends AbstractXMLPackageInstallat
                                ->required()
                                ->instantiable(false),
                        
-                       TextFormField::create('eventName')
+                       ItemListFormField::create('eventName')
                                ->objectProperty('eventname')
                                ->label('wcf.acp.pip.eventListener.eventName')
                                ->description('wcf.acp.pip.eventListener.eventName.description')
index 97d19c07726cce8167a2cdecbde7cd3c5fd775cb..9452a6c2cb9125c5bd2d2b01b2b866787123ea4e 100644 (file)
@@ -6,7 +6,6 @@ use wcf\data\object\type\ObjectTypeEditor;
 use wcf\data\DatabaseObjectList;
 use wcf\data\page\PageNode;
 use wcf\data\page\PageNodeTree;
-use wcf\data\user\group\option\UserGroupOption;
 use wcf\system\application\ApplicationHandler;
 use wcf\system\condition\AbstractIntegerCondition;
 use wcf\system\condition\UserGroupCondition;
@@ -42,7 +41,7 @@ use wcf\util\DirectoryUtil;
  * Installs, updates and deletes object types.
  * 
  * @author     Alexander Ebert, Matthias Schmidt
- * @copyright  2001-2019 WoltLab GmbH
+ * @copyright  2001-2020 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\Acp\Package\Plugin
  */
index 6971bb702cc02cfa5b2a00e3c46302a86fc8a85b..6b2cd2bd962655e6490ffa11256e0cb11290c936 100644 (file)
@@ -69,11 +69,10 @@ class StylePackageInstallationPlugin extends AbstractPackageInstallationPlugin {
                        $styleList->sqlOrderBy = 'style.styleID ASC';
                        $styleList->sqlLimit = 1;
                        $styleList->readObjects();
-                       $styles = $styleList->getObjects();
+                       $style = $styleList->getSingleObject();
                        
-                       if (!empty($styles)) {
-                               $styleEditor = new StyleEditor(current($styles));
-                               $styleEditor->setAsDefault();
+                       if ($style !== null) {
+                               (new StyleEditor($style))->setAsDefault();
                        }
                }
        }
index ee8b4b57bac826983dede87cfafc7334d41c4cfa..799c3b694d8e1207dd6ebda16505b70129bd4d80 100644 (file)
@@ -789,4 +789,23 @@ class ReactionHandler extends SingletonFactory {
                
                return null;
        }
+       
+       /**
+        * Renders an inline list of reaction counts.
+        * 
+        * @param       int[]   $reactionCounts         format: `[reactionID => count]`
+        * @return      string
+        * @since       5.3
+        */
+       public function renderInlineList(array $reactionCounts) {
+               $reactionsOuput = [];
+               foreach ($reactionCounts as $reactionTypeID => $count) {
+                       $reactionsOuput[] = WCF::getLanguage()->getDynamicVariable('wcf.reactions.reactionTypeCount', [
+                               'count' => $count,
+                               'reaction' => $this->getReactionTypeByID($reactionTypeID),
+                       ]);
+               }
+               
+               return implode(',', $reactionsOuput);
+       }
 }
index f0359c64268da2430c4279dc152dcef267b293a0..f39fd1125babe3193029b6e27afa3bf80bc3de62 100644 (file)
@@ -2,7 +2,6 @@
 namespace wcf\system\search\acp;
 use wcf\data\package\Package;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
-use wcf\system\request\LinkHandler;
 use wcf\system\WCF;
 
 /**
@@ -59,10 +58,7 @@ class PackageACPSearchResultProvider implements IACPSearchResultProvider {
                
                /** @var Package $package */
                while ($package = $statement->fetchObject(Package::class)) {
-                       $results[] = new ACPSearchResult($package->getName(), LinkHandler::getInstance()->getLink('Package', [
-                               'id' => $package->packageID,
-                               'title' => $package->getName()
-                       ]));
+                       $results[] = new ACPSearchResult($package->getName(), $package->getLink());
                }
                
                return $results;
diff --git a/wcfsetup/install/files/lib/system/template/plugin/AnchorFunctionTemplatePlugin.class.php b/wcfsetup/install/files/lib/system/template/plugin/AnchorFunctionTemplatePlugin.class.php
new file mode 100644 (file)
index 0000000..75c6efd
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+namespace wcf\system\template\plugin;
+use wcf\data\ILinkableObject;
+use wcf\data\IPopoverObject;
+use wcf\data\ITitledLinkObject;
+use wcf\data\ITitledObject;
+use wcf\system\template\TemplateEngine;
+use wcf\util\ClassUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Template function plugin which generate `a` HTML elements.
+ * 
+ * Required parameters are either:
+ *     `object` (`ITitledLinkObject`)
+ * or both:
+ *     `link` (`ILinkableObject`)
+ *     `content` (`ITitledObject`, object with `__toString()` method, or string)
+ * 
+ * The only additional parameter that is treated in a special way is `append` whose value is appended
+ * to the link.
+ * All other additional parameter values are added as attributes to the `a` element. Parameter names
+ * in camel case are changed to kebab case (`fooBar` becomes `foo-bar`).
+ * 
+ * The only additional parameter name that is disallowed is `href`.
+ *
+ * Usage:
+ *     {anchor object=$object data-foo='bar'}
+ *     {anchor object=$object}
+ *     {anchor object=$object append='#anchor'}
+ *     {anchor link=$linkObject content=$titleObject}
+ *     {anchor link=$linkObject content='Title'}
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Template\Plugin
+ * @since      5.3
+ */
+class AnchorFunctionTemplatePlugin implements IFunctionTemplatePlugin {
+       /**
+        * @inheritDoc
+        */
+       public function execute($tagArgs, TemplateEngine $tplObj) {
+               $link = $content = $object = null;
+               if (isset($tagArgs['object'])) {
+                       $object = $tagArgs['object'];
+                       unset($tagArgs['object']);
+                       
+                       if (!($object instanceof ITitledLinkObject) && !ClassUtil::isDecoratedInstanceOf($object, ITitledLinkObject::class)) {
+                               throw new \InvalidArgumentException("'object' attribute does not implement interface '" . ITitledLinkObject::class . "'.");
+                       }
+                       
+                       $link = $object->getLink();
+                       $content = $object->getTitle();
+               }
+               else if (isset($tagArgs['link']) && isset($tagArgs['content'])) {
+                       if (!($tagArgs['link'] instanceof ILinkableObject) && !ClassUtil::isDecoratedInstanceOf($tagArgs['link'], ITitledLinkObject::class)) {
+                               throw new \InvalidArgumentException("'link' attribute does not implement interface '" . ILinkableObject::class . "'.");
+                       }
+                       
+                       $link = $tagArgs['link']->getLink();
+                       unset($tagArgs['link']);
+                       
+                       if (is_object($tagArgs['content'])) {
+                               if ($tagArgs['content'] instanceof ITitledObject || ClassUtil::isDecoratedInstanceOf($tagArgs['content'], ITitledObject::class)) {
+                                       $content = $tagArgs['content']->getTitle();
+                               }
+                               else if (method_exists($tagArgs['content'], '__toString')) {
+                                       $content = (string)$tagArgs['content'];
+                               }
+                               else {
+                                       throw new \InvalidArgumentException("'content' object does not implement " . ITitledObject::class . ".");
+                               }
+                       }
+                       else if (is_string($tagArgs['content']) || is_numeric($tagArgs['content'])) {
+                               $content = $tagArgs['content'];
+                       }
+                       else {
+                               throw new \InvalidArgumentException("'content' attribute is of invalid type " . gettype($tagArgs['content']) . ".");
+                       }
+                       unset($tagArgs['content']);
+               }
+               else {
+                       throw new \InvalidArgumentException("Missing 'object' attribute or 'link' and 'content' attributes.");
+               }
+               
+               if (isset($tagArgs['href'])) {
+                       throw new \InvalidArgumentException("'href' attribute is not allowed.");
+               }
+               
+               $append = '';
+               if (isset($tagArgs['append'])) {
+                       $append = $tagArgs['append'];
+                       unset($tagArgs['append']);
+               }
+               
+               $classes = [];
+               $additionalParameters = '';
+               foreach ($tagArgs as $name => $value) {
+                       if (!preg_match('~^[a-z]+([A-z]+)+$~', $name)) {
+                               throw new \InvalidArgumentException("Invalid additional argument name '{$name}'.");
+                       }
+                       
+                       if ($name === 'class') {
+                               $classes = explode(' ', $value);
+                       }
+                       
+                       $additionalParameters .= ' ' . strtolower(preg_replace('~([A-Z])~', '-$1', $name)) . '="' . StringUtil::encodeHTML($value) . '"';
+               }
+               
+               if (
+                       $object !== null
+                       && ($object instanceof IPopoverObject || ClassUtil::isDecoratedInstanceOf($object, IPopoverObject::class))
+                       && in_array($object->getPopoverLinkClass(), $classes)
+               ) {
+                       $additionalParameters .= ' data-object-id="' . $object->getObjectID() . '"';
+               }
+               
+               return '<a href="' . StringUtil::encodeHTML($link . $append) . '"' . $additionalParameters . '>' . StringUtil::encodeHTML($content) . '</a>';
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/template/plugin/PluralFunctionTemplatePlugin.class.php b/wcfsetup/install/files/lib/system/template/plugin/PluralFunctionTemplatePlugin.class.php
new file mode 100644 (file)
index 0000000..1867718
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+namespace wcf\system\template\plugin;
+use wcf\system\exception\SystemException;
+use wcf\system\language\I18nPlural;
+use wcf\system\template\TemplateEngine;
+use wcf\util\StringUtil;
+
+/**
+ * Template function plugin which generate plural phrases.
+ *
+ * Languages vary in how they handle plurals of nouns or unit expressions.
+ * Some languages have two forms, like English; some languages have only a
+ * single form; and some languages have multiple forms.
+ * 
+ * Supported parameters:
+ * value (number|array|Countable - required)
+ * other (string - required)
+ * zero (string), one (string), two (string), few (string), many (string)
+ * 
+ * Usage:
+ *      {plural value=$number zero='0' one='1' two='2' few='few' many='many' other='#'}
+ *      There {plural value=$worlds one='is one world' other='are # worlds'}
+ *      Updated {plural value=$minutes 0='just now' 1='one minute ago' other='# minutes ago'}
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Template\Plugin
+ * @since      5.3
+ */
+class PluralFunctionTemplatePlugin implements IFunctionTemplatePlugin {
+       /**
+        * @inheritDoc
+        */
+       public function execute($tagArgs, TemplateEngine $tplObj) {
+               if (!isset($tagArgs['value'])) {
+                       throw new SystemException("Missing attribute 'value'");
+               }
+               if (!isset($tagArgs['other'])) {
+                       throw new SystemException("Missing attribute 'other'");
+               }
+               
+               $value = $tagArgs['value'];
+               if (is_countable($value)) {
+                       $value = count($value);
+               }
+               
+               // handle numeric attributes
+               foreach ($tagArgs as $key => $_value) {
+                       if (is_numeric($key)) {
+                               if ($key == $value) return $_value;
+                       }
+               }
+               
+               $category = I18nPlural::getCategory($value);
+               if (!isset($tagArgs[$category])) {
+                       $category = I18nPlural::PLURAL_OTHER;
+               }
+               
+               $string = $tagArgs[$category];
+               if (strpos($string, '#') !== false) {
+                       return str_replace('#', StringUtil::formatNumeric($value), $string);
+               }
+               
+               return $string;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/template/plugin/UserFunctionTemplatePlugin.class.php b/wcfsetup/install/files/lib/system/template/plugin/UserFunctionTemplatePlugin.class.php
new file mode 100644 (file)
index 0000000..ad6d9ec
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+namespace wcf\system\template\plugin;
+use wcf\data\user\UserProfile;
+use wcf\system\template\TemplateEngine;
+use wcf\util\ClassUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Template function plugin which generates links to user profiles.
+ * 
+ * Attributes:
+ * - `object` (required) has to be a (decorated) `UserProfile` object.
+ * - `type` (optional) supports the following values:
+ *      - `default` (default value) generates a link with the formatted username with popover support.
+ *      - `avatarXY` generates a link with the user's avatar in size `XY`.
+ *      - `plain` generates a link link without username formatting and popover support
+ * - `append` (optional) is appended to the user link.
+ * 
+ * All other additional parameter values are added as attributes to the `a` element. Parameter names
+ * in camel case are changed to kebab case (`fooBar` becomes `foo-bar`).
+ *
+ * Usage:
+ *      {user object=$user}
+ *      {user object=$user type='plain'}
+ *      {user object=$user type='avatar48'}
+ *      {user object=$user append='#wall'}
+ * 
+ * @author      Matthias Schmidt
+ * @copyright   2001-2020 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package     WoltLabSuite\Core\System\Template\Plugin
+ * @since       5.3
+ */
+class UserFunctionTemplatePlugin implements IFunctionTemplatePlugin {
+       /**
+        * @inheritDoc
+        */
+       public function execute($tagArgs, TemplateEngine $tplObj) {
+               if (!isset($tagArgs['object'])) {
+                       throw new \InvalidArgumentException("Missing 'object' attribute.");
+               }
+               
+               $object = $tagArgs['object'];
+               unset($tagArgs['object']);
+               if (!($object instanceof UserProfile) && !ClassUtil::isDecoratedInstanceOf($object, UserProfile::class)) {
+                       throw new \InvalidArgumentException("'object' attribute is no '" . UserProfile::class . "' object.");
+               }
+               
+               $additionalParameters = '';
+               $content = '';
+               if (isset($tagArgs['type'])) {
+                       $type = $tagArgs['type'];
+                       unset($tagArgs['type']);
+                       
+                       if ($type === 'plain') {
+                               $content = $object->getTitle();
+                       }
+                       else if (preg_match('~^avatar(\d+)$~', $type, $matches)) {
+                               $content = $object->getAvatar()->getImageTag($matches[1]);
+                       }
+                       else if ($type !== 'default') {
+                               throw new \InvalidArgumentException("Unknown 'type' value '{$type}'.");
+                       }
+               }
+               
+               // default case
+               if ($content === '') {
+                       $additionalParameters = ' data-object-id="' . $object->getObjectID() . '"';
+                       $content = $object->getFormattedUsername();
+                       if (isset($tagArgs['class'])) {
+                               $tagArgs['class'] = 'userLink ' . $tagArgs['class'];
+                       }
+                       else {
+                               $tagArgs['class'] = 'userLink';
+                       }
+               }
+               
+               $append = '';
+               if (isset($tagArgs['append'])) {
+                       $append = $tagArgs['append'];
+                       unset($tagArgs['append']);
+               }
+               
+               foreach ($tagArgs as $name => $value) {
+                       if (!preg_match('~^[a-z]+([A-z]+)+$~', $name)) {
+                               throw new \InvalidArgumentException("Invalid additional argument name '{$name}'.");
+                       }
+                       
+                       $additionalParameters .= ' ' . strtolower(preg_replace('~([A-Z])~', '-$1', $name))
+                               . '="' . StringUtil::encodeHTML($value) . '"';
+               }
+               
+               return '<a href="' . StringUtil::encodeHTML($object->getLink() . $append) . '"' . $additionalParameters . '>' . $content . '</a>';
+       }
+}
index 3a79a15d75a65b12fd00d99b1fae740d22b66208..05b6125350f2313343530aea4f3a6c05e5ddcd82 100644 (file)
@@ -52,13 +52,13 @@ class DefaultUploadFileValidationStrategy implements IUploadFileValidationStrate
                        return false;
                }
                
-               if ($uploadFile->getFilesize() > $this->maxFilesize) {
-                       $uploadFile->setValidationErrorType('tooLarge');
+               if (!preg_match($this->fileExtensionRegex, mb_strtolower($uploadFile->getFilename()))) {
+                       $uploadFile->setValidationErrorType('invalidExtension');
                        return false;
                }
                
-               if (!preg_match($this->fileExtensionRegex, mb_strtolower($uploadFile->getFilename()))) {
-                       $uploadFile->setValidationErrorType('invalidExtension');
+               if ($uploadFile->getFilesize() > $this->maxFilesize) {
+                       $uploadFile->setValidationErrorType('tooLarge');
                        return false;
                }
                
index e7327424cf2621c4d3d7163298bf26398a54005c..c5f3d3f0ce421127e4ba3f8cf154a97dd0cb8649 100644 (file)
@@ -1,59 +1,32 @@
 <?php
 namespace wcf\system\user\activity\event;
 use wcf\data\article\ViewableArticleList;
-use wcf\data\comment\response\CommentResponseList;
-use wcf\data\comment\CommentList;
-use wcf\data\user\UserList;
 use wcf\system\SingletonFactory;
 use wcf\system\WCF;
 
 /**
  * User activity event implementation for responses to article comments.
- *
+ * 
  * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
+ * @copyright  2001-2020 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Activity\Event
  * @since      3.0
  */
 class ArticleCommentResponseUserActivityEvent extends SingletonFactory implements IUserActivityEvent {
+       use TCommentResponseUserActivityEvent;
+       
        /**
         * @inheritDoc
         */
        public function prepare(array $events) {
-               $responseIDs = [];
-               foreach ($events as $event) {
-                       $responseIDs[] = $event->objectID;
-               }
-               
-               // fetch responses
-               $responseList = new CommentResponseList();
-               $responseList->setObjectIDs($responseIDs);
-               $responseList->readObjects();
-               $responses = $responseList->getObjects();
-               
-               // fetch comments
-               $commentIDs = $comments = [];
-               foreach ($responses as $response) {
-                       $commentIDs[] = $response->commentID;
-               }
-               if (!empty($commentIDs)) {
-                       $commentList = new CommentList();
-                       $commentList->setObjectIDs($commentIDs);
-                       $commentList->readObjects();
-                       $comments = $commentList->getObjects();
-               }
+               $this->readResponseData($events);
                
                // fetch articles
-               $articleContentIDs = [];
-               foreach ($comments as $comment) {
-                       $articleContentIDs[] = $comment->objectID;
-               }
-               
                $articles = $articleContentToArticle = [];
-               if (!empty($articleContentIDs)) {
+               if (!empty($this->commentObjectIDs)) {
                        $articleList = new ViewableArticleList();
-                       $articleList->getConditionBuilder()->add("article.articleID IN (SELECT articleID FROM wcf".WCF_N."_article_content WHERE articleContentID IN (?))", [$articleContentIDs]);
+                       $articleList->getConditionBuilder()->add("article.articleID IN (SELECT articleID FROM wcf".WCF_N."_article_content WHERE articleContentID IN (?))", [$this->commentObjectIDs]);
                        $articleList->readObjects();
                        foreach ($articleList as $article) {
                                $articles[$article->articleID] = $article;
@@ -62,24 +35,12 @@ class ArticleCommentResponseUserActivityEvent extends SingletonFactory implement
                        }
                }
                
-               // fetch users
-               $userIDs = $users = [];
-               foreach ($comments as $comment) {
-                       $userIDs[] = $comment->userID;
-               }
-               if (!empty($userIDs)) {
-                       $userList = new UserList();
-                       $userList->setObjectIDs($userIDs);
-                       $userList->readObjects();
-                       $users = $userList->getObjects();
-               }
-               
                // set message
                foreach ($events as $event) {
-                       if (isset($responses[$event->objectID])) {
-                               $response = $responses[$event->objectID];
-                               $comment = $comments[$response->commentID];
-                               if (isset($articleContentToArticle[$comment->objectID]) && isset($users[$comment->userID])) {
+                       if (isset($this->responses[$event->objectID])) {
+                               $response = $this->responses[$event->objectID];
+                               $comment = $this->comments[$response->commentID];
+                               if (isset($articleContentToArticle[$comment->objectID]) && isset($this->commentAuthors[$comment->userID])) {
                                        $article = $articles[$articleContentToArticle[$comment->objectID]];
                                        
                                        // check permissions
@@ -90,10 +51,10 @@ class ArticleCommentResponseUserActivityEvent extends SingletonFactory implement
                                        
                                        // title
                                        $text = WCF::getLanguage()->getDynamicVariable('wcf.article.recentActivity.articleCommentResponse', [
-                                               'commentAuthor' => $users[$comment->userID],
+                                               'commentAuthor' => $this->commentAuthors[$comment->userID],
                                                'commentID' => $comment->commentID,
                                                'responseID' => $response->responseID,
-                                               'article' => $article
+                                               'article' => $article,
                                        ]);
                                        $event->setTitle($text);
                                        
index 65d71f4221308747627d3c138ae9eab16e896dff..2d76ff634c49e4318a240e5bcd0419df3a0ecf91 100644 (file)
@@ -1,69 +1,33 @@
 <?php
 namespace wcf\system\user\activity\event;
-use wcf\data\comment\response\CommentResponseList;
-use wcf\data\comment\CommentList;
 use wcf\data\page\PageCache;
-use wcf\data\user\UserList;
 use wcf\system\SingletonFactory;
 use wcf\system\WCF;
 
 /**
  * User activity event implementation for responses to page comments.
- *
+ * 
  * @author     Joshua Ruesweg
- * @copyright  2001-2019 WoltLab GmbH
+ * @copyright  2001-2020 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Activity\Event
  * @since      5.2
  */
 class PageCommentResponseUserActivityEvent extends SingletonFactory implements IUserActivityEvent {
+       use TCommentResponseUserActivityEvent;
+       
        /**
         * @inheritDoc
         */
        public function prepare(array $events) {
-               $responseIDs = [];
-               foreach ($events as $event) {
-                       $responseIDs[] = $event->objectID;
-               }
-               
-               // fetch responses
-               $responseList = new CommentResponseList();
-               $responseList->setObjectIDs($responseIDs);
-               $responseList->readObjects();
-               $responses = $responseList->getObjects();
-               
-               // fetch comments
-               $commentIDs = $comments = [];
-               foreach ($responses as $response) {
-                       $commentIDs[] = $response->commentID;
-               }
-               
-               if (!empty($commentIDs)) {
-                       $commentList = new CommentList();
-                       $commentList->setObjectIDs($commentIDs);
-                       $commentList->readObjects();
-                       $comments = $commentList->getObjects();
-               }
-               
-               // fetch users
-               $userIDs = $users = [];
-               foreach ($comments as $comment) {
-                       $userIDs[] = $comment->userID;
-               }
-               
-               if (!empty($userIDs)) {
-                       $userList = new UserList();
-                       $userList->setObjectIDs($userIDs);
-                       $userList->readObjects();
-                       $users = $userList->getObjects();
-               }
+               $this->readResponseData($events);
                
                // set message
                foreach ($events as $event) {
-                       if (isset($responses[$event->objectID])) {
-                               $response = $responses[$event->objectID];
-                               $comment = $comments[$response->commentID];
-                               if (PageCache::getInstance()->getPage($comment->objectID) && isset($users[$comment->userID])) {
+                       if (isset($this->responses[$event->objectID])) {
+                               $response = $this->responses[$event->objectID];
+                               $comment = $this->comments[$response->commentID];
+                               if (PageCache::getInstance()->getPage($comment->objectID) && isset($this->commentAuthors[$comment->userID])) {
                                        $page = PageCache::getInstance()->getPage($comment->objectID);
                                        
                                        // check permissions
@@ -74,10 +38,10 @@ class PageCommentResponseUserActivityEvent extends SingletonFactory implements I
                                        
                                        // title
                                        $text = WCF::getLanguage()->getDynamicVariable('wcf.page.recentActivity.pageCommentResponse', [
-                                               'commentAuthor' => $users[$comment->userID],
+                                               'commentAuthor' => $this->commentAuthors[$comment->userID],
                                                'commentID' => $comment->commentID,
                                                'responseID' => $response->responseID,
-                                               'page' => $page
+                                               'page' => $page,
                                        ]);
                                        $event->setTitle($text);
                                        
index 9e975638a445640fd75cd99943ef8633b4ab0a4b..7aa05d28e489a089f0b5b67a7b22dba3729b9ffc 100644 (file)
@@ -1,7 +1,5 @@
 <?php
 namespace wcf\system\user\activity\event;
-use wcf\data\comment\response\CommentResponseList;
-use wcf\data\comment\CommentList;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
 use wcf\system\SingletonFactory;
 use wcf\system\WCF;
@@ -10,11 +8,13 @@ use wcf\system\WCF;
  * User activity event implementation for profile comment responses.
  * 
  * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
+ * @copyright  2001-2020 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Activity\Event
  */
 class ProfileCommentResponseUserActivityEvent extends SingletonFactory implements IUserActivityEvent {
+       use TCommentResponseUserActivityEvent;
+       
        /**
         * @inheritDoc
         */
@@ -23,34 +23,12 @@ class ProfileCommentResponseUserActivityEvent extends SingletonFactory implement
                        return;
                }
                
-               $responseIDs = [];
-               foreach ($events as $event) {
-                       $responseIDs[] = $event->objectID;
-               }
-               
-               // fetch responses
-               $responseList = new CommentResponseList();
-               $responseList->setObjectIDs($responseIDs);
-               $responseList->readObjects();
-               $responses = $responseList->getObjects();
-               
-               // fetch comments
-               $commentIDs = $comments = [];
-               foreach ($responses as $response) {
-                       $commentIDs[] = $response->commentID;
-               }
-               if (!empty($commentIDs)) {
-                       $commentList = new CommentList();
-                       $commentList->setObjectIDs($commentIDs);
-                       $commentList->readObjects();
-                       $comments = $commentList->getObjects();
-               }
+               $this->readResponseData($events);
                
                // fetch users
                $userIDs = $users = [];
-               foreach ($comments as $comment) {
+               foreach ($this->comments as $comment) {
                        $userIDs[] = $comment->objectID;
-                       $userIDs[] = $comment->userID;
                }
                if (!empty($userIDs)) {
                        $users = UserProfileRuntimeCache::getInstance()->getObjects($userIDs);
@@ -58,19 +36,19 @@ class ProfileCommentResponseUserActivityEvent extends SingletonFactory implement
                
                // set message
                foreach ($events as $event) {
-                       if (isset($responses[$event->objectID])) {
-                               $response = $responses[$event->objectID];
-                               $comment = $comments[$response->commentID];
-                               if (isset($users[$comment->objectID]) && isset($users[$comment->userID])) {
+                       if (isset($this->responses[$event->objectID])) {
+                               $response = $this->responses[$event->objectID];
+                               $comment = $this->comments[$response->commentID];
+                               if (isset($users[$comment->objectID]) && isset($this->commentAuthors[$comment->userID])) {
                                        if (!$users[$comment->objectID]->isProtected()) {
                                                $event->setIsAccessible();
                                                
                                                // title
                                                $text = WCF::getLanguage()->getDynamicVariable('wcf.user.profile.recentActivity.profileCommentResponse', [
-                                                       'commentAuthor' => $users[$comment->userID],
+                                                       'commentAuthor' => $this->commentAuthors[$comment->userID],
                                                        'commentID' => $comment->commentID,
                                                        'responseID' => $response->responseID,
-                                                       'user' => $users[$comment->objectID]
+                                                       'user' => $users[$comment->objectID],
                                                ]);
                                                $event->setTitle($text);
                                                
diff --git a/wcfsetup/install/files/lib/system/user/activity/event/TCommentResponseUserActivityEvent.class.php b/wcfsetup/install/files/lib/system/user/activity/event/TCommentResponseUserActivityEvent.class.php
new file mode 100644 (file)
index 0000000..d505dfa
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+namespace wcf\system\user\activity\event;
+use wcf\data\comment\Comment;
+use wcf\data\comment\response\CommentResponse;
+use wcf\data\user\activity\event\ViewableUserActivityEvent;
+use wcf\data\user\UserProfile;
+use wcf\system\cache\runtime\CommentResponseRuntimeCache;
+use wcf\system\cache\runtime\CommentRuntimeCache;
+use wcf\system\cache\runtime\UserProfileRuntimeCache;
+
+/**
+ * Provides a method to read the comment response, comment, and user objects related to comment
+ * response user activity events.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\User\Activity\Event
+ * @since      5.3
+ */
+trait TCommentResponseUserActivityEvent {
+       /**
+        * user objects for the comment authors
+        * @var UserProfile[]
+        */
+       protected $commentAuthors = [];
+       
+       /**
+        * ids of the objects the comments belongs to
+        * @var int[] 
+        */
+       protected $commentObjectIDs = [];
+       
+       /**
+        * comment objects the responses belongs to
+        * @var Comment[]
+        */
+       protected $comments = [];
+       
+       /**
+        * comment response the comment response user activity events belong to
+        * @var CommentResponse[]
+        */
+       protected $responses = [];
+       
+       /**
+        * Reads the data of the comment responses the given events belong to.
+        * 
+        * @param       ViewableUserActivityEvent[]     $events
+        */
+       protected function readResponseData(array $events) {
+               $responseIDs = [];
+               foreach ($events as $event) {
+                       $responseIDs[] = $event->objectID;
+               }
+               
+               $this->responses = CommentResponseRuntimeCache::getInstance()->getObjects($responseIDs);
+               
+               $commentIDs = [];
+               foreach ($this->responses as $response) {
+                       $commentIDs[] = $response->commentID;
+               }
+               
+               if (!empty($commentIDs)) {
+                       $this->comments = CommentRuntimeCache::getInstance()->getObjects($commentIDs);
+               }
+               
+               $userIDs = [];
+               foreach ($this->comments as $comment) {
+                       $userIDs[] = $comment->userID;
+                       $this->commentObjectIDs[] = $comment->objectID;
+               }
+               if (!empty($userIDs)) {
+                       $this->commentAuthors = UserProfileRuntimeCache::getInstance()->getObjects($userIDs);
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/user/notification/event/ArticleLikeUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/ArticleLikeUserNotificationEvent.class.php
new file mode 100644 (file)
index 0000000..7b31e08
--- /dev/null
@@ -0,0 +1,146 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\article\category\ArticleCategory;
+use wcf\data\article\LikeableArticle;
+use wcf\data\user\UserProfile;
+use wcf\system\cache\runtime\ViewableArticleRuntimeCache;
+use wcf\system\user\notification\object\LikeUserNotificationObject;
+use wcf\system\user\storage\UserStorageHandler;
+use wcf\system\WCF;
+
+/**
+ * User notification event for post likes.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    WoltLab License <http://www.woltlab.com/license-agreement.html>
+ * @package    WoltLabSuite\Core\System\User\Notification\Event
+ * @since      5.3
+ * 
+ * @method     LikeUserNotificationObject      getUserNotificationObject()
+ */
+class ArticleLikeUserNotificationEvent extends AbstractSharedUserNotificationEvent implements ITestableUserNotificationEvent {
+       use TTestableLikeUserNotificationEvent {
+               TTestableLikeUserNotificationEvent::canBeTriggeredByGuests insteadof TTestableUserNotificationEvent;
+       }
+       use TTestableArticleUserNotificationEvent;
+       use TTestableCategorizedUserNotificationEvent;
+       use TTestableUserNotificationEvent;
+       use TReactionUserNotificationEvent;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $stackable = true;
+       
+       /**
+        * @inheritDoc
+        */
+       protected function prepare() {
+               ViewableArticleRuntimeCache::getInstance()->cacheObjectID($this->getUserNotificationObject()->objectID);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTitle() {
+               $count = count($this->getAuthors());
+               if ($count > 1) {
+                       return $this->getLanguage()->getDynamicVariable('wcf.article.like.notification.title.stacked', [
+                               'count' => $count,
+                               'timesTriggered' => $this->notification->timesTriggered
+                       ]);
+               }
+               
+               return $this->getLanguage()->getDynamicVariable('wcf.article.like.notification.title');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getMessage() {
+               $article = ViewableArticleRuntimeCache::getInstance()->getObject($this->getUserNotificationObject()->objectID);
+               $authors = array_values($this->getAuthors());
+               $count = count($authors);
+               
+               if ($count > 1) {
+                       return $this->getLanguage()->getDynamicVariable('wcf.article.like.notification.message.stacked', [
+                               'author' => $this->author,
+                               'authors' => $authors,
+                               'count' => $count,
+                               'others' => $count - 1,
+                               'article' => $article,
+                               'reactions' => $this->getReactionsForAuthors(),
+                       ]);
+               }
+               
+               return $this->getLanguage()->getDynamicVariable('wcf.article.like.notification.message', [
+                       'author' => $this->author,
+                       'article' => $article,
+                       'userNotificationObject' => $this->getUserNotificationObject(),
+                       'reactions' => $this->getReactionsForAuthors(),
+               ]);
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function getEmailMessage($notificationType = 'instant') {
+               // not supported
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLink() {
+               return ViewableArticleRuntimeCache::getInstance()->getObject($this->getUserNotificationObject()->objectID)->getLink();
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function supportsEmailNotification() {
+               return false;
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function getEventHash() {
+               return sha1($this->eventID . '-' . $this->additionalData['objectID']);
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function checkAccess() {
+               if (!ViewableArticleRuntimeCache::getInstance()->getObject($this->getUserNotificationObject()->objectID)->canRead()) {
+                       UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'wbbUnreadWatchedThreads');
+                       UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadArticles');
+                       UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadWatchedArticles');
+                       UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadArticlesByCategory');
+                       
+                       return false;
+               }
+               
+               return true;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected static function createTestLikeObject(UserProfile $recipient, UserProfile $author) {
+               return new LikeableArticle(self::getTestArticle(self::createTestCategory(ArticleCategory::OBJECT_TYPE_NAME), $author));
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected static function getTestLikeableObjectTypeName() {
+               return 'com.woltlab.wcf.likeableArticle';
+       }
+}
index 9e18b93990135c6f74d318bd11298d02c8b23c78..ce96adc2262c2f481882f020b3402230087a85b6 100644 (file)
@@ -3,7 +3,6 @@ namespace wcf\system\user\notification\event;
 use wcf\data\user\follow\UserFollow;
 use wcf\data\user\follow\UserFollowAction;
 use wcf\data\user\UserProfile;
-use wcf\system\request\LinkHandler;
 use wcf\system\user\notification\object\IUserNotificationObject;
 use wcf\system\user\notification\object\UserFollowUserNotificationObject;
 
@@ -11,7 +10,7 @@ use wcf\system\user\notification\object\UserFollowUserNotificationObject;
  * Notification event for followers.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2019 WoltLab GmbH
+ * @copyright  2001-2020 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Event
  * 
@@ -70,7 +69,7 @@ class UserFollowFollowingUserNotificationEvent extends AbstractUserNotificationE
         * @inheritDoc
         */
        public function getLink() {
-               return LinkHandler::getInstance()->getLink('User', ['object' => $this->author]);
+               return $this->author->getLink();
        }
        
        /**
index 202a765556fded23debae5a0d533115df1019566..e761464c13d23791fab3549c84bf6c787a2cff6e 100644 (file)
@@ -3,7 +3,6 @@ namespace wcf\system\user\notification\event;
 use wcf\data\user\UserProfile;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
 use wcf\system\comment\CommentHandler;
-use wcf\system\request\LinkHandler;
 use wcf\system\user\notification\object\LikeUserNotificationObject;
 use wcf\system\WCF;
 
@@ -96,7 +95,7 @@ class UserProfileCommentLikeUserNotificationEvent extends AbstractSharedUserNoti
                        $owner = UserProfileRuntimeCache::getInstance()->getObject($this->additionalData['objectID']);
                }
                
-               return LinkHandler::getInstance()->getLink('User', ['object' => $owner], '#wall/comment' . $this->getCommentID());
+               return $owner->getLink() . '#wall/comment' . $this->getCommentID();
        }
        
        /**
index f737828b4d63eddbccc894cbea848b3e267cae53..a927a1b7264691feee4907bda7ca4a934cff1a3c 100644 (file)
@@ -3,7 +3,6 @@ namespace wcf\system\user\notification\event;
 use wcf\data\user\UserProfile;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
 use wcf\system\comment\CommentHandler;
-use wcf\system\request\LinkHandler;
 use wcf\system\user\notification\object\LikeUserNotificationObject;
 use wcf\system\WCF;
 
@@ -103,11 +102,7 @@ class UserProfileCommentResponseLikeUserNotificationEvent extends AbstractShared
                        $owner = UserProfileRuntimeCache::getInstance()->getObject($this->additionalData['objectID']);
                }
                
-               return LinkHandler::getInstance()->getLink(
-                       'User',
-                       ['object' => $owner],
-                       '#wall/comment' . $this->additionalData['commentID'] . '/response' . $this->getResponseID()
-               );
+               return $owner->getLink() . '#wall/comment' . $this->additionalData['commentID'] . '/response' . $this->getResponseID();
        }
        
        /**
index 6c77e2ff9aedfc81ed1c1e74a9794c3b2b1a3789..7b1ab0c06c58b815e152298799cc0ed0c1f7aafb 100644 (file)
@@ -5,7 +5,6 @@ use wcf\system\cache\runtime\CommentRuntimeCache;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
 use wcf\system\comment\CommentHandler;
 use wcf\system\email\Email;
-use wcf\system\request\LinkHandler;
 use wcf\system\user\notification\object\CommentResponseUserNotificationObject;
 
 /**
@@ -123,11 +122,8 @@ class UserProfileCommentResponseOwnerUserNotificationEvent extends AbstractShare
         * @inheritDoc
         */
        public function getLink() {
-               return LinkHandler::getInstance()->getLink(
-                       'User',
-                       ['object' => UserProfileRuntimeCache::getInstance()->getObject($this->additionalData['objectID'])],
-                       '#wall/comment' . $this->getUserNotificationObject()->commentID
-               );
+               return UserProfileRuntimeCache::getInstance()->getObject($this->additionalData['objectID'])->getLink() .
+                       '#wall/comment' . $this->getUserNotificationObject()->commentID;
        }
        
        /**
index 4da2d378ecf69c57e964f71e37fa1006ef1a8c89..f994687d1331afe6cf18799144fefb16939c20d3 100644 (file)
@@ -3,7 +3,6 @@ namespace wcf\system\user\notification\event;
 use wcf\data\user\UserProfile;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
 use wcf\system\comment\CommentHandler;
-use wcf\system\request\LinkHandler;
 use wcf\system\user\notification\object\CommentUserNotificationObject;
 
 /**
@@ -93,11 +92,8 @@ class UserProfileCommentUserNotificationEvent extends AbstractSharedUserNotifica
         * @inheritDoc
         */
        public function getLink() {
-               return LinkHandler::getInstance()->getLink(
-                       'User',
-                       ['object' => UserProfileRuntimeCache::getInstance()->getObject($this->getUserNotificationObject()->objectID)],
-                       '#wall/comment' . $this->getUserNotificationObject()->commentID
-               );
+               return UserProfileRuntimeCache::getInstance()->getObject($this->getUserNotificationObject()->objectID)->getLink() .
+                       '#wall/comment' . $this->getUserNotificationObject()->commentID;
        }
        
        /**
index 1e69acb9c7c0727f0619f98d93f6edbd03f8c724..5c51349a44b9d1b3801f79525f0dd1c0cb17d619 100644 (file)
@@ -1,19 +1,18 @@
 <?php
 namespace wcf\system\user\notification\event;
 use wcf\data\user\UserProfile;
-use wcf\system\request\LinkHandler;
 use wcf\system\user\notification\object\UserFollowUserNotificationObject;
 use wcf\system\user\notification\object\UserRegistrationUserNotificationObject;
 
 /**
  * Notification event for new user registrations.
- *
+ * 
  * @author     Marcel Werk
- * @copyright  2001-2019 WoltLab GmbH
+ * @copyright  2001-2020 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    WoltLabSuite\Core\System\User\Notification\Event
  * @since       5.2
- *
+ * 
  * @method     UserRegistrationUserNotificationObject  getUserNotificationObject()
  */
 class UserRegistrationUserNotificationEvent extends AbstractUserNotificationEvent implements ITestableUserNotificationEvent {
@@ -69,7 +68,7 @@ class UserRegistrationUserNotificationEvent extends AbstractUserNotificationEven
         * @inheritDoc
         */
        public function getLink() {
-               return LinkHandler::getInstance()->getLink('User', ['object' => $this->author]);
+               return $this->author->getLink();
        }
        
        /**
index a66b0b6a454eb76deb8a098ccd51871f56b74f88..cde876f57a07f1e76a450c4bdd5f6fabfc9d53a5 100644 (file)
@@ -40,11 +40,74 @@ class MessageUtil {
                return $text;
        }
        
+       /**
+        * Returns the mentioned users in the given text.
+        *
+        * @param       HtmlInputProcessor      $htmlInputProcessor     html input processor instance
+        * @return      int[]                   ids of the mentioned users
+        * @since       5.3
+        */
+       public static function getMentionedUserIDs(HtmlInputProcessor $htmlInputProcessor) {
+               $userIDs = [];
+               $groups = [];
+               
+               $elements = $htmlInputProcessor->getHtmlInputNodeProcessor()->getDocument()->getElementsByTagName('woltlab-metacode');
+               /** @var \DOMElement $element */
+               foreach ($elements as $element) {
+                       $type = $element->getAttribute('data-name');
+                       if ($type !== 'user' && $type !== 'group') {
+                               continue;
+                       }
+                       
+                       if (DOMUtil::hasParent($element, 'woltlab-quote')) {
+                               // ignore mentions within quotes
+                               continue;
+                       }
+                       
+                       $attributes = $htmlInputProcessor->getHtmlInputNodeProcessor()->parseAttributes(
+                               $element->getAttribute('data-attributes')
+                       );
+                       
+                       if ($type === 'user') {
+                               if (!empty($attributes[0])) {
+                                       $userIDs[] = $attributes[0];
+                               }
+                       }
+                       else if ($type === 'group' && WCF::getSession()->getPermission('user.message.canMentionGroups')) {
+                               if (!empty($attributes[0])) {
+                                       $group = UserGroup::getGroupByID($attributes[0]);
+                                       if ($group !== null && $group->canBeMentioned() && !isset($groups[$group->groupID])) {
+                                               $groups[$group->groupID] = $group;
+                                       }
+                               }
+                       }
+               }
+               
+               $userIDs = array_unique($userIDs);
+               if (!empty($groups)) {
+                       $conditions = new PreparedStatementConditionBuilder();
+                       $conditions->add('groupID IN (?)', [array_keys($groups)]);
+                       if (!empty($userIDs)) $conditions->add('userID NOT IN (?)', [$userIDs]);
+                       
+                       $sql = "SELECT  userID
+                               FROM    wcf".WCF_N."_user_to_group
+                               ".$conditions;
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute($conditions->getParameters());
+                       while ($userID = $statement->fetchColumn()) {
+                               $userIDs[] = $userID;
+                       }
+               }
+               
+               return $userIDs;
+       }
+       
        /**
         * Returns the mentioned users in the given text.
         * 
         * @param       HtmlInputProcessor      $htmlInputProcessor     html input processor instance
         * @return      string[]                mentioned usernames
+        * @deprecated  5.3
         */
        public static function getMentionedUsers(HtmlInputProcessor $htmlInputProcessor) {
                $usernames = [];
index 29dc64b66c5d161e28c091e5b86d5779f87e9c84..50d1f391819fa862198a693c833d706b277d4c38 100644 (file)
@@ -279,6 +279,7 @@ input {
        }
 }
 
+.formFieldRequired,
 .customOptionRequired {
        color: rgba(204, 0, 1, 1) !important;
 }
index 4186ed79fc92a2d40b8e695bdf922a45e9d9f00a..9c06d38b1f7f3a191e2bbe867f0f0db202320d93 100644 (file)
@@ -677,7 +677,7 @@ ACHTUNG: Die oben genannten Meldungen sind stark gekürzt. Sie können Details z
                <item name="wcf.acp.group.option.admin.management.canViewLog"><![CDATA[Kann Protokolle abrufen]]></item>
                <item name="wcf.acp.group.option.admin.configuration.canManageApplication"><![CDATA[Kann Apps verwalten]]></item>
                <item name="wcf.acp.group.option.admin.management.canManageCronjob"><![CDATA[Kann zeitgesteuerte Aufgaben verwalten]]></item>
-               <item name="wcf.acp.group.option.admin.configuration.package.canEditServer"><![CDATA[Kann Update-Server bearbeiten]]></item>
+               <item name="wcf.acp.group.option.admin.configuration.package.canEditServer"><![CDATA[Kann Paket-Server bearbeiten]]></item>
                <item name="wcf.acp.group.option.admin.configuration.package.canInstallPackage"><![CDATA[Kann Pakete installieren]]></item>
                <item name="wcf.acp.group.option.admin.configuration.package.canUninstallPackage"><![CDATA[Kann Pakete deinstallieren]]></item>
                <item name="wcf.acp.group.option.admin.configuration.package.canUpdatePackage"><![CDATA[Kann Pakete aktualisieren]]></item>
@@ -757,7 +757,6 @@ ACHTUNG: Die oben genannten Meldungen sind stark gekürzt. Sie können Details z
                <item name="wcf.acp.group.option.user.profile.avatar.maxSize"><![CDATA[Maximale Dateigröße]]></item>
                <item name="wcf.acp.group.option.user.profile.canChangeEmail"><![CDATA[Kann E-Mail-Adresse ändern]]></item>
                <item name="wcf.acp.group.option.user.profile.canEditUserTitle"><![CDATA[Kann eigenen Benutzertitel bearbeiten]]></item>
-               <item name="wcf.acp.group.option.user.profile.canMail"><![CDATA[Kann E-Mails an andere Benutzer senden]]></item>
                <item name="wcf.acp.group.option.user.profile.canQuit"><![CDATA[Kann Benutzerkonto löschen]]></item>
                <item name="wcf.acp.group.option.user.profile.canRename"><![CDATA[Kann Benutzernamen ändern]]></item>
                <item name="wcf.acp.group.option.user.profile.canViewMembersList"><![CDATA[Kann Mitglieder-Liste sehen]]></item>
@@ -768,8 +767,8 @@ ACHTUNG: Die oben genannten Meldungen sind stark gekürzt. Sie können Details z
                <item name="wcf.acp.group.option.user.signature.disallowedBBCodes.description"><![CDATA[Die hier ausgewählten BBCodes dürfen von Mitgliedern dieser Benutzergruppe in ihrer Signatur <em>nicht</em> verwendet werden.]]></item>
                <item name="wcf.acp.group.priority"><![CDATA[Priorisierung]]></item>
                <item name="wcf.acp.group.priority.description"><![CDATA[Bestimmt u.a. die Reihenfolge auf der Team-Seite sowie die Auswahl von Benutzerrängen und „Wer ist online“-Darstellungen auf Basis der höchsten Priorität.]]></item>
-               <item name="wcf.acp.group.userOnlineMarking"><![CDATA[„Benutzer online“-Darstellung]]></item>
-               <item name="wcf.acp.group.userOnlineMarking.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst{else}Sie können{/if} die HTML-Formatierung für Mitglieder dieser Benutzergruppe in der „Wer ist online“-Anzeige anpassen. <em>&lt;strong&gt;%s&lt;/strong&gt;</em> stellt Mitglieder dieser Gruppe beispielsweise in Fettdruck dar.]]></item>
+               <item name="wcf.acp.group.userOnlineMarking"><![CDATA[Benutzer-Darstellung]]></item>
+               <item name="wcf.acp.group.userOnlineMarking.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst{else}Sie können{/if} die HTML-Formatierung für Mitglieder dieser Benutzergruppe anpassen. <em>&lt;strong&gt;%s&lt;/strong&gt;</em> stellt Mitglieder dieser Gruppe beispielsweise in Fettdruck dar.]]></item>
                <item name="wcf.acp.group.userOnlineMarking.error.invalid"><![CDATA[Die Darstellung muss „%s“ enthalten]]></item>
                <item name="wcf.acp.group.showOnTeamPage"><![CDATA[Mitglieder dieser Benutzergruppe auf der Team-Seite anzeigen]]></item>
                <item name="wcf.acp.group.option.admin.user.canEnableUser"><![CDATA[Kann Benutzer aktivieren]]></item>
@@ -1380,7 +1379,7 @@ ACHTUNG: Die oben genannten Meldungen sind stark gekürzt. Sie können Details z
                <item name="wcf.acp.option.external_link_target_blank.description"><![CDATA[Setzt das Attribut „target="_blank"“ auf externe Links und weist den Browser dadurch an, einen aufgerufenen Link in einem neuen Browser-Fenster zu öffnen.]]></item>
                <item name="wcf.acp.option.enable_benchmark"><![CDATA[Benchmark aktivieren]]></item>
                <item name="wcf.acp.option.enable_benchmark.description"><![CDATA[Erfasst zusätzliche Daten zur Ressourcennutzung von Komponenten. Diese Option sollte im Live-Betrieb abgeschaltet werden.]]></item>
-               <item name="wcf.acp.option.category.general.system.packageServer"><![CDATA[Update-Server]]></item>
+               <item name="wcf.acp.option.category.general.system.packageServer"><![CDATA[Paket-Server]]></item>
                <item name="wcf.acp.option.package_server_auth_code"><![CDATA[Authentifizierung-Code]]></item>
                <item name="wcf.acp.option.package_server_auth_code.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Deinen{else}Ihren{/if} Authentifizierung-Code {if LANGUAGE_USE_INFORMAL_VARIANT}findest du{else}finden Sie{/if} in {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Kundenkonto auf woltlab.com.]]></item>
                <item name="wcf.acp.option.enable_woltlab_news"><![CDATA[WoltLab-Nachrichten anzeigen]]></item>
@@ -1754,6 +1753,7 @@ Die Datenbestände werden sorgfältig gepflegt, aber es ist nicht ausgeschlossen
                <item name="wcf.acp.option.blacklist_sfs_action.block"><![CDATA[Blockieren]]></item>
                <item name="wcf.acp.option.blacklist_sfs_action.description"><![CDATA[Es besteht immer das Risiko eines fehlerhaften Eintrages, daher wird die Einstellung <strong>Deaktivierung, erfordert manuelle Freischaltung</strong> ausdrücklich empfohlen.]]></item>
                <item name="wcf.acp.option.blacklist_sfs_action.disable"><![CDATA[Deaktivierung, erfordert manuelle Freischaltung]]></item>
+               <item name="wcf.acp.option.module_amp"><![CDATA[<abbr title="Accelerated Mobile Pages">AMP</abbr>]]></item>
        </category>
        <category name="wcf.acp.customOption">
                <item name="wcf.acp.customOption.list"><![CDATA[Eingabefelder]]></item>
@@ -1806,7 +1806,7 @@ Die Datenbestände werden sorgfältig gepflegt, aber es ist nicht ausgeschlossen
                <item name="wcf.acp.package.description"><![CDATA[Beschreibung]]></item>
                <item name="wcf.acp.package.error.cli.installIsApplication"><![CDATA[Apps können per CLI nicht installiert werden.]]></item>
                <item name="wcf.acp.package.error.exceedsPhpLimit"><![CDATA[Die Datei ist größer als das PHP-Limit „upload_max_filesize“ und/oder „post_max_size“.]]></item>
-               <item name="wcf.acp.package.error.noUniqueAbbrevation"><![CDATA[Es ist bereits eine App installiert, die die gleiche Abkürzung besitzt.]]></item>
+               <item name="wcf.acp.package.error.noUniqueAbbreviation"><![CDATA[Es ist bereits eine App installiert, die die gleiche Abkürzung besitzt.]]></item>
                <item name="wcf.acp.package.error.noValidPackage"><![CDATA[Das angegebene Archiv ist kein gültiges Paket.]]></item>
                <item name="wcf.acp.package.error.sql.createTable"><![CDATA[Existierende Tabellen überschreiben]]></item>
                <item name="wcf.acp.package.error.sql.createTable.description"><![CDATA[Die oben genannten Tabellen existieren bereits und werden beim Fortfahren der Installation überschrieben. Alle Daten dieser Tabellen gehen unwiderruflich verloren.]]></item>
@@ -2337,8 +2337,8 @@ Die Datenbestände werden sorgfältig gepflegt, aber es ist nicht ausgeschlossen
                <item name="wcf.acp.pip.eventListener.environment.description"><![CDATA[Die Umgebung entscheidet, ob der Event-Listener im Frontend (<kbd>user</kbd>) oder in der Administrationsoberfläche (<kbd>admin</kbd>) ausgeführt wird.]]></item>
                <item name="wcf.acp.pip.eventListener.eventClassName"><![CDATA[PHP-Event-Klasse]]></item>
                <item name="wcf.acp.pip.eventListener.eventClassName.description"><![CDATA[Die angegebene Klasse (ohne Backslash als erstes Zeichen) feuert das Event ab. Alternativ erbt die angegebene Klasse von der Klasse, die das Event abfeuert,]]></item>
-               <item name="wcf.acp.pip.eventListener.eventName"><![CDATA[Eventname]]></item>
-               <item name="wcf.acp.pip.eventListener.eventName.description"><![CDATA[Name des Events der relevanten Klasse, auf das der Event-Listener reagiert.]]></item>
+               <item name="wcf.acp.pip.eventListener.eventName"><![CDATA[Eventnamen]]></item>
+               <item name="wcf.acp.pip.eventListener.eventName.description"><![CDATA[Namen der Events der relevanten Klasse, auf die der Event-Listener reagiert.]]></item>
                <item name="wcf.acp.pip.eventListener.inherit"><![CDATA[Vererbung unterstützen]]></item>
                <item name="wcf.acp.pip.eventListener.inherit.description"><![CDATA[Wird Vererbung unterstützt, reagiert der Event-Listener nicht nur, wenn das Event von der oben angegebenen Klasse ausgelöst wird, sondern auch wenn es von Kindklassen der angegebenen Klasse ausgelöst wird.]]></item>
                <item name="wcf.acp.pip.eventListener.listenerClassName"><![CDATA[PHP-Event-Listener-Klasse]]></item>
@@ -2758,7 +2758,7 @@ Kein Abschnitt darf leer sein und alle Abschnitten dürfen nur folgende Zeichen
                <item name="wcf.acp.updateServer.delete.sure"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} den Server <span class="confirmationObject">{$updateServer->serverURL}</span> wirklich löschen?]]></item>
                <item name="wcf.acp.updateServer.edit"><![CDATA[Server bearbeiten]]></item>
                <item name="wcf.acp.updateServer.errorMessage"><![CDATA[Fehlermeldungen]]></item>
-               <item name="wcf.acp.updateServer.list"><![CDATA[Update-Server]]></item>
+               <item name="wcf.acp.updateServer.list"><![CDATA[Paket-Server]]></item>
                <item name="wcf.acp.updateServer.lastErrorMessage"><![CDATA[Beim letzten Verbindungsversuch trat folgender Fehler auf:]]></item>
                <item name="wcf.acp.updateServer.loginPassword"><![CDATA[Kennwort]]></item>
                <item name="wcf.acp.updateServer.loginPassword.description"><![CDATA[Kennwort für Authentifizierung (optional)]]></item>
@@ -3035,7 +3035,6 @@ Kein Abschnitt darf leer sein und alle Abschnitten dürfen nur folgende Zeichen
                <item name="wcf.acp.user.search.display.itemsPerPage"><![CDATA[Benutzer pro Seite]]></item>
                <item name="wcf.acp.user.search.display.sort"><![CDATA[Sortierung]]></item>
                <item name="wcf.acp.user.search.error.noMatches"><![CDATA[Zu den angegebenen Kriterien wurde kein Benutzer gefunden.]]></item>
-               <item name="wcf.acp.user.search.matches"><![CDATA[{if $items == 1}Ein Ergebnis{else}{#$items} Ergebnisse{/if}]]></item>
                <item name="wcf.acp.user.sendMail"><![CDATA[E-Mail an Benutzer senden]]></item>
                <item name="wcf.acp.user.sendMail.all"><![CDATA[E-Mail an alle Benutzer senden]]></item>
                <item name="wcf.acp.user.sendMail.enableHTML"><![CDATA[E-Mail als HTML versenden]]></item>
@@ -3251,6 +3250,10 @@ Benutzerkontos nun in vollem Umfang nutzen.]]></item>
                <item name="wcf.article.search.results"><![CDATA[Suchergebnisse]]></item>
                <item name="wcf.article.publicationStatus.0"><![CDATA[Dieser Artikel wurde noch nicht veröffentlicht.]]></item>
                <item name="wcf.article.publicationStatus.2"><![CDATA[Dieser Artikel wird am {@$publicationDate|plainTime} veröffentlicht.]]></item>
+               <item name="wcf.article.like.notification.title"><![CDATA[Reaktion auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Artikel]]></item>
+               <item name="wcf.article.like.notification.title.stacked"><![CDATA[{#$count} Benutzer haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Artikel reagiert]]></item>
+               <item name="wcf.article.like.notification.message"><![CDATA[<strong>{$author}</strong> hat auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Artikel <strong>{$article->getTitle()}</strong> reagiert ({@$__wcf->getReactionHandler()->renderInlineList($reactions)}).]]></item>
+               <item name="wcf.article.like.notification.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Artikel <strong>{$article->getTitle()}</strong> reagiert ({@$__wcf->getReactionHandler()->renderInlineList($reactions)}).]]></item>
        </category>
        <category name="wcf.attachment">
                <item name="wcf.attachment.file.info"><![CDATA[({@$attachment->filesize|filesize}, <b>{#$attachment->downloads}</b> Mal heruntergeladen{if $attachment->downloads > 0}, zuletzt: {@$attachment->lastDownloadTime|time}{/if})]]></item>
@@ -4014,14 +4017,14 @@ Dateianhänge:
                <item name="wcf.like.likes.noMoreEntries"><![CDATA[Keine weiteren Reaktionen]]></item>
                <item name="wcf.like.dislikes.more"><![CDATA[Weitere Dislikes]]></item>
                <item name="wcf.like.dislikes.noMoreEntries"><![CDATA[Keine weiteren Dislikes]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.user.profileComment"><![CDATA[Hat mit <span title="{$like->getReactionType()->getTitle()}" class="jsTooltip">{@$like->getReactionType()->renderIcon()}</span> auf den Kommentar {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">von {$commentAuthor->username}</a>{else}eines Gasts{/if} an der <a href="{$comment->getLink()}">Pinnwand von {$user->username}</a> reagiert.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.user.profileComment.response"><![CDATA[Hat mit <span title="{$like->getReactionType()->getTitle()}" class="jsTooltip">{@$like->getReactionType()->renderIcon()}</span> auf die Antwort {if $responseAuthor}<a href="{link controller='User' object=$responseAuthor}{/link}">von {$responseAuthor->username}</a>{else}eines Gasts{/if} zum Kommentar {if $commentAuthor}von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}eines Gasts{/if} an der <a href="{$response->getLink()}">Pinnwand von {$user->username}</a> reagiert.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.user.profileComment"><![CDATA[Hat mit {@$reaction->render()} auf den Kommentar {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">von {$commentAuthor->username}</a>{else}eines Gasts{/if} an der <a href="{$comment->getLink()}">Pinnwand von {$user->username}</a> reagiert.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.user.profileComment.response"><![CDATA[Hat mit {@$reaction->render()} auf die Antwort {if $responseAuthor}<a href="{link controller='User' object=$responseAuthor}{/link}">von {$responseAuthor->username}</a>{else}eines Gasts{/if} zum Kommentar {if $commentAuthor}von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}eines Gasts{/if} an der <a href="{$response->getLink()}">Pinnwand von {$user->username}</a> reagiert.]]></item>
                <item name="wcf.like.objectType.com.woltlab.wcf.likeableArticle"><![CDATA[Artikel]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.likeableArticle"><![CDATA[Hat mit <span title="{$like->getReactionType()->getTitle()}" class="jsTooltip">{@$like->getReactionType()->renderIcon()}</span> auf den Artikel <a href="{$article->getLink()}">{$article->getTitle()}</a> reagiert.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.articleComment"><![CDATA[Hat mit <span title="{$like->getReactionType()->getTitle()}" class="jsTooltip">{@$like->getReactionType()->renderIcon()}</span> auf den Kommentar {if $commentAuthor}von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}eines Gasts{/if} zum Artikel <a href="{$comment->getLink()}">{$articleContent->getTitle()}</a> reagiert.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.articleComment.response"><![CDATA[Hat mit <span title="{$like->getReactionType()->getTitle()}" class="jsTooltip">{@$like->getReactionType()->renderIcon()}</span> auf die Antwort {if $responseAuthor}von <a href="{link controller='User' object=$responseAuthor}{/link}">{$responseAuthor->username}</a>{else}eines Gasts{/if} zum Kommentar {if $commentAuthor}von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}eines Gasts{/if} zum Artikel <a href="{$response->getLink()}">{$articleContent->getTitle()}</a> reagiert.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.pageComment"><![CDATA[Hat mit <span title="{$like->getReactionType()->getTitle()}" class="jsTooltip">{@$like->getReactionType()->renderIcon()}</span> auf den Kommentar {if $commentAuthor}von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}eines Gasts{/if} zu der Seite <a href="{$comment->getLink()}">{$page->getTitle()}</a> reagiert.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.pageComment.response"><![CDATA[Hat mit <span title="{$like->getReactionType()->getTitle()}" class="jsTooltip">{@$like->getReactionType()->renderIcon()}</span> auf die Antwort {if $responseAuthor}von <a href="{link controller='User' object=$responseAuthor}{/link}">{$responseAuthor->username}</a>{else}eines Gasts{/if} zum Kommentar {if $commentAuthor}von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}eines Gasts{/if} zu der Seite <a href="{$response->getLink()}">{$page->getTitle()}</a> reagiert.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.likeableArticle"><![CDATA[Hat mit {@$reaction->render()} auf den Artikel <a href="{$article->getLink()}">{$article->getTitle()}</a> reagiert.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.articleComment"><![CDATA[Hat mit {@$reaction->render()} auf den Kommentar {if $commentAuthor}von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}eines Gasts{/if} zum Artikel <a href="{$comment->getLink()}">{$articleContent->getTitle()}</a> reagiert.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.articleComment.response"><![CDATA[Hat mit {@$reaction->render()} auf die Antwort {if $responseAuthor}von <a href="{link controller='User' object=$responseAuthor}{/link}">{$responseAuthor->username}</a>{else}eines Gasts{/if} zum Kommentar {if $commentAuthor}von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}eines Gasts{/if} zum Artikel <a href="{$response->getLink()}">{$articleContent->getTitle()}</a> reagiert.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.pageComment"><![CDATA[Hat mit {@$reaction->render()} auf den Kommentar {if $commentAuthor}von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}eines Gasts{/if} zu der Seite <a href="{$comment->getLink()}">{$page->getTitle()}</a> reagiert.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.pageComment.response"><![CDATA[Hat mit {@$reaction->render()} auf die Antwort {if $responseAuthor}von <a href="{link controller='User' object=$responseAuthor}{/link}">{$responseAuthor->username}</a>{else}eines Gasts{/if} zum Kommentar {if $commentAuthor}von <a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}eines Gasts{/if} zu der Seite <a href="{$response->getLink()}">{$page->getTitle()}</a> reagiert.]]></item>
                <item name="wcf.like.reaction.label"><![CDATA[{#$reactions} Reaktion{if $reactions != 1}en{/if}]]></item>
                <item name="wcf.like.reaction.more"><![CDATA[Weitere Reaktionen]]></item>
                <item name="wcf.like.reaction.noMoreEntries"><![CDATA[Keine weiteren Reaktionen]]></item>
@@ -4104,7 +4107,7 @@ Dateianhänge:
                <item name="wcf.message.quote.manageQuotes"><![CDATA[Zitate verwalten]]></item>
                <item name="wcf.message.quote.quoteSelected"><![CDATA[Zitat speichern]]></item>
                <item name="wcf.message.quote.quoteAndReply"><![CDATA[Zitat einfügen]]></item>
-               <item name="wcf.message.quote.showQuotes"><![CDATA[Zitate (#count#)]]></item>
+               <item name="wcf.message.quote.showQuotes"><![CDATA[{if $count == 1}Ein Zitat{else}{#$count} Zitate{/if}]]></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>
@@ -4147,6 +4150,8 @@ Dateianhänge:
                <item name="wcf.moderation.assignedUser.change"><![CDATA[Zugewiesenen Benutzer ändern]]></item>
                <item name="wcf.moderation.assignedUser.error.notAffected"><![CDATA[Dieser Benutzer hat unzureichende Zugriffsrechte]]></item>
                <item name="wcf.moderation.assignedUser.nobody"><![CDATA[Niemand]]></item>
+               <item name="wcf.moderation.assignedUsername"><![CDATA[Zugewiesener Benutzer]]></item>
+               <item name="wcf.moderation.filter"><![CDATA[Einträge filtern]]></item>
                <item name="wcf.moderation.filterByType"><![CDATA[Typ]]></item>
                <item name="wcf.moderation.filterByUser"><![CDATA[Zugewiesener Benutzer]]></item>
                <item name="wcf.moderation.filterByUser.allEntries"><![CDATA[Alle Einträge]]></item>
@@ -4159,14 +4164,14 @@ Dateianhänge:
                <item name="wcf.moderation.noMoreItems"><![CDATA[Keine weiteren Einträge]]></item>
                <item name="wcf.moderation.notification.comment.title"><![CDATA[Neuer Kommentar (Moderation)]]></item>
                <item name="wcf.moderation.notification.comment.title.stacked"><![CDATA[{#$timesTriggered} neue Kommentare (Moderation)]]></item>
-               <item name="wcf.moderation.notification.comment.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat einen Kommentar zum Moderationseintrag <a href="{@$moderationQueue->getLink()}#comment{@$commentID}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.moderation.notification.comment.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} und {@$authors[2]->getAnchorTag()}{/if}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$others} weitere Benutzer{/if} haben Kommentare zum Moderationseintrag <a href="{@$moderationQueue->getLink()}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
+               <item name="wcf.moderation.notification.comment.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat einen Kommentar zum Moderationseintrag <strong>{$moderationQueue}</strong> verfasst.]]></item>
+               <item name="wcf.moderation.notification.comment.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben Kommentare zum Moderationseintrag <strong>{$moderationQueue}</strong> verfasst.]]></item>
                <item name="wcf.moderation.notification.comment.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat einen Kommentar{else}haben Kommentare{/if} zum Moderationseintrag {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}] verfasst{if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}:{else}.{/if}]]></item>
                <item name="wcf.moderation.notification.comment.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat einen Kommentar{else}haben Kommentare{/if} zum Moderationseintrag <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a> verfasst:</p>]]></item>
                <item name="wcf.moderation.notification.commentResponse.title"><![CDATA[Neue Antwort (Moderation)]]></item>
                <item name="wcf.moderation.notification.commentResponse.title.stacked"><![CDATA[{#$timesTriggered} neue Antworten (Moderation)]]></item>
-               <item name="wcf.moderation.notification.commentResponse.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat eine Antwort zum Kommentar von {if $commentAuthor->userID}<a href="{link controller='User' object=$commentAuthor}{/link}" class="userLink" data-user-id="{@$commentAuthor->userID}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if} zum Moderationseintrag <a href="{@$moderationQueue->getLink()}#comment{@$commentID}/response{@$responseID}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.moderation.notification.commentResponse.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}und {@$authors[2]->getAnchorTag()}{/if}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$others} weitere Benutzer{/if} haben Antworten zum Kommentar zum Moderationseintrag <a href="{@$moderationQueue->getLink()}#comment{@$commentID}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
+               <item name="wcf.moderation.notification.commentResponse.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat eine Antwort zum Kommentar von <strong>{$commentAuthor->username}</strong> zum Moderationseintrag <strong>{$moderationQueue}</strong> verfasst.]]></item>
+               <item name="wcf.moderation.notification.commentResponse.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben Antworten zum Kommentar zum Moderationseintrag <strong>{$moderationQueue}</strong> verfasst.]]></item>
                <item name="wcf.moderation.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {@$notificationContent[variables][commentAuthor]->username}{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} zum Moderationseintrag {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.moderation.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if} zum Moderationseintrag <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a> verfasst:</p>]]></item>
                <item name="wcf.moderation.status"><![CDATA[Status]]></item>
@@ -4196,6 +4201,9 @@ Dateianhänge:
                <item name="wcf.moderation.jumpToContent"><![CDATA[Zum Inhalt gehen]]></item>
                <item name="wcf.moderation.markAllAsRead"><![CDATA[Alle Einträge als gelesen markieren]]></item>
                <item name="wcf.moderation.markAsRead.doubleClick"><![CDATA[Eintrag durch Doppelklick als gelesen markieren]]></item>
+               <item name="wcf.moderation.comments"><![CDATA[Kommentare]]></item>
+               <item name="wcf.moderation.username"><![CDATA[Autor]]></item>
+               <item name="wcf.moderation.noEntries"><![CDATA[Es wurden keine Einträge gefunden.{if $hasActiveFilter} <a href="#" class="jsStaticDialog" data-dialog-id="moderationListSortFilter" role="button">Die aktiven Filter anpassen.</a>{/if}]]></item>
        </category>
        <category name="wcf.moderation.activation">
                <item name="wcf.moderation.activation"><![CDATA[Freischaltung]]></item>
@@ -4205,14 +4213,14 @@ Dateianhänge:
                <item name="wcf.moderation.activation.enableContent.confirmMessage"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Willst du{else}Wollen Sie{/if} diesen Inhalt wirklich freischalten?]]></item>
                <item name="wcf.moderation.activation.notification.comment.title"><![CDATA[Neuer Kommentar (Freischaltung)]]></item>
                <item name="wcf.moderation.activation.notification.comment.title.stacked"><![CDATA[{#$timesTriggered} neue Kommentare (Freischaltung)]]></item>
-               <item name="wcf.moderation.activation.notification.comment.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat einen Kommentar zum freizuschaltenden Inhalt <a href="{@$moderationQueue->getLink()}#comment{@$commentID}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.moderation.activation.notification.comment.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}und {@$authors[2]->getAnchorTag()}{/if}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$others} weitere Benutzer{/if} haben Kommentare zum freizuschaltenden Inhalt <a href="{@$moderationQueue->getLink()}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
+               <item name="wcf.moderation.activation.notification.comment.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat einen Kommentar zum freizuschaltenden Inhalt <strong>{$moderationQueue}</strong> verfasst.]]></item>
+               <item name="wcf.moderation.activation.notification.comment.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben Kommentare zum freizuschaltenden Inhalt <strong>{$moderationQueue}</strong> verfasst.]]></item>
                <item name="wcf.moderation.activation.notification.comment.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat einen Kommentar{else}haben Kommentare{/if} zum freizuschaltenden Inhalt {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.moderation.activation.notification.comment.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat einen Kommentar{else}haben Kommentare{/if} zum freizuschaltenden Inhalt <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a> verfasst:</p>]]></item>
                <item name="wcf.moderation.activation.notification.commentResponse.title"><![CDATA[Neue Antwort (Freischaltung)]]></item>
                <item name="wcf.moderation.activation.notification.commentResponse.title.stacked"><![CDATA[{#$timesTriggered} neue Antworten (Freischaltung)]]></item>
-               <item name="wcf.moderation.activation.notification.commentResponse.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat eine Antwort zum Kommentar von {if $commentAuthor->userID}<a href="{link controller='User' object=$commentAuthor}{/link}" class="userLink" data-user-id="{@$commentAuthor->userID}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if} zum freizuschaltenden Inhalt <a href="{@$moderationQueue->getLink()}#comment{@$commentID}/response{@$responseID}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.moderation.activation.notification.commentResponse.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} und {@$authors[2]->getAnchorTag()}{/if}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$others} weitere Benutzer{/if} haben Antworten zu Kommentaren zum freizuschaltenden Inhalt <a href="{@$moderationQueue->getLink()}#comment{@$commentID}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
+               <item name="wcf.moderation.activation.notification.commentResponse.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat eine Antwort zum Kommentar von <strong>{$commentAuthor}</strong> zum freizuschaltenden Inhalt <strong>{$moderationQueue}</strong> verfasst.]]></item>
+               <item name="wcf.moderation.activation.notification.commentResponse.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben Antworten zu Kommentaren zum freizuschaltenden Inhalt <strong>{$moderationQueue}</strong> verfasst.]]></item>
                <item name="wcf.moderation.activation.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {@$notificationContent[variables][commentAuthor]->username}{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} zum freizuschaltenden Inhalt {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.moderation.activation.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if} zum freizuschaltenden Inhalt <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a> verfasst:</p>]]></item>
                <item name="wcf.moderation.activation.removeContent"><![CDATA[Inhalt löschen]]></item>
@@ -4224,14 +4232,14 @@ Dateianhänge:
                <item name="wcf.moderation.report.details"><![CDATA[Informationen]]></item>
                <item name="wcf.moderation.report.notification.comment.title"><![CDATA[Neuer Kommentar (Meldung)]]></item>
                <item name="wcf.moderation.report.notification.comment.title.stacked"><![CDATA[{#$timesTriggered} neue Kommentare (Meldung)]]></item>
-               <item name="wcf.moderation.report.notification.comment.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat einen Kommentar zur Meldung <a href="{@$moderationQueue->getLink()}#comment{@$commentID}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.moderation.report.notification.comment.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} und {@$authors[2]->getAnchorTag()}{/if}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$others} weitere Benutzer{/if} haben Kommentare zur Meldung <a href="{@$moderationQueue->getLink()}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
+               <item name="wcf.moderation.report.notification.comment.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat einen Kommentar zur Meldung <strong>{$moderationQueue}</strong> verfasst.]]></item>
+               <item name="wcf.moderation.report.notification.comment.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben Kommentare zur Meldung <strong>{$moderationQueue}</strong> verfasst.]]></item>
                <item name="wcf.moderation.report.notification.comment.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat einen Kommentar{else}haben Kommentare{/if} zu der Meldung {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.moderation.report.notification.comment.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat einen Kommentar{else}haben Kommentare{/if} zu der Meldung <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a> verfasst:</p>]]></item>
                <item name="wcf.moderation.report.notification.commentResponse.title"><![CDATA[Neue Antwort (Meldung)]]></item>
                <item name="wcf.moderation.report.notification.commentResponse.title.stacked"><![CDATA[{#$timesTriggered} neue Antworten (Meldung)]]></item>
-               <item name="wcf.moderation.report.notification.commentResponse.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat eine Antwort zum Kommentar von {if $commentAuthor->userID}<a href="{link controller='User' object=$commentAuthor}{/link}" class="userLink" data-user-id="{@$commentAuthor->userID}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if} zur Meldung <a href="{@$moderationQueue->getLink()}#comment{@$commentID}/response{@$responseID}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.moderation.report.notification.commentResponse.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} und {@$authors[2]->getAnchorTag()}{/if}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$others} weitere Benutzer{/if} haben Antworten zu Kommentare zur Meldung <a href="{@$moderationQueue->getLink()}#comment{@$commentID}">{$moderationQueue->getTitle()}</a> verfasst.]]></item>
+               <item name="wcf.moderation.report.notification.commentResponse.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat eine Antwort zum Kommentar von <strong>{$commentAuthor}</strong> zur Meldung <strong>{$moderationQueue}</strong> verfasst.]]></item>
+               <item name="wcf.moderation.report.notification.commentResponse.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben Antworten zu Kommentare zur Meldung <strong>{$moderationQueue}</strong> verfasst.]]></item>
                <item name="wcf.moderation.report.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {@$notificationContent[variables][commentAuthor]->username}{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} zur Meldung {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.moderation.report.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if} zur Meldung <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a> verfasst:</p>]]></item>
                <item name="wcf.moderation.report.reason"><![CDATA[Grund der Meldung]]></item>
@@ -4312,7 +4320,7 @@ Dateianhänge:
                <item name="wcf.paidSubscription.confirmTOS"><![CDATA[Hiermit bestätige ich mein Einverständnis mit den <a href="{PAID_SUBSCRIPTION_TOS_URL}">Nutzungsbedingungen</a>]]></item>
                <item name="wcf.paidSubscription.button.moreInformation"><![CDATA[Mehr Informationen]]></item>
                <item name="wcf.paidSubscription.expiringSubscription.notification.title"><![CDATA[Ablaufende Mitgliedschaft]]></item>
-               <item name="wcf.paidSubscription.expiringSubscription.notification.message"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} Mitgliedschaft „{$userNotificationObject->getTitle()}“ läuft {dateInterval start=$notification->time end=$userNotificationObject->endDate format='sentence'} (am {$userNotificationObject->endDate|date:'d. F'}) ab.]]></item>
+               <item name="wcf.paidSubscription.expiringSubscription.notification.message"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} Mitgliedschaft <strong>{$userNotificationObject->getTitle()}</strong> läuft {dateInterval start=$notification->time end=$userNotificationObject->endDate format='sentence'} (am {$userNotificationObject->endDate|date:'d. F'}) ab.]]></item>
                <item name="wcf.paidSubscription.expiringSubscription.notification.mail.plaintext"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} Mitgliedschaft „{@$subscription->getTitle()}“ läuft {dateInterval start=$notification->time end=$subscription->endDate format='sentence'} (am {@$subscription->endDate|date:'d. F'}) ab.]]></item>
                <item name="wcf.paidSubscription.expiringSubscription.notification.mail.html"><![CDATA[<p>{if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} Mitgliedschaft „{$subscription->getTitle()}“ läuft <b>{dateInterval start=$notification->time end=$subscription->endDate format='sentence'}</b> (am {$subscription->endDate|date:'d. F'}) ab.</p>]]></item>
        </category>
@@ -4352,6 +4360,7 @@ Dateianhänge:
                <item name="wcf.reactions.summary.noReactions"><![CDATA[Es gibt bisher keine Reaktionen dieses Types auf das Objekt.]]></item>
                <item name="wcf.reactions.summary.listReactions"><![CDATA[Reaktionen auflisten]]></item>
                <item name="wcf.reactions.react"><![CDATA[Reagieren]]></item>
+               <item name="wcf.reactions.reactionTypeCount"><![CDATA[{@$reaction->renderIcon()}×{#$count}]]></item>
        </category>
        <category name="wcf.reactionType">
                <item name="wcf.reactionType.title1"><![CDATA[Gefällt mir]]></item>
@@ -4769,7 +4778,7 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
                <item name="wcf.user.trophy.dialogTitle"><![CDATA[Trophäen von {$username}]]></item>
                <item name="wcf.user.trophy.trophies"><![CDATA[Trophäen]]></item>
                <item name="wcf.user.trophy.specialTrophies"><![CDATA[Besondere Trophäen]]></item>
-               <item name="wcf.user.trophy.specialTrophies.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Wähle{else}Wählen Sie{/if} hier {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} besonderen Trophäen aus, welche im Profil und in der Nachrichten-Seitenleiste angezeigt werden.]]></item>
+               <item name="wcf.user.trophy.specialTrophies.description"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Wähle{else}Wählen Sie{/if} hier {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} {if $__wcf->getSession()->getPermission('user.profile.trophy.maxUserSpecialTrophies') > 1}besonderen Trophäen{else}besondere Trophäe{/if} aus, welche im Profil und in der Nachrichten-Seitenleiste angezeigt {if $__wcf->getSession()->getPermission('user.profile.trophy.maxUserSpecialTrophies') > 1}werden. Es können maximal {#$__wcf->getSession()->getPermission('user.profile.trophy.maxUserSpecialTrophies')} Trophäen ausgewählt werden.{else}wird.{/if}]]></item>
                <item name="wcf.user.trophy.specialTrophies.error.tooMany"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst{else}Sie können{/if} maximal {#$__wcf->session->getPermission('user.profile.trophy.maxUserSpecialTrophies')} Trophäen auswählen.]]></item>
                <item name="wcf.user.trophy.specialTrophies.error.invalid"><![CDATA[Die angegebenen Trophäen sind ungültig.]]></item>
                <item name="wcf.user.trophy.recentActivity.received"><![CDATA[Hat die Trophäe <a href="{$userTrophy->getTrophy()->getLink()}">{$userTrophy->getTrophy()->getTitle()}</a> erhalten.]]></item>
@@ -4950,6 +4959,12 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
                <item name="wcf.user.condition.notUserTrophyIDs.description"><![CDATA[Benutzer dürfen keine der ausgewählten Trophäen erhalten haben.]]></item>
                <item name="wcf.user.condition.notUserTrophyIDs.error.userTrophyIntersection"><![CDATA[Die ausgewählten Trophäen in „hat Trophäe“ und „hat nicht Trophäe“ sind widersprüchlich.]]></item>
                <item name="wcf.user.condition.trophyPoints"><![CDATA[Trophäen]]></item>
+               <item name="wcf.user.condition.coverPhoto"><![CDATA[Titelbild]]></item>
+               <item name="wcf.user.condition.coverPhoto.coverPhoto"><![CDATA[Hat Titelbild]]></item>
+               <item name="wcf.user.condition.coverPhoto.noCoverPhoto"><![CDATA[Hat kein Titelbild]]></item>
+               <item name="wcf.user.condition.signature"><![CDATA[Signatur]]></item>
+               <item name="wcf.user.condition.signature.signature"><![CDATA[Hat Signatur]]></item>
+               <item name="wcf.user.condition.signature.noSignature"><![CDATA[Hat kein Signatur]]></item>
        </category>
        <category name="wcf.user.coverPhoto">
                <item name="wcf.user.coverPhoto"><![CDATA[Titelbild]]></item>
@@ -5030,39 +5045,40 @@ Benachrichtigungen auf <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|lang
 <p>Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} keine E-Mail-Benachrichtigungen mehr erhalten {if LANGUAGE_USE_INFORMAL_VARIANT}möchtest{else}möchten{/if}, dann {if LANGUAGE_USE_INFORMAL_VARIANT}kannst du{else}können Sie{/if} diese direkt <a href="{link controller='NotificationDisable' isHtmlEmail=true}userID={@$mailbox->getUser()->userID}&token={@$mailbox->getUser()->notificationMailToken}{/link}">abbestellen</a>.</p>]]></item>
                <item name="wcf.user.notification.mail.authorList.plaintext"><![CDATA[{if !$event->getAuthor()->userID}{if $guestTimesTriggered > 1}Gäste{else}Ein Gast{/if}{else}{@$event->getAuthor()->username} [URL:{link controller='User' object=$event->getAuthor() isEmail=true}{/link}]{/if}{if $count > 1 && $count < 4}{if $count == 2 && !$guestTimesTriggered} und {else}, {/if}{@$authors[1]->username} [URL:{link controller='User' object=$authors[1] isEmail=true}{/link}]{if $count == 3}{if !$guestTimesTriggered} und {else}, {/if}{@$authors[2]->username} [URL:{link controller='User' object=$authors[2] isEmail=true}{/link}]{/if}{elseif $count >= 4}{if $guestTimesTriggered},{else} und{/if} {#$count-1} weitere Benutzer{/if}{if $event->getAuthor()->userID && $guestTimesTriggered} und {if $guestTimesTriggered == 1}ein Gast{else}{#$guestTimesTriggered} Gäste{/if}{/if}]]></item>
                <item name="wcf.user.notification.mail.authorList.html"><![CDATA[{if !$event->getAuthor()->userID}{if $guestTimesTriggered > 1}Gäste{else}Ein Gast{/if}{else}<a href="{link controller='User' object=$event->getAuthor() isHtmlEmail=true}{/link}">{$event->getAuthor()->username}</a>{/if}{if $count > 1 && $count < 4}{if $count == 2 && !$guestTimesTriggered} und {else}, {/if}<a href="{link controller='User' object=$authors[1] isHtmlEmail=true}{/link}">{$authors[1]->username}</a>{if $count == 3}{if !$guestTimesTriggered} und {else}, {/if}<a href="{link controller='User' object=$authors[2] isHtmlEmail=true}{/link}">{$authors[2]->username}</a>{/if}{elseif $count >= 4}{if $guestTimesTriggered},{else} und{/if} {#$count-1} weitere Benutzer{/if}{if $event->getAuthor()->userID && $guestTimesTriggered} und {if $guestTimesTriggered == 1}ein Gast{else}{#$guestTimesTriggered} Gäste{/if}{/if}]]></item>
+               <item name="wcf.user.notification.stacked.authorList"><![CDATA[{if !$guestTimesTriggered|isset}{assign var=guestTimesTriggered value=0}{/if}{if $count < 4}<strong>{$authors[0]}</strong>{if $count != 1}{if $count == 2 && !$guestTimesTriggered} und {else}, {/if}<strong>{$authors[1]}</strong>{if $count == 3}{if !$guestTimesTriggered} und {else}, {/if} <strong>{$authors[2]}</strong>{/if}{/if}{if $guestTimesTriggered} und {plural value=$guestTimesTriggered one='ein Gast' other='# Gäste'}{/if}{else}<strong>{$authors[0]}</strong>{if $guestTimesTriggered},{else} und{/if} {#$others} weitere Benutzer {if $guestTimesTriggered}und {plural value=$guestTimesTriggered one='ein Gast' other='# Gäste'}{/if}{/if}]]></item>
                <!-- Notifications -->
                <item name="wcf.user.notification.com.woltlab.wcf.user"><![CDATA[Benutzer-Profile]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.follow.following"><![CDATA[Jemand folgt {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if}]]></item>
                <item name="wcf.user.notification.follow.title"><![CDATA[Neuer Follower]]></item>
                <item name="wcf.user.notification.follow.title.stacked"><![CDATA[{#$count} neue Follower]]></item>
-               <item name="wcf.user.notification.follow.message"><![CDATA[{@$author->getAnchorTag()} folgt {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if}.]]></item>
-               <item name="wcf.user.notification.follow.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} und {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$others} weitere Benutzer{/if} folgen {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if}.]]></item>
+               <item name="wcf.user.notification.follow.message"><![CDATA[<strong>{$author}</strong> folgt {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if}.]]></item>
+               <item name="wcf.user.notification.follow.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} folgen {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if}.]]></item>
                <item name="wcf.user.notification.follow.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1}folgt{else}folgen{/if} {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if}.]]></item>
                <item name="wcf.user.notification.follow.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1}folgt{else}folgen{/if} {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if}:</p>]]></item>
                <item name="wcf.user.notification.comment.title"><![CDATA[Neuer Kommentar (Pinnwand)]]></item>
                <item name="wcf.user.notification.comment.title.stacked"><![CDATA[{#$timesTriggered} neue Kommentare (Pinnwand)]]></item>
-               <item name="wcf.user.notification.comment.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat einen Kommentar an <a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a> verfasst.]]></item>
-               <item name="wcf.user.notification.comment.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} und {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} und{/if} {#$others} weitere Benutzer {if $guestTimesTriggered}und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{/if} haben Kommentare an <a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a> verfasst.]]></item>
+               <item name="wcf.user.notification.comment.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat einen Kommentar an {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand verfasst.]]></item>
+               <item name="wcf.user.notification.comment.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben Kommentare an {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand verfasst.]]></item>
                <item name="wcf.user.notification.comment.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat einen Kommentar{else}haben Kommentare{/if} an {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand [URL:{link controller='User' object=$notificationContent[variables][owner] isEmail=true}#wall/comment{@$commentID}{/link}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.user.notification.comment.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat einen Kommentar{else}haben Kommentare{/if} an <a href="{link controller='User' object=$notificationContent[variables][owner] isHtmlEmail=true}#wall/comment{@$commentID}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a> verfasst:</p>]]></item>
                <item name="wcf.user.notification.comment.like.title"><![CDATA[Reaktion auf einen Kommentar (Pinnwand)]]></item>
                <item name="wcf.user.notification.comment.like.title.stacked"><![CDATA[{#$count} Benutzern haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar reagiert (Pinnwand)]]></item>
-               <item name="wcf.user.notification.comment.like.message"><![CDATA[{@$author->getAnchorTag()} hat auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar an {if $owner === null}<a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a>{else}der <a href="{link controller='User' object=$owner}#wall/comment{@$commentID}{/link}">Pinnwand von {$owner->username}</a>{/if} reagiert ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}×{#$count}{/implode}).]]></item>
-               <item name="wcf.user.notification.comment.like.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} und {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$others} weitere Benutzer{/if} haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar an {if $owner === null}<a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a>{else}der <a href="{link controller='User' object=$owner}#wall/comment{@$commentID}{/link}">Pinnwand von {$owner->username}</a>{/if} reagiert ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}×{#$count}{/implode}).]]></item>
+               <item name="wcf.user.notification.comment.like.message"><![CDATA[<strong>{$author}</strong> hat auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar an {if $owner === null}{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand{else}der Pinnwand von <strong>{$owner}</strong>{/if} reagiert ({@$__wcf->getReactionHandler()->renderInlineList($reactions)}).]]></item>
+               <item name="wcf.user.notification.comment.like.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language}  haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar an {if $owner === null}{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand{else}der Pinnwand von <strong>{$owner}</strong>{/if} reagiert ({@$__wcf->getReactionHandler()->renderInlineList($reactions)}).]]></item>
                <item name="wcf.user.notification.commentResponse.title"><![CDATA[Neue Antwort (Pinnwand)]]></item>
                <item name="wcf.user.notification.commentResponse.title.stacked"><![CDATA[{#$timesTriggered} neue Antworten (Pinnwand)]]></item>
-               <item name="wcf.user.notification.commentResponse.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat eine Antwort zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Kommentar an {if $owner->userID == $__wcf->getUser()->userID}<a href="{link controller='User' object=$owner}#wall/comment{@$commentID}/response{@$responseID}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a>{else}der <a href="{link controller='User' object=$owner}#wall/comment{@$commentID}/response{@$responseID}{/link}">Pinnwand von {$owner->username}</a>{/if} verfasst.]]></item>
-               <item name="wcf.user.notification.commentResponse.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} und {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} und{/if} {#$others} weitere Benutzer {if $guestTimesTriggered}und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{/if} haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar an {if $owner->userID == $__wcf->getUser()->userID}<a href="{link controller='User' object=$owner}#wall/comment{@$commentID}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a>{else}der <a href="{link controller='User' object=$owner}#wall/comment{@$commentID}{/link}">Pinnwand von {$owner->username}</a>{/if} geantwortet.]]></item>
+               <item name="wcf.user.notification.commentResponse.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat eine Antwort zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Kommentar an {if $owner->userID == $__wcf->getUser()->userID}{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand{else}der Pinnwand von <strong>{$owner}</strong>{/if} verfasst.]]></item>
+               <item name="wcf.user.notification.commentResponse.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar an {if $owner->userID == $__wcf->getUser()->userID}{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand{else}der Pinnwand von <strong>{$owner}</strong>{/if} geantwortet.]]></item>
                <item name="wcf.user.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar an {if $mailbox->getUser()->userID == $notificationContent[variables][owner]->userID}{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand{else}der Pinnwand von {@$notificationContent[variables][owner]->username}{/if} [URL:{link controller='User' object=$notificationContent[variables][owner] isEmail=true}#wall/comment{@$commentID}/response{@$responseID}{/link}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.user.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Kommentar an {if $mailbox->getUser()->userID == $notificationContent[variables][owner]->userID}<a href="{link controller='User' object=$notificationContent[variables][owner] isHtmlEmail=true}#wall/comment{@$commentID}/response{@$responseID}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a>{else}der <a href="{link controller='User' object=$notificationContent[variables][owner] isHtmlEmail=true}#wall/comment{@$commentID}/response{@$responseID}{/link}">Pinnwand von {$notificationContent[variables][owner]->username}</a>{/if} verfasst:</p>]]></item>
                <item name="wcf.user.notification.commentResponse.like.title"><![CDATA[Reaktion auf eine Antwort eines Kommentares (Pinnwand)]]></item>
                <item name="wcf.user.notification.commentResponse.like.title.stacked"><![CDATA[{#$count} Benutzer haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Antwort auf einen Kommentar reagiert (Pinnwand)]]></item>
-               <item name="wcf.user.notification.commentResponse.like.message"><![CDATA[{@$author->getAnchorTag()} hat auf {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Antwort auf einen Kommentar an {if $owner === null}<a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}/response{@$responseID}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a>{else}der <a href="{link controller='User' object=$owner}#wall/comment{@$commentID}/response{@$responseID}{/link}">Pinnwand von {$owner->username}</a>{/if} reagiert ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}×{#$count}{/implode}).]]></item>
-               <item name="wcf.user.notification.commentResponse.like.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} und {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$others} weitere Benutzer{/if} haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Antwort auf einen Kommentar an {if $owner === null}<a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}/response{@$responseID}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a>{else}der <a href="{link controller='User' object=$owner}#wall/comment{@$commentID}/response{@$responseID}{/link}">Pinnwand von {$owner->username}</a>{/if} reagiert ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}×{#$count}{/implode}).]]></item>
+               <item name="wcf.user.notification.commentResponse.like.message"><![CDATA[<strong>{$author}</strong> hat auf {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Antwort auf einen Kommentar an {if $owner === null}{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand{else}der Pinnwand von <strong>{$owner}</strong>{/if} reagiert ({@$__wcf->getReactionHandler()->renderInlineList($reactions)}).]]></item>
+               <item name="wcf.user.notification.commentResponse.like.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}deine{else}Ihre{/if} Antwort auf einen Kommentar an {if $owner === null}{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand{else}der Pinnwand von <strong>{$owner}</strong>{/if} reagiert ({@$__wcf->getReactionHandler()->renderInlineList($reactions)}).]]></item>
                <item name="wcf.user.notification.commentResponseOwner.title"><![CDATA[Neue Antwort (Pinnwand)]]></item>
                <item name="wcf.user.notification.commentResponseOwner.title.stacked"><![CDATA[{#$timesTriggered} neue Antworten (Pinnwand)]]></item>
-               <item name="wcf.user.notification.commentResponseOwner.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat eine Antwort zum Kommentar von {if $commentAuthor->userID}<a href="{link controller='User' object=$commentAuthor}{/link}" class="userLink" data-user-id="{@$commentAuthor->userID}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if} an <a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}/response{@$responseID}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a> verfasst.]]></item>
-               <item name="wcf.user.notification.commentResponseOwner.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} und {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} und{/if} {#$others} weitere Benutzer {if $guestTimesTriggered}und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{/if} haben auf den Kommentar von {if $author->userID}<a href="{link controller='User' object=$author}{/link}" class="userLink" data-user-id="{@$author->userID}">{$author->username}</a>{else}{$author->username}{/if} an <a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a> geantwortet.]]></item>
+               <item name="wcf.user.notification.commentResponseOwner.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat eine Antwort zum Kommentar von <strong>{$commentAuthor}</strong> an {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand verfasst.]]></item>
+               <item name="wcf.user.notification.commentResponseOwner.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben auf den Kommentar von <strong>{$author}</strong> an {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand geantwortet.]]></item>
                <item name="wcf.user.notification.commentResponseOwner.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {@$notificationContent[variables][commentAuthor]->username}{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} an {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand [URL:{link controller='User' object=$notificationContent[variables][owner] isEmail=true}#wall/comment{@$commentID}/response{@$responseID}{/link}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.user.notification.commentResponseOwner.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if} an <a href="{link controller='User' object=$notificationContent[variables][owner] isHtmlEmail=true}#wall/comment{@$commentID}/response{@$responseID}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand</a> verfasst:</p>]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.notification.comment"><![CDATA[Neuer Kommentar an {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Pinnwand]]></item>
@@ -5073,7 +5089,7 @@ Benachrichtigungen auf <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|lang
                <item name="wcf.user.notification.com.woltlab.wcf.paidSubscription.user.expiring"><![CDATA[Eine {if LANGUAGE_USE_INFORMAL_VARIANT}deiner{else}Ihrer{/if} Mitgliedschaften läuft bald ab]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.userTrophy.notification.received"><![CDATA[Trophäe erhalten]]></item>
                <item name="wcf.user.notification.trophy.received.title"><![CDATA[Trophäe erhalten]]></item>
-               <item name="wcf.user.notification.trophy.received.message"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} die Trophäe <a href="{$userTrophy->getTrophy()->getLink()}">{$userTrophy->getTrophy()->getTitle()}</a> erhalten.]]></item>
+               <item name="wcf.user.notification.trophy.received.message"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} die Trophäe <strong>{$userTrophy->getTrophy()}</strong> erhalten.]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.administration"><![CDATA[Administration]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.registration.notification.registration"><![CDATA[Neue Registrierung durch Benutzer]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.moderation"><![CDATA[Moderation]]></item>
@@ -5083,22 +5099,23 @@ Benachrichtigungen auf <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|lang
                <item name="wcf.user.notification.com.woltlab.wcf.page.notification.comment"><![CDATA[Neuer Kommentar auf einer Seite]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.page.response.notification.commentResponse"><![CDATA[Neue Antwort auf einen Kommentar auf einer Seite]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.page.response.notification.commentResponseOwner"><![CDATA[Neue Antwort auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar auf einer Seite]]></item>
+               <item name="wcf.user.notification.com.woltlab.wcf.likeableArticle.notification.like"><![CDATA[Jemand hat auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Artikel reagiert]]></item>
                <item name="wcf.user.notification.pageComment.title"><![CDATA[Neuer Kommentar (Seite)]]></item>
                <item name="wcf.user.notification.pageComment.title.stacked"><![CDATA[{#$timesTriggered} neue Kommentare (Seite)]]></item>
-               <item name="wcf.user.notification.pageComment.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat einen Kommentar auf der Seite <a href="{$page->getLink()}#comment{@$commentID}">{$page->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.user.notification.pageComment.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} und {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} und{/if} {#$others} weitere Benutzer {if $guestTimesTriggered}und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{/if} haben Kommentare auf der Seite <a href="{$page->getLink()}#comment{@$commentID}">{$page->getTitle()}</a> verfasst.]]></item>
+               <item name="wcf.user.notification.pageComment.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat einen Kommentar auf der Seite <strong>{$page->getTitle()}</strong> verfasst.]]></item>
+               <item name="wcf.user.notification.pageComment.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben Kommentare auf der Seite <strong>{$page->getTitle()}</strong> verfasst.]]></item>
                <item name="wcf.user.notification.pageComment.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat einen Kommentar{else}haben Kommentare{/if} auf der Seite „{@$notificationContent[variables][page]->getTitle()}“ [URL:{@$notificationContent[variables][page]->getLink()}#comment{@$commentID}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.user.notification.pageComment.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat einen Kommentar{else}haben Kommentare{/if} auf der Seite <a href="{$notificationContent[variables][page]->getLink()}#comment{@$commentID}">{$notificationContent[variables][page]->getTitle()}</a> verfasst:</p>]]></item>
                <item name="wcf.user.notification.pageComment.response.title"><![CDATA[Neue Antwort (Seite)]]></item>
                <item name="wcf.user.notification.pageComment.response.title.stacked"><![CDATA[{#$timesTriggered} neue Antworten (Seite)]]></item>
-               <item name="wcf.user.notification.pageComment.response.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat eine Antwort zu dem Kommentar von {if $commentAuthor->userID}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if} auf der Seite <a href="{$page->getLink()}#comment{@$commentID}/response{@$responseID}">{$page->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.user.notification.pageComment.response.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} und {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} und{/if} {#$others} weitere Benutzer {if $guestTimesTriggered}und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{/if} haben auf den Kommentar von {if $commentAuthor->userID}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if} auf der Seite <a href="{$page->getLink()}#comment{@$commentID}">{$page->getTitle()}</a> geantwortet.]]></item>
+               <item name="wcf.user.notification.pageComment.response.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat eine Antwort zu dem Kommentar von <strong>{$commentAuthor}</strong> auf der Seite <strong>{$page->getTitle()}</strong> verfasst.]]></item>
+               <item name="wcf.user.notification.pageComment.response.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben auf den Kommentar von <strong>{$commentAuthor}</strong> auf der Seite <strong>{$page->getTitle()}</strong> geantwortet.]]></item>
                <item name="wcf.user.notification.pageComment.response.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {@$notificationContent[variables][commentAuthor]->username}{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} auf der Seite „{@$notificationContent[variables][page]->getTitle()}“ [URL:{@$notificationContent[variables][page]->getLink()}#comment{@$commentID}/response{@$responseID}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.user.notification.pageComment.response.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if} auf der Seite <a href="{$notificationContent[variables][page]->getLink()}#comment{@$commentID}">{$notificationContent[variables][page]->getTitle()}</a> verfasst:</p>]]></item>
                <item name="wcf.user.notification.pageComment.responseOwner.title"><![CDATA[Neue Antwort (Seite)]]></item>
                <item name="wcf.user.notification.pageComment.responseOwner.title.stacked"><![CDATA[{#$timesTriggered} neue Antworten (Seite)]]></item>
-               <item name="wcf.user.notification.pageComment.responseOwner.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat eine Antwort auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar auf der Seite <a href="{$page->getLink()}#comment{@$commentID}/response{@$responseID}">{$page->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.user.notification.pageComment.responseOwner.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} und {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} und{/if} {#$others} weitere Benutzer {if $guestTimesTriggered}und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{/if} haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar auf der Seite <a href="{$page->getLink()}#comment{@$commentID}">{$page->getTitle()}</a> geantwortet.]]></item>
+               <item name="wcf.user.notification.pageComment.responseOwner.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat eine Antwort auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar auf der Seite <strong>{$page->getTitle()}</strong> verfasst.]]></item>
+               <item name="wcf.user.notification.pageComment.responseOwner.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar auf der Seite <strong>{$page->getTitle()}</strong> geantwortet.]]></item>
                <item name="wcf.user.notification.pageComment.responseOwner.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar auf der Seite „{@$notificationContent[variables][page]->getTitle()}“ [URL:{@$notificationContent[variables][page]->getLink()}#comment{@$commentID}/response{@$responseID}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.user.notification.pageComment.responseOwner.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar auf der Seite <a href="{$notificationContent[variables][page]->getLink()}#comment{@$commentID}">{$notificationContent[variables][page]->getTitle()}</a> verfasst:</p>]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.article.comment.notification.comment"><![CDATA[Neuer Kommentar zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel]]></item>
@@ -5106,25 +5123,25 @@ Benachrichtigungen auf <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|lang
                <item name="wcf.user.notification.com.woltlab.wcf.article.comment.response.notification.commentResponseOwner"><![CDATA[Neue Antwort auf einen Kommentar zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel]]></item>
                <item name="wcf.user.notification.articleComment.title"><![CDATA[Neuer Kommentar (Artikel)]]></item>
                <item name="wcf.user.notification.articleComment.title.stacked"><![CDATA[{#$timesTriggered} neue Kommentare (Artikel)]]></item>
-               <item name="wcf.user.notification.articleComment.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat einen Kommentar zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel <a href="{$article->getLink()}#comment{@$commentID}">{$article->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.user.notification.articleComment.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} und {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} und{/if} {#$others} weitere Benutzer {if $guestTimesTriggered}und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{/if} haben Kommentare zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel <a href="{$article->getLink()}#comment{@$commentID}">{$article->getTitle()}</a> verfasst.]]></item>
+               <item name="wcf.user.notification.articleComment.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat einen Kommentar zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel <strong>{$article->getTitle()}</strong> verfasst.]]></item>
+               <item name="wcf.user.notification.articleComment.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben Kommentare zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel <strong>{$article->getTitle()}</strong> verfasst.]]></item>
                <item name="wcf.user.notification.articleComment.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat einen Kommentar{else}haben Kommentare{/if} zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel „{@$notificationContent[variables][article]->getTitle()}“ [URL:{@$notificationContent[variables][article]->getLink()}#comment{@$commentID}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.user.notification.articleComment.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat einen Kommentar{else}haben Kommentare{/if} zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel <a href="{$notificationContent[variables][article]->getLink()}#comment{@$commentID}">{$notificationContent[variables][article]->getTitle()}</a> verfasst:</p>]]></item>
                <item name="wcf.user.notification.articleComment.response.title"><![CDATA[Neue Antwort (Artikel)]]></item>
                <item name="wcf.user.notification.articleComment.response.title.stacked"><![CDATA[{#$timesTriggered} neue Antworten (Artikel)]]></item>
-               <item name="wcf.user.notification.articleComment.response.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat eine Antwort zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Kommentar in dem Artikel <a href="{$article->getLink()}#comment{@$commentID}/response{@$responseID}">{$article->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.user.notification.articleComment.response.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} und {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} und{/if} {#$others} weitere Benutzer {if $guestTimesTriggered}und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{/if} haben Antworten zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Kommentar zu dem Artikel <a href="{$article->getLink()}#comment{@$commentID}">{$article->getTitle()}</a> verfasst.]]></item>
+               <item name="wcf.user.notification.articleComment.response.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat eine Antwort zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Kommentar in dem Artikel <strong>{$article->getTitle()}</strong> verfasst.]]></item>
+               <item name="wcf.user.notification.articleComment.response.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben Antworten zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Kommentar zu dem Artikel <strong>{$article->getTitle()}</strong> verfasst.]]></item>
                <item name="wcf.user.notification.articleComment.response.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar zu dem Artikel „{@$notificationContent[variables][article]->getTitle()}“ [URL:{link controller='Article' object=$notificationContent[variables][article] isEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.user.notification.articleComment.response.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} auf {if LANGUAGE_USE_INFORMAL_VARIANT}deinen{else}Ihren{/if} Kommentar zu dem Artikel <a href="{link controller='Article' object=$notificationContent[variables][article] isHtmlEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}">{$notificationContent[variables][article]->getTitle()}</a> verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}</p>]]></item>
                <item name="wcf.user.notification.articleComment.responseOwner.title"><![CDATA[Neue Antwort (Artikel)]]></item>
                <item name="wcf.user.notification.articleComment.responseOwner.title.stacked"><![CDATA[{#$timesTriggered} neue Antworten (Artikel)]]></item>
-               <item name="wcf.user.notification.articleComment.responseOwner.message"><![CDATA[{if !$author->userID}Ein Gast{else}{@$author->getAnchorTag()}{/if} hat eine Antwort zum Kommentar von {if $commentAuthor->userID}<a href="{link controller='User' object=$commentAuthor}{/link}" class="userLink" data-user-id="{@$commentAuthor->userID}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if} zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel <a href="{$article->getLink()}#comment{@$commentID}/response{@$responseID}">{$article->getTitle()}</a> verfasst.]]></item>
-               <item name="wcf.user.notification.articleComment.responseOwner.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} und {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} und{/if} {#$others} weitere Benutzer {if $guestTimesTriggered}und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{/if} haben Antworten zum Kommentar von {if $commentAuthor->userID}<a href="{link controller='User' object=$commentAuthor}{/link}" class="userLink" data-user-id="{@$commentAuthor->userID}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if} zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel <a href="{$article->getLink()}#comment{@$commentID}/response{@$responseID}">{$article->getTitle()}</a> verfasst.]]></item>
+               <item name="wcf.user.notification.articleComment.responseOwner.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat eine Antwort zum Kommentar von {$commentAuthor} zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel <strong>{$article->getTitle()}</strong> verfasst.]]></item>
+               <item name="wcf.user.notification.articleComment.responseOwner.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben Antworten zum Kommentar von {$commentAuthor} zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel <strong>{$article->getTitle()}</strong> verfasst.]]></item>
                <item name="wcf.user.notification.articleComment.responseOwner.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {@$notificationContent[variables][commentAuthor]->username}{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel „{@$notificationContent[variables][article]->getTitle()}“ [URL:{link controller='Article' object=$notificationContent[variables][article] isEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.user.notification.articleComment.responseOwner.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{@$notificationContent[variables][commentAuthor]->username}</a>{else}{@$notificationContent[variables][commentAuthor]->username}{/if} zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel <a href="{link controller='Article' object=$notificationContent[variables][article] isHtmlEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}">{$notificationContent[variables][article]->getTitle()}</a> verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}</p>]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.article.notification.article"><![CDATA[Neuer Artikel in abonnierter Kategorie]]></item>
                <item name="wcf.user.notification.article.title"><![CDATA[Neuer Artikel]]></item>
-               <item name="wcf.user.notification.article.message"><![CDATA[{if $author->userID}{@$author->getAnchorTag()}{else}Ein Gast{/if} hat den Artikel <a href="{$article->getLink()}">{$article->getTitle()}</a> verfasst.]]></item>
+               <item name="wcf.user.notification.article.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}Ein Gast{/if} hat den Artikel <strong>{$article->getTitle()}</strong> verfasst.]]></item>
                <item name="wcf.user.notification.article.mail.plaintext"><![CDATA[{if $event->getAuthor()->userID}{@$event->getAuthor()->username} [URL:{link controller='User' object=$event->getAuthor() isEmail=true}{/link}]{else}Ein Gast{/if} hat den Artikel „{@$notificationContent[variables][articleContent]->getTitle()}“ [URL:{link controller='Article' object=$notificationContent[variables][articleContent] isEmail=true}{/link}] verfasst:]]></item>
                <item name="wcf.user.notification.article.mail.html"><![CDATA[<p>{if $event->getAuthor()->userID}<a href="{link controller='User' object=$event->getAuthor() isHtmlEmail=true}{/link}">{$event->getAuthor()->username}</a>{else}Ein Gast{/if} hat den Artikel <a href="{link controller='Article' object=$notificationContent[variables][articleContent] isHtmlEmail=true}{/link}">{$notificationContent[variables][articleContent]->getTitle()}</a> verfasst:</p>]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.articleComment.response.notification.commentResponseOwner"><![CDATA[Neue Antwort auf einen Kommentar zu Ihrem Artikel]]></item>
@@ -5132,8 +5149,8 @@ Benachrichtigungen auf <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|lang
                <item name="wcf.user.notification.com.woltlab.wcf.articleComment.notification.comment"><![CDATA[Neuer Kommentar zu Ihrem Artikel]]></item>
                <item name="wcf.user.notification.userRegistration.title"><![CDATA[Neue Benutzer-Registrierung]]></item>
                <item name="wcf.user.notification.userRegistration.title.stacked"><![CDATA[{#$count} neue Benutzer-Registrierungen]]></item>
-               <item name="wcf.user.notification.userRegistration.message"><![CDATA[{@$author->getAnchorTag()} hat sich registriert.]]></item>
-               <item name="wcf.user.notification.userRegistration.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} und {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} und {#$others} weitere Benutzer{/if} haben sich registriert.]]></item>
+               <item name="wcf.user.notification.userRegistration.message"><![CDATA[<strong>{$author}</strong> hat sich registriert.]]></item>
+               <item name="wcf.user.notification.userRegistration.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} haben sich registriert.]]></item>
                <item name="wcf.user.notification.userRegistration.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1}hat{else}haben{/if} sich registriert.]]></item>
                <item name="wcf.user.notification.userRegistration.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1}hat{else}haben{/if} sich registriert:</p>]]></item>
        </category>
@@ -5174,7 +5191,6 @@ Benachrichtigungen auf <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|lang
                <item name="wcf.user.option.birthday"><![CDATA[Geburtstag]]></item>
                <item name="wcf.user.option.birthdayShowYear"><![CDATA[Geburtsjahr im Profil anzeigen]]></item>
                <item name="wcf.user.option.birthdayShowYear.description"><![CDATA[Mitglieder können dadurch {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} Alter sehen.]]></item>
-               <item name="wcf.user.option.canMail"><![CDATA[Kann E-Mails senden]]></item>
                <item name="wcf.user.option.canViewEmailAddress"><![CDATA[Kann E-Mail-Adresse sehen]]></item>
                <item name="wcf.user.option.canViewOnlineStatus"><![CDATA[Kann Online-Status sehen]]></item>
                <item name="wcf.user.option.canViewProfile"><![CDATA[Kann Benutzerprofil sehen]]></item>
@@ -5198,7 +5214,6 @@ Benachrichtigungen auf <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|lang
                <item name="wcf.user.option.occupation"><![CDATA[Beruf]]></item>
                <item name="wcf.user.option.showSignature"><![CDATA[Persönliche Signatur anderer Mitglieder anzeigen]]></item>
                <item name="wcf.user.option.timezone"><![CDATA[Zeitzone]]></item>
-               <item name="wcf.user.option.icq"><![CDATA[ICQ]]></item>
                <item name="wcf.user.option.skype"><![CDATA[Skype]]></item>
                <item name="wcf.user.option.facebook"><![CDATA[Facebook]]></item>
                <item name="wcf.user.option.twitter"><![CDATA[Twitter]]></item>
@@ -5210,8 +5225,6 @@ Benachrichtigungen auf <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|lang
                <item name="wcf.user.option.searchBooleanOption"><![CDATA[Auswahl des Benutzers bei „{$option->getTitle()}“:]]></item>
        </category>
        <category name="wcf.user.mail">
-               <item name="wcf.user.mail.information"><![CDATA[Informationen]]></item>
-               <item name="wcf.user.mail.mail.subject"><![CDATA[Nachricht von {@$username}: {@$subject}]]></item>
                <item name="wcf.user.mail.mail.plaintext"><![CDATA[Hallo {@$mailbox->getUser()->username},
 
 „{@$username}“ hat {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if} über die Website {@PAGE_TITLE|language} [URL:{link isEmail=true}{/link}] folgende Nachricht gesandt:
@@ -5222,11 +5235,6 @@ Benachrichtigungen auf <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|lang
 <p>„{$username}“ hat {if LANGUAGE_USE_INFORMAL_VARIANT}dir{else}Ihnen{/if} über die Website <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|language}</a> folgende Nachricht gesandt:</p>
 
 <p>{@$message|newlineToBreak}</p>]]></item>
-               <item name="wcf.user.mail.message"><![CDATA[Nachricht]]></item>
-               <item name="wcf.user.mail.senderEmail"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} E-Mail-Adresse]]></item>
-               <item name="wcf.user.mail.sent"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Deine{else}Ihre{/if} Nachricht an {$user->username} wurde erfolgreich versandt.]]></item>
-               <item name="wcf.user.mail.showAddress"><![CDATA[Meine E-Mail-Adresse als Absender-Adresse benutzen. Der Empfänger kann direkt auf die Nachricht antworten.]]></item>
-               <item name="wcf.user.mail.subject"><![CDATA[Betreff]]></item>
        </category>
        <category name="wcf.user.rank">
                <item name="wcf.user.rank.administrator"><![CDATA[Administrator]]></item>
index 9ba53b5b930e1b3a65e1ab0396f8f52e2f14bfdb..dd0465612cb87d3fd10ac7e632caefd99e632251 100644 (file)
@@ -653,7 +653,7 @@ ATTENTION: The messages listed above are greatly shortened. You can view details
                <item name="wcf.acp.group.option.admin.management.canViewLog"><![CDATA[Can view log files]]></item>
                <item name="wcf.acp.group.option.admin.configuration.canManageApplication"><![CDATA[Can manage apps]]></item>
                <item name="wcf.acp.group.option.admin.management.canManageCronjob"><![CDATA[Can manage cronjobs]]></item>
-               <item name="wcf.acp.group.option.admin.configuration.package.canEditServer"><![CDATA[Can edit update servers]]></item>
+               <item name="wcf.acp.group.option.admin.configuration.package.canEditServer"><![CDATA[Can edit package servers]]></item>
                <item name="wcf.acp.group.option.admin.configuration.package.canInstallPackage"><![CDATA[Can install packages]]></item>
                <item name="wcf.acp.group.option.admin.configuration.package.canUninstallPackage"><![CDATA[Can uninstall packages]]></item>
                <item name="wcf.acp.group.option.admin.configuration.package.canUpdatePackage"><![CDATA[Can update packages]]></item>
@@ -733,7 +733,6 @@ ATTENTION: The messages listed above are greatly shortened. You can view details
                <item name="wcf.acp.group.option.user.profile.avatar.maxSize"><![CDATA[Maximum Image File Size]]></item>
                <item name="wcf.acp.group.option.user.profile.canChangeEmail"><![CDATA[Can change their email address]]></item>
                <item name="wcf.acp.group.option.user.profile.canEditUserTitle"><![CDATA[Can edit their user title]]></item>
-               <item name="wcf.acp.group.option.user.profile.canMail"><![CDATA[Can send emails to users]]></item>
                <item name="wcf.acp.group.option.user.profile.canQuit"><![CDATA[Can delete their user account]]></item>
                <item name="wcf.acp.group.option.user.profile.canRename"><![CDATA[Can change their username]]></item>
                <item name="wcf.acp.group.option.user.profile.canViewMembersList"><![CDATA[Can view members list]]></item>
@@ -744,8 +743,8 @@ ATTENTION: The messages listed above are greatly shortened. You can view details
                <item name="wcf.acp.group.option.user.signature.disallowedBBCodes.description"><![CDATA[Selected BBCodes <em>cannot</em> be used in the signature.]]></item>
                <item name="wcf.acp.group.priority"><![CDATA[Priority]]></item>
                <item name="wcf.acp.group.priority.description"><![CDATA[Determines the display order on the team page, user rank, and ‘users online’ marking based on highest priority.]]></item>
-               <item name="wcf.acp.group.userOnlineMarking"><![CDATA[Users Online Marking]]></item>
-               <item name="wcf.acp.group.userOnlineMarking.description"><![CDATA[Adjust the HTML formatting for members of this user group in the ‘users online’ list. <em>&lt;strong&gt;%s&lt;/strong&gt;</em> results in a bolder appearance.]]></item>
+               <item name="wcf.acp.group.userOnlineMarking"><![CDATA[User Marking]]></item>
+               <item name="wcf.acp.group.userOnlineMarking.description"><![CDATA[Adjust the HTML formatting for members of this user group. <em>&lt;strong&gt;%s&lt;/strong&gt;</em> results in a bolder appearance.]]></item>
                <item name="wcf.acp.group.userOnlineMarking.error.invalid"><![CDATA[There must be an occurrence of “%s”.]]></item>
                <item name="wcf.acp.group.showOnTeamPage"><![CDATA[Display members on team page]]></item>
                <item name="wcf.acp.group.option.admin.user.canEnableUser"><![CDATA[Can approve users]]></item>
@@ -1357,7 +1356,7 @@ ATTENTION: The messages listed above are greatly shortened. You can view details
                <item name="wcf.acp.option.external_link_target_blank.description"><![CDATA[Appends the attribute “target="_blank"” to external links, causing the user’s browser to open the link in a new window.]]></item>
                <item name="wcf.acp.option.enable_benchmark"><![CDATA[Enable benchmark]]></item>
                <item name="wcf.acp.option.enable_benchmark.description"><![CDATA[Captures additional data on resource usage by individual components. It is strongly recommended to disable this option in production environments.]]></item>
-               <item name="wcf.acp.option.category.general.system.packageServer"><![CDATA[Update Server]]></item>
+               <item name="wcf.acp.option.category.general.system.packageServer"><![CDATA[Package Server]]></item>
                <item name="wcf.acp.option.package_server_auth_code"><![CDATA[Authentication Code]]></item>
                <item name="wcf.acp.option.package_server_auth_code.description"><![CDATA[Your authentication code is available in the customers area on woltlab.com.]]></item>
                <item name="wcf.acp.option.enable_woltlab_news"><![CDATA[Display WoltLab news]]></item>
@@ -1738,6 +1737,7 @@ The database is carefully maintained, but there will be always be a margin of er
                <item name="wcf.acp.option.blacklist_sfs_action.block"><![CDATA[Block]]></item>
                <item name="wcf.acp.option.blacklist_sfs_action.description"><![CDATA[There is always the risk of a false-positive match, therefore it is highly recommended to set it to <strong>Disable and require manual approval</strong>.]]></item>
                <item name="wcf.acp.option.blacklist_sfs_action.disable"><![CDATA[Disable and require manual approval]]></item>
+               <item name="wcf.acp.option.module_amp"><![CDATA[<abbr title="Accelerated Mobile Pages">AMP</abbr>]]></item>
        </category>
        <category name="wcf.acp.customOption">
                <item name="wcf.acp.customOption.list"><![CDATA[Option Fields]]></item>
@@ -1790,7 +1790,7 @@ The database is carefully maintained, but there will be always be a margin of er
                <item name="wcf.acp.package.description"><![CDATA[Description]]></item>
                <item name="wcf.acp.package.error.cli.installIsApplication"><![CDATA[Apps cannot be installed via CLI.]]></item>
                <item name="wcf.acp.package.error.exceedsPhpLimit"><![CDATA[The file exceeds the PHP limit “upload_max_filesize” and/or “post_max_size”.]]></item>
-               <item name="wcf.acp.package.error.noUniqueAbbrevation"><![CDATA[There is already an app installed which has the same abbreviation.]]></item>
+               <item name="wcf.acp.package.error.noUniqueAbbreviation"><![CDATA[There is already an app installed which has the same abbreviation.]]></item>
                <item name="wcf.acp.package.error.noValidPackage"><![CDATA[The uploaded archive is invalid.]]></item>
                <item name="wcf.acp.package.error.sql.createTable"><![CDATA[Overwrite Existing Tables]]></item>
                <item name="wcf.acp.package.error.sql.createTable.description"><![CDATA[The tables above already exist and will be replaced during the installation. Any contained data will be lost.]]></item>
@@ -1999,14 +1999,14 @@ If you have <strong>already bought the licenses for the listed apps</strong>, th
                <item name="wcf.acp.pluginStore.purchasedItems"><![CDATA[Purchased Products (Plugin-Store)]]></item>
                <item name="wcf.acp.pluginStore.purchasedItems.noResults"><![CDATA[The search returned no results, because either you have not purchased any products yet, or your purchases are not compatible with this version.]]></item>
                <item name="wcf.acp.pluginStore.purchasedItems.status.update"><![CDATA[Newer Version Available]]></item>
-               <item name="wcf.acp.pluginStore.purchasedItems.status.unavailable"><![CDATA[Update-Server not installed]]></item>
+               <item name="wcf.acp.pluginStore.purchasedItems.status.unavailable"><![CDATA[Package server not installed]]></item>
                <item name="wcf.acp.pluginStore.purchasedItems.status.upToDate"><![CDATA[You already have the most recent version installed]]></item>
-               <item name="wcf.acp.pluginStore.purchasedItems.status.requireUpdate"><![CDATA[Update-Server must be updated]]></item>
+               <item name="wcf.acp.pluginStore.purchasedItems.status.requireUpdate"><![CDATA[Package server must be updated]]></item>
                <item name="wcf.acp.pluginStore.purchasedItems.status.install.confirmMessage"><![CDATA[Do you really want to install the product <span class="confirmationObject">{$product[packageName]}</span>?]]></item>
-               <item name="wcf.acp.pluginStore.purchasedItems.updateServer.disabled"><![CDATA[The Update-Server for “{$wcfMajorRelease}” (“http://store.woltlab.com/{$wcfMajorRelease}/”) is disabled and cannot be used for new installs or updates.]]></item>
-               <item name="wcf.acp.pluginStore.purchasedItems.updateServer.missing"><![CDATA[The Update-Server for “{$wcfMajorRelease}” does not exist in your installation. If you wish to install the packages below, you must <a href="{link controller='PackageUpdateServerAdd'}{/link}">add it</a>.<br>The server address is: “http://store.woltlab.com/{$wcfMajorRelease}/”]]></item>
-               <item name="wcf.acp.pluginStore.purchasedItems.updateServer.requireUpdate"><![CDATA[The Update-Server for “{$wcfMajorRelease}” has not been queried yet, please search for updates to fetch the package list.]]></item>
-               <item name="wcf.acp.pluginStore.purchasedItems.wcfMajorRelease"><![CDATA[Update-Server for “{$wcfMajorRelease}”]]></item>
+               <item name="wcf.acp.pluginStore.purchasedItems.updateServer.disabled"><![CDATA[The package server for “{$wcfMajorRelease}” (“http://store.woltlab.com/{$wcfMajorRelease}/”) is disabled and cannot be used for new installs or updates.]]></item>
+               <item name="wcf.acp.pluginStore.purchasedItems.updateServer.missing"><![CDATA[The package server for “{$wcfMajorRelease}” does not exist in your installation. If you wish to install the packages below, you must <a href="{link controller='PackageUpdateServerAdd'}{/link}">add it</a>.<br>The server address is: “http://store.woltlab.com/{$wcfMajorRelease}/”]]></item>
+               <item name="wcf.acp.pluginStore.purchasedItems.updateServer.requireUpdate"><![CDATA[The package server for “{$wcfMajorRelease}” has not been queried yet, please search for updates to fetch the package list.]]></item>
+               <item name="wcf.acp.pluginStore.purchasedItems.wcfMajorRelease"><![CDATA[Package server for “{$wcfMajorRelease}”]]></item>
        </category>
        <category name="wcf.acp.pip">
                <item name="wcf.acp.pip.objectType.com.woltlab.wcf.adLocation.data.title"><![CDATA[Ad Location Data]]></item>
@@ -2323,8 +2323,8 @@ If you have <strong>already bought the licenses for the listed apps</strong>, th
                <item name="wcf.acp.pip.eventListener.listenerName.error.notUnique"><![CDATA[The entered identifier is already used by another event listener.]]></item>
                <item name="wcf.acp.pip.eventListener.eventClassName"><![CDATA[PHP Event Class]]></item>
                <item name="wcf.acp.pip.eventListener.eventClassName.description"><![CDATA[The entered class (without leading backslash) fires the event. Alternatively, the entered class inherits from the the class firing the event.]]></item>
-               <item name="wcf.acp.pip.eventListener.eventName"><![CDATA[Event Name]]></item>
-               <item name="wcf.acp.pip.eventListener.eventName.description"><![CDATA[Name of the event of the relevant class the event listener is listening to.]]></item>
+               <item name="wcf.acp.pip.eventListener.eventName"><![CDATA[Event Names]]></item>
+               <item name="wcf.acp.pip.eventListener.eventName.description"><![CDATA[Names of the event of the relevant class the event listener is listening to.]]></item>
                <item name="wcf.acp.pip.eventListener.listenerClassName"><![CDATA[PHP Event Listener Class]]></item>
                <item name="wcf.acp.pip.eventListener.environment"><![CDATA[Environment]]></item>
                <item name="wcf.acp.pip.eventListener.environment.description"><![CDATA[The environment determines whether the event listener is executed in the frontend (<kbd>user</kbd>) or the ACP (<kbd>admin</kbd>).]]></item>
@@ -2685,7 +2685,7 @@ If you have <strong>already bought the licenses for the listed apps</strong>, th
                <item name="wcf.acp.updateServer.delete.sure"><![CDATA[Do you really want to delete the server <span class="confirmationObject">{$updateServer->serverURL}</span>?]]></item>
                <item name="wcf.acp.updateServer.edit"><![CDATA[Edit Server]]></item>
                <item name="wcf.acp.updateServer.errorMessage"><![CDATA[Error Message]]></item>
-               <item name="wcf.acp.updateServer.list"><![CDATA[Update-Servers]]></item>
+               <item name="wcf.acp.updateServer.list"><![CDATA[Package Servers]]></item>
                <item name="wcf.acp.updateServer.lastErrorMessage"><![CDATA[Errors occurred on last connection attempt:]]></item>
                <item name="wcf.acp.updateServer.loginPassword"><![CDATA[Password]]></item>
                <item name="wcf.acp.updateServer.loginPassword.description"><![CDATA[Enter your authentication password (optional).]]></item>
@@ -2962,7 +2962,6 @@ If you have <strong>already bought the licenses for the listed apps</strong>, th
                <item name="wcf.acp.user.search.display.itemsPerPage"><![CDATA[Users per Page]]></item>
                <item name="wcf.acp.user.search.display.sort"><![CDATA[Sort Order]]></item>
                <item name="wcf.acp.user.search.error.noMatches"><![CDATA[No users matched your criteria.]]></item>
-               <item name="wcf.acp.user.search.matches"><![CDATA[{#$items} result{if $items != 1}s{/if}]]></item>
                <item name="wcf.acp.user.sendMail"><![CDATA[Send Email to Users]]></item>
                <item name="wcf.acp.user.sendMail.all"><![CDATA[Email All Users]]></item>
                <item name="wcf.acp.user.sendMail.enableHTML"><![CDATA[Enable HTML code in email message]]></item>
@@ -3174,6 +3173,10 @@ full extend.]]></item>
                <item name="wcf.article.search.results"><![CDATA[Search Results]]></item>
                <item name="wcf.article.publicationStatus.0"><![CDATA[This article has not been published yet.]]></item>
                <item name="wcf.article.publicationStatus.2"><![CDATA[This article will be published on {@$publicationDate|plainTime}.]]></item>
+               <item name="wcf.article.like.notification.title"><![CDATA[Reaction to your article]]></item>
+               <item name="wcf.article.like.notification.title.stacked"><![CDATA[{#$count} users reacted to your article]]></item>
+               <item name="wcf.article.like.notification.message"><![CDATA[<strong>{$author}</strong> reacted to your article <strong>{$article->getTitle()}</strong> ({@$__wcf->getReactionHandler()->renderInlineList($reactions)}).]]></item>
+               <item name="wcf.article.like.notification.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} reacted to article <strong>{$article->getTitle()}</strong> ({@$__wcf->getReactionHandler()->renderInlineList($reactions)}).]]></item>
        </category>
        <category name="wcf.attachment">
                <item name="wcf.attachment.file.info"><![CDATA[({@$attachment->filesize|filesize}, downloaded <b>{#$attachment->downloads}</b> times{if $attachment->downloads > 0}, last: {@$attachment->lastDownloadTime|time}{/if})]]></item>
@@ -3959,14 +3962,14 @@ Attachments:
                <item name="wcf.like.likes.noMoreEntries"><![CDATA[There are no new likes at the moment.]]></item>
                <item name="wcf.like.dislikes.more"><![CDATA[More Dislikes]]></item>
                <item name="wcf.like.dislikes.noMoreEntries"><![CDATA[There are no new dislikes at the moment.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.user.profileComment"><![CDATA[Reacted with <span title="{$like->getReactionType()->getTitle()}" class="jsTooltip">{@$like->getReactionType()->renderIcon()}</span> to the comment by {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}a guest{/if} on <a href="{$comment->getLink()}">{$user->username}’s wall</a>.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.user.profileComment.response"><![CDATA[Reacted with <span title="{$like->getReactionType()->getTitle()}" class="jsTooltip">{@$like->getReactionType()->renderIcon()}</span> to the response by {if $responseAuthor}<a href="{link controller='User' object=$responseAuthor}{/link}">{$responseAuthor->username}</a>{else}a guest{/if} on the comment by {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}a guest{/if} on <a href="{$response->getLink()}">{$user->username}’s wall</a>.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.user.profileComment"><![CDATA[Reacted with {@$reaction->render()} to the comment by {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}a guest{/if} on <a href="{$comment->getLink()}">{$user->username}’s wall</a>.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.user.profileComment.response"><![CDATA[Reacted with {@$reaction->render()} to the response by {if $responseAuthor}<a href="{link controller='User' object=$responseAuthor}{/link}">{$responseAuthor->username}</a>{else}a guest{/if} on the comment by {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}a guest{/if} on <a href="{$response->getLink()}">{$user->username}’s wall</a>.]]></item>
                <item name="wcf.like.objectType.com.woltlab.wcf.likeableArticle"><![CDATA[Article]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.likeableArticle"><![CDATA[Reacted with <span title="{$like->getReactionType()->getTitle()}" class="jsTooltip">{@$like->getReactionType()->renderIcon()}</span> to the article <a href="{$article->getLink()}">{$article->getTitle()}</a>.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.articleComment"><![CDATA[Reacted with <span title="{$like->getReactionType()->getTitle()}" class="jsTooltip">{@$like->getReactionType()->renderIcon()}</span> to the comment by {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}a guest{/if} on the article <a href="{$comment->getLink()}">{$articleContent->getTitle()}</a>.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.articleComment.response"><![CDATA[Reacted with <span title="{$like->getReactionType()->getTitle()}" class="jsTooltip">{@$like->getReactionType()->renderIcon()}</span> to the response by {if $responseAuthor}<a href="{link controller='User' object=$responseAuthor}{/link}">{$responseAuthor->username}</a>{else}a guest{/if} on the comment by {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}a guest{/if} on the article <a href="{$response->getLink()}">{$articleContent->getTitle()}</a>.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.pageComment"><![CDATA[Reacted with <span title="{$like->getReactionType()->getTitle()}" class="jsTooltip">{@$like->getReactionType()->renderIcon()}</span> to the comment by {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}a guest{/if} on the page <a href="{$comment->getLink()}">{$page->getTitle()}</a>.]]></item>
-               <item name="wcf.like.title.com.woltlab.wcf.pageComment.response"><![CDATA[Reacted with <span title="{$like->getReactionType()->getTitle()}" class="jsTooltip">{@$like->getReactionType()->renderIcon()}</span> to the response by {if $responseAuthor}<a href="{link controller='User' object=$responseAuthor}{/link}">{$responseAuthor->username}</a>{else}a guest{/if} on the comment by {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}a guest{/if} on the page <a href="{$response->getLink()}">{$page->getTitle()}</a>.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.likeableArticle"><![CDATA[Reacted with {@$reaction->render()} to the article <a href="{$article->getLink()}">{$article->getTitle()}</a>.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.articleComment"><![CDATA[Reacted with {@$reaction->render()} to the comment by {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}a guest{/if} on the article <a href="{$comment->getLink()}">{$articleContent->getTitle()}</a>.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.articleComment.response"><![CDATA[Reacted with {@$reaction->render()} to the response by {if $responseAuthor}<a href="{link controller='User' object=$responseAuthor}{/link}">{$responseAuthor->username}</a>{else}a guest{/if} on the comment by {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}a guest{/if} on the article <a href="{$response->getLink()}">{$articleContent->getTitle()}</a>.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.pageComment"><![CDATA[Reacted with {@$reaction->render()} to the comment by {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}a guest{/if} on the page <a href="{$comment->getLink()}">{$page->getTitle()}</a>.]]></item>
+               <item name="wcf.like.title.com.woltlab.wcf.pageComment.response"><![CDATA[Reacted with {@$reaction->render()} to the response by {if $responseAuthor}<a href="{link controller='User' object=$responseAuthor}{/link}">{$responseAuthor->username}</a>{else}a guest{/if} on the comment by {if $commentAuthor}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}a guest{/if} on the page <a href="{$response->getLink()}">{$page->getTitle()}</a>.]]></item>
                <item name="wcf.like.reaction.label"><![CDATA[{#$reactions} reaction{if $reactions != 1}s{/if}]]></item>
                <item name="wcf.like.reaction.more"><![CDATA[More Reactions]]></item>
                <item name="wcf.like.reaction.noMoreEntries"><![CDATA[There are no new reactions at the moment.]]></item>
@@ -4049,7 +4052,7 @@ Attachments:
                <item name="wcf.message.quote.manageQuotes"><![CDATA[Manage Quotes]]></item>
                <item name="wcf.message.quote.quoteSelected"><![CDATA[Save Quote]]></item>
                <item name="wcf.message.quote.quoteAndReply"><![CDATA[Insert Quote]]></item>
-               <item name="wcf.message.quote.showQuotes"><![CDATA[Quotes (#count#)]]></item>
+               <item name="wcf.message.quote.showQuotes"><![CDATA[{if $count == 1}One Quote{else}{#$count} Quotes{/if}]]></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>
@@ -4092,6 +4095,8 @@ Attachments:
                <item name="wcf.moderation.assignedUser.change"><![CDATA[Change Assigned User]]></item>
                <item name="wcf.moderation.assignedUser.error.notAffected"><![CDATA[This user does not have sufficient privileges.]]></item>
                <item name="wcf.moderation.assignedUser.nobody"><![CDATA[Nobody]]></item>
+               <item name="wcf.moderation.assignedUsername"><![CDATA[Assigned User]]></item>
+               <item name="wcf.moderation.filter"><![CDATA[Filter Items]]></item>
                <item name="wcf.moderation.filterByType"><![CDATA[Type]]></item>
                <item name="wcf.moderation.filterByUser"><![CDATA[Assigned User]]></item>
                <item name="wcf.moderation.filterByUser.allEntries"><![CDATA[All Items]]></item>
@@ -4104,16 +4109,16 @@ Attachments:
                <item name="wcf.moderation.noMoreItems"><![CDATA[You have no recent items.]]></item>
                <item name="wcf.moderation.notification.comment.title"><![CDATA[New comment (Moderation)]]></item>
                <item name="wcf.moderation.notification.comment.title.stacked"><![CDATA[{#$timesTriggered} new comments (Moderation)]]></item>
-               <item name="wcf.moderation.notification.comment.message"><![CDATA[{if !$author->userID}A guest{else}{@$author->getAnchorTag()}{/if} wrote a comment on the moderation entry <a href="{@$moderationQueue->getLink()}#comment{@$commentID}">{$moderationQueue->getTitle()}</a>.]]></item>
-               <item name="wcf.moderation.notification.comment.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$others} other users{/if} wrote comments on the moderation entry <a href="{@$moderationQueue->getLink()}">{$moderationQueue->getTitle()}</a>.]]></item>
-               <item name="wcf.moderation.notification.comment.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a comment{else}comments{/if} on the moderation entry {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.moderation.notification.comment.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a comment{else}comments{/if} on the moderation entry <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a>:</p>]]></item>
+               <item name="wcf.moderation.notification.comment.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} commented on the moderation entry <strong>{$moderationQueue}</strong>.]]></item>
+               <item name="wcf.moderation.notification.comment.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} commented on the moderation entry <strong>{$moderationQueue}</strong>.]]></item>
+               <item name="wcf.moderation.notification.comment.mail.plaintext"><![CDATA[{@$authorList} commented on the moderation entry {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
+               <item name="wcf.moderation.notification.comment.mail.html"><![CDATA[<p>{@$authorList} commented on the moderation entry <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a>:</p>]]></item>
                <item name="wcf.moderation.notification.commentResponse.title"><![CDATA[New reply (Moderation)]]></item>
                <item name="wcf.moderation.notification.commentResponse.title.stacked"><![CDATA[{#$timesTriggered} new replies (Moderation)]]></item>
-               <item name="wcf.moderation.notification.commentResponse.message"><![CDATA[{if !$author->userID}A guest{else}{@$author->getAnchorTag()}{/if} wrote a reply to a comment by {if $commentAuthor->userID}<a href="{link controller='User' object=$commentAuthor}{/link}" class="userLink" data-user-id="{@$commentAuthor->userID}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if} on the moderation entry <a href="{@$moderationQueue->getLink()}#comment{@$commentID}/response{@$responseID}">{$moderationQueue->getTitle()}</a>.]]></item>
-               <item name="wcf.moderation.notification.commentResponse.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$others} other users{/if} wrote replies to comments on the moderation entry <a href="{@$moderationQueue->getLink()}#comment{@$commentID}">{$moderationQueue->getTitle()}</a>.]]></item>
-               <item name="wcf.moderation.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {@$notificationContent[variables][commentAuthor]->username}’s{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} comment on the moderation entry {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.moderation.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if}’s comment on the moderation entry <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a>:</p>]]></item>
+               <item name="wcf.moderation.notification.commentResponse.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} replied to a comment by <strong>{$commentAuthor}</strong> on the moderation entry <strong>{$moderationQueue}</strong>.]]></item>
+               <item name="wcf.moderation.notification.commentResponse.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} replied to comments on the moderation entry <strong>{$moderationQueue}</strong>.]]></item>
+               <item name="wcf.moderation.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} replied to {@$notificationContent[variables][commentAuthor]->username}’s{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} comment on the moderation entry {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
+               <item name="wcf.moderation.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} replied to {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if}’s comment on the moderation entry <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a>:</p>]]></item>
                <item name="wcf.moderation.status"><![CDATA[Status]]></item>
                <item name="wcf.moderation.status.outstanding"><![CDATA[Pending]]></item>
                <item name="wcf.moderation.status.processing"><![CDATA[In Progress]]></item>
@@ -4141,6 +4146,9 @@ Attachments:
                <item name="wcf.moderation.jumpToContent"><![CDATA[Go to Related Content]]></item>
                <item name="wcf.moderation.markAllAsRead"><![CDATA[Mark All Items Read]]></item>
                <item name="wcf.moderation.markAsRead.doubleClick"><![CDATA[Double-Click to Mark This Item Read]]></item>
+               <item name="wcf.moderation.comments"><![CDATA[Comments]]></item>
+               <item name="wcf.moderation.username"><![CDATA[Author]]></item>
+               <item name="wcf.moderation.noEntries"><![CDATA[There are no items at the moment.{if $hasActiveFilter} <a href="#" class="jsStaticDialog" data-dialog-id="moderationListSortFilter" role="button">Change the applied filters.</a>{/if}]]></item>
        </category>
        <category name="wcf.moderation.activation">
                <item name="wcf.moderation.activation"><![CDATA[Approval]]></item>
@@ -4150,16 +4158,16 @@ Attachments:
                <item name="wcf.moderation.activation.enableContent.confirmMessage"><![CDATA[Do you really want to approve this content?]]></item>
                <item name="wcf.moderation.activation.notification.comment.title"><![CDATA[New comment (Approval)]]></item>
                <item name="wcf.moderation.activation.notification.comment.title.stacked"><![CDATA[{#$timesTriggered} new comments (Approval)]]></item>
-               <item name="wcf.moderation.activation.notification.comment.message"><![CDATA[{if !$author->userID}A guest{else}{@$author->getAnchorTag()}{/if} wrote a comment on <a href="{@$moderationQueue->getLink()}#comment{@$commentID}">{$moderationQueue->getTitle()}</a> waiting for approval.]]></item>
-               <item name="wcf.moderation.activation.notification.comment.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$others} other users{/if} wrote comments on <a href="{@$moderationQueue->getLink()}">{$moderationQueue->getTitle()}</a> waiting for approval.]]></item>
-               <item name="wcf.moderation.activation.notification.comment.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a comment{else}comments{/if} on {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}] waiting for approval{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.moderation.activation.notification.comment.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a comment{else}comments{/if} on the moderation entry <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a> waiting for approval:</p>]]></item>
+               <item name="wcf.moderation.activation.notification.comment.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} commented on <strong>{$moderationQueue}</strong> waiting for approval.]]></item>
+               <item name="wcf.moderation.activation.notification.comment.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} commented on <strong>{$moderationQueue}</strong> waiting for approval.]]></item>
+               <item name="wcf.moderation.activation.notification.comment.mail.plaintext"><![CDATA[{@$authorList} commented on {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}] waiting for approval{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
+               <item name="wcf.moderation.activation.notification.comment.mail.html"><![CDATA[<p>{@$authorList} commented on the moderation entry <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a> waiting for approval:</p>]]></item>
                <item name="wcf.moderation.activation.notification.commentResponse.title"><![CDATA[New reply (Approval)]]></item>
                <item name="wcf.moderation.activation.notification.commentResponse.title.stacked"><![CDATA[{#$timesTriggered} new replies (Approval)]]></item>
-               <item name="wcf.moderation.activation.notification.commentResponse.message"><![CDATA[{if !$author->userID}A guest{else}{@$author->getAnchorTag()}{/if} wrote a reply to a comment by {if $commentAuthor->userID}<a href="{link controller='User' object=$commentAuthor}{/link}" class="userLink" data-user-id="{@$commentAuthor->userID}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if} on <a href="{@$moderationQueue->getLink()}#comment{@$commentID}/response{@$responseID}">{$moderationQueue->getTitle()}</a> waiting for approval.]]></item>
-               <item name="wcf.moderation.activation.notification.commentResponse.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$others} other users{/if} wrote replies to comments on <a href="{@$moderationQueue->getLink()}#comment{@$commentID}">{$moderationQueue->getTitle()}</a> waiting for approval.]]></item>
-               <item name="wcf.moderation.activation.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {@$notificationContent[variables][commentAuthor]->username}’s{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} comment on {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}] waiting for approval{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.moderation.activation.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if}’s comment on <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a>  waiting for approval:</p>]]></item>
+               <item name="wcf.moderation.activation.notification.commentResponse.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} replied to a comment by <strong>{$commentAuthor}</strong> on <strong>{$moderationQueue}</strong> waiting for approval.]]></item>
+               <item name="wcf.moderation.activation.notification.commentResponse.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} replied to comments on <strong>{$moderationQueue}</strong> waiting for approval.]]></item>
+               <item name="wcf.moderation.activation.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} replied to {@$notificationContent[variables][commentAuthor]->username}’s{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} comment on {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}] waiting for approval{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
+               <item name="wcf.moderation.activation.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} replied to {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if}’s comment on <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a>  waiting for approval:</p>]]></item>
                <item name="wcf.moderation.activation.removeContent"><![CDATA[Delete Content]]></item>
                <item name="wcf.moderation.activation.removeContent.confirmMessage"><![CDATA[Do you really want to delete this content?]]></item>
        </category>
@@ -4169,16 +4177,16 @@ Attachments:
                <item name="wcf.moderation.report.details"><![CDATA[Information]]></item>
                <item name="wcf.moderation.report.notification.comment.title"><![CDATA[New comment (Report)]]></item>
                <item name="wcf.moderation.report.notification.comment.title.stacked"><![CDATA[{#$timesTriggered} new comments (Report)]]></item>
-               <item name="wcf.moderation.report.notification.comment.message"><![CDATA[{if !$author->userID}A guest{else}{@$author->getAnchorTag()}{/if} wrote a comment on the report <a href="{@$moderationQueue->getLink()}#comment{@$commentID}">{$moderationQueue->getTitle()}</a>.]]></item>
-               <item name="wcf.moderation.report.notification.comment.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$others} other users{/if} wrote comments on the report <a href="{@$moderationQueue->getLink()}">{$moderationQueue->getTitle()}</a>.]]></item>
-               <item name="wcf.moderation.report.notification.comment.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a comment{else}comments{/if} on the report {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.moderation.report.notification.comment.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a comment{else}comments{/if} on the report <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a>:</p>]]></item>
+               <item name="wcf.moderation.report.notification.comment.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} commented on the report <strong>{$moderationQueue}</strong>.]]></item>
+               <item name="wcf.moderation.report.notification.comment.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} commented on the report <strong>{$moderationQueue}</strong>.]]></item>
+               <item name="wcf.moderation.report.notification.comment.mail.plaintext"><![CDATA[{@$authorList} commented on the report {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
+               <item name="wcf.moderation.report.notification.comment.mail.html"><![CDATA[<p>{@$authorList} commented on the report <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a>:</p>]]></item>
                <item name="wcf.moderation.report.notification.commentResponse.title"><![CDATA[New reply (Report)]]></item>
                <item name="wcf.moderation.report.notification.commentResponse.title.stacked"><![CDATA[{#$timesTriggered} new replies (Report)]]></item>
-               <item name="wcf.moderation.report.notification.commentResponse.message"><![CDATA[{if !$author->userID}A guest{else}{@$author->getAnchorTag()}{/if} wrote a reply to a comment by {if $commentAuthor->userID}<a href="{link controller='User' object=$commentAuthor}{/link}" class="userLink" data-user-id="{@$commentAuthor->userID}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if} on the report <a href="{@$moderationQueue->getLink()}#comment{@$commentID}/response{@$responseID}">{$moderationQueue->getTitle()}</a>.]]></item>
-               <item name="wcf.moderation.report.notification.commentResponse.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$others} other users{/if} wrote replies to comments on the report <a href="{@$moderationQueue->getLink()}#comment{@$commentID}">{$moderationQueue->getTitle()}</a>.]]></item>
-               <item name="wcf.moderation.report.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {@$notificationContent[variables][commentAuthor]->username}’s{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} comment on the report {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.moderation.report.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if}’s comment on the report <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a>:</p>]]></item>
+               <item name="wcf.moderation.report.notification.commentResponse.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} replied to a comment by <strong>{$commentAuthor->username}</strong> on the report <strong>{$moderationQueue}</strong>.]]></item>
+               <item name="wcf.moderation.report.notification.commentResponse.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} replied to comments on the report <strong>{$moderationQueue->getTitle()}</strong>.]]></item>
+               <item name="wcf.moderation.report.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} replied to {@$notificationContent[variables][commentAuthor]->username}’s{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} comment on the report {@$notificationContent[variables][moderationQueue]->getTitle()} [URL:{@$notificationContent[variables][moderationQueue]->getLink()}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
+               <item name="wcf.moderation.report.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} replied to {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if}’s comment on the report <a href="{$notificationContent[variables][moderationQueue]->getLink()}">{$notificationContent[variables][moderationQueue]->getTitle()}</a>:</p>]]></item>
                <item name="wcf.moderation.report.reason"><![CDATA[Reason]]></item>
                <item name="wcf.moderation.report.reason.description"><![CDATA[This function is reserved for: spam, advertisement and other questionable (racism, glorification of violence, offending, or sexist) content.]]></item>
                <item name="wcf.moderation.report.removeContent"><![CDATA[Delete Content]]></item>
@@ -4313,7 +4321,7 @@ Attachments:
                <item name="wcf.paidSubscription.confirmTOS"><![CDATA[I agree to the <a href="{PAID_SUBSCRIPTION_TOS_URL}">Terms of Service</a>]]></item>
                <item name="wcf.paidSubscription.button.moreInformation"><![CDATA[More Details]]></item>
                <item name="wcf.paidSubscription.expiringSubscription.notification.title"><![CDATA[Expiring Subscription]]></item>
-               <item name="wcf.paidSubscription.expiringSubscription.notification.message"><![CDATA[Your subscription “{$userNotificationObject->getTitle()}” will expire {dateInterval start=$notification->time end=$userNotificationObject->endDate format='sentence'} (on {$userNotificationObject->endDate|date:'F jS'}).]]></item>
+               <item name="wcf.paidSubscription.expiringSubscription.notification.message"><![CDATA[Your subscription <strong>{$userNotificationObject->getTitle()}</strong> will expire {dateInterval start=$notification->time end=$userNotificationObject->endDate format='sentence'} (on {$userNotificationObject->endDate|date:'F jS'}).]]></item>
                <item name="wcf.paidSubscription.expiringSubscription.notification.mail.plaintext"><![CDATA[Your subscription “{@$subscription->getTitle()}” will expire {dateInterval start=$notification->time end=$subscription->endDate format='sentence'} (on {@$subscription->endDate|date:'F jS'}).]]></item>
                <item name="wcf.paidSubscription.expiringSubscription.notification.mail.html"><![CDATA[Your subscription “{$subscription->getTitle()}” will expire <b>{dateInterval start=$notification->time end=$subscription->endDate format='sentence'}</b> (on {$subscription->endDate|date:'F jS'}).]]></item>
        </category>
@@ -4353,6 +4361,7 @@ Attachments:
                <item name="wcf.reactions.summary.noReactions"><![CDATA[There are no reactions at the moment.]]></item>
                <item name="wcf.reactions.summary.listReactions"><![CDATA[List Reactions]]></item>
                <item name="wcf.reactions.react"><![CDATA[React]]></item>
+               <item name="wcf.reactions.reactionTypeCount"><![CDATA[{@$reaction->renderIcon()}×{#$count}]]></item>
        </category>
        <category name="wcf.reactionType">
                <item name="wcf.reactionType.title1"><![CDATA[Like]]></item>
@@ -4764,7 +4773,7 @@ Open the link below to access the user profile:
                <item name="wcf.user.trophy.dialogTitle"><![CDATA[Trophies of {$username}]]></item>
                <item name="wcf.user.trophy.trophies"><![CDATA[Trophies]]></item>
                <item name="wcf.user.trophy.specialTrophies"><![CDATA[Special Trophies]]></item>
-               <item name="wcf.user.trophy.specialTrophies.description"><![CDATA[Choose the special trophies that you want to be shown in your profile and in the message sidebar.]]></item>
+               <item name="wcf.user.trophy.specialTrophies.description"><![CDATA[Choose the special {if $__wcf->getSession()->getPermission('user.profile.trophy.maxUserSpecialTrophies') > 1}trophies{else}trophy{/if} that you want to be shown in your profile and in the message sidebar.{if $__wcf->getSession()->getPermission('user.profile.trophy.maxUserSpecialTrophies') > 1} You can select a maximum of {#$__wcf->getSession()->getPermission('user.profile.trophy.maxUserSpecialTrophies')} trophies.{/if}]]></item>
                <item name="wcf.user.trophy.specialTrophies.error.tooMany"><![CDATA[You can choose a maximum of {#$__wcf->session->getPermission('user.profile.trophy.maxUserSpecialTrophies')} trophies.]]></item>
                <item name="wcf.user.trophy.specialTrophies.error.invalid"><![CDATA[The selected trophies are invalid.]]></item>
                <item name="wcf.user.trophy.recentActivity.received"><![CDATA[Has received the trophy <a href="{$userTrophy->getTrophy()->getLink()}">{$userTrophy->getTrophy()->getTitle()}</a>.]]></item>
@@ -4945,6 +4954,12 @@ Open the link below to access the user profile:
                <item name="wcf.user.condition.notUserTrophyIDs.description"><![CDATA[User has not received the selected trophies.]]></item>
                <item name="wcf.user.condition.notUserTrophyIDs.error.userTrophyIntersection"><![CDATA[The selected trophies in “User has Trophy” and “User does not have Trophy” are conflicting.]]></item>
                <item name="wcf.user.condition.trophyPoints"><![CDATA[Trophies]]></item>
+               <item name="wcf.user.condition.coverPhoto"><![CDATA[Cover Photo]]></item>
+               <item name="wcf.user.condition.coverPhoto.coverPhoto"><![CDATA[Has Cover Photo]]></item>
+               <item name="wcf.user.condition.coverPhoto.noCoverPhoto"><![CDATA[Has no Cover Photo]]></item>
+               <item name="wcf.user.condition.signature"><![CDATA[Signature]]></item>
+               <item name="wcf.user.condition.signature.signature"><![CDATA[Has Signature]]></item>
+               <item name="wcf.user.condition.signature.noSignature"><![CDATA[Has no Signature]]></item>
        </category>
        <category name="wcf.user.coverPhoto">
                <item name="wcf.user.coverPhoto"><![CDATA[Cover Photo]]></item>
@@ -5027,41 +5042,42 @@ your notifications on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|langu
 <p>You can <a href="{link controller='NotificationDisable' isHtmlEmail=true}userID={@$mailbox->getUser()->userID}&token={@$mailbox->getUser()->notificationMailToken}{/link}">disable all email notifications</a> as well.</p>]]></item>
                <item name="wcf.user.notification.mail.authorList.plaintext"><![CDATA[{if !$event->getAuthor()->userID}{if $guestTimesTriggered > 1}Guests{else}A guest{/if}{else}{@$event->getAuthor()->username} [URL:{link controller='User' object=$event->getAuthor() isEmail=true}{/link}]{/if}{if $count > 1 && $count < 4}{if $count == 2 && !$guestTimesTriggered} and {else}, {/if}{@$authors[1]->username} [URL:{link controller='User' object=$authors[1] isEmail=true}{/link}]{if $count == 3}{if !$guestTimesTriggered} and {else}, {/if}{@$authors[2]->username} [URL:{link controller='User' object=$authors[2] isEmail=true}{/link}]{/if}{elseif $count >= 4}{if $guestTimesTriggered},{else} and{/if} {#$count-1} other users{/if}{if $event->getAuthor()->userID && $guestTimesTriggered} and {if $guestTimesTriggered == 1}a guest{else}{#$guestTimesTriggered} guests{/if}{/if}]]></item>
                <item name="wcf.user.notification.mail.authorList.html"><![CDATA[{if !$event->getAuthor()->userID}{if $guestTimesTriggered > 1}Guests{else}A guest{/if}{else}<a href="{link controller='User' object=$event->getAuthor() isHtmlEmail=true}{/link}">{$event->getAuthor()->username}</a>{/if}{if $count > 1 && $count < 4}{if $count == 2 && !$guestTimesTriggered} and {else}, {/if}<a href="{link controller='User' object=$authors[1] isHtmlEmail=true}{/link}">{$authors[1]->username}</a>{if $count == 3}{if !$guestTimesTriggered} and {else}, {/if}<a href="{link controller='User' object=$authors[2] isHtmlEmail=true}{/link}">{$authors[2]->username}</a>{/if}{elseif $count >= 4}{if $guestTimesTriggered},{else} and{/if} {#$count-1} other users{/if}{if $event->getAuthor()->userID && $guestTimesTriggered} and {if $guestTimesTriggered == 1}a guest{else}{#$guestTimesTriggered} guests{/if}{/if}]]></item>
+               <item name="wcf.user.notification.stacked.authorList"><![CDATA[{if !$guestTimesTriggered|isset}{assign var=guestTimesTriggered value=0}{/if}{if $count < 4}<strong>{$authors[0]}</strong>{if $count != 1}{if $count == 2 && !$guestTimesTriggered} and {else}, {/if}<strong>{$authors[1]}</strong>{if $count == 3}{if !$guestTimesTriggered} and {else}, {/if} <strong>{$authors[2]}</strong>{/if}{/if}{if $guestTimesTriggered} and {plural value=$guestTimesTriggered one='a guest' other='# guests'}{/if}{else}<strong>{$authors[0]}</strong>{if $guestTimesTriggered},{else} and{/if} {#$others} other users {if $guestTimesTriggered}and {plural value=$guestTimesTriggered one='a guest' other='# guests'}{/if}{/if}]]></item>
                <!-- Notifications -->
                <item name="wcf.user.notification.com.woltlab.wcf.user"><![CDATA[User Profiles]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.follow.following"><![CDATA[Notify me of new followers]]></item>
                <item name="wcf.user.notification.follow.title"><![CDATA[New Follower]]></item>
                <item name="wcf.user.notification.follow.title.stacked"><![CDATA[{#$count} new followers]]></item>
-               <item name="wcf.user.notification.follow.message"><![CDATA[{@$author->getAnchorTag()} follows you.]]></item>
-               <item name="wcf.user.notification.follow.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$others} other users{/if} follow you.]]></item>
+               <item name="wcf.user.notification.follow.message"><![CDATA[<strong>{$author}</strong> follows you.]]></item>
+               <item name="wcf.user.notification.follow.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} follow you.]]></item>
                <item name="wcf.user.notification.follow.mail.plaintext"><![CDATA[{@$authorList} {if $authors|count == 1}follows{else}follow{/if} you.]]></item>
                <item name="wcf.user.notification.follow.mail.html"><![CDATA[<p>{@$authorList} {if $authors|count == 1}follows{else}follow{/if} you:</p>]]></item>
                <item name="wcf.user.notification.comment.title"><![CDATA[New Comment (Wall)]]></item>
                <item name="wcf.user.notification.comment.title.stacked"><![CDATA[{#$timesTriggered} new comments (Wall)]]></item>
-               <item name="wcf.user.notification.comment.message"><![CDATA[{if !$author->userID}A guest{else}{@$author->getAnchorTag()}{/if} wrote a comment on <a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}{/link}">your wall</a>.]]></item>
-               <item name="wcf.user.notification.comment.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} and {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} and{/if} {#$others} other users {if $guestTimesTriggered}and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{/if} wrote comments on <a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}{/link}">your wall</a>.]]></item>
-               <item name="wcf.user.notification.comment.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a comment{else}comments{/if} on your wall [URL:{link controller='User' object=$notificationContent[variables][owner] isEmail=true}#wall/comment{@$commentID}{/link}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.user.notification.comment.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a comment{else}comments{/if} on <a href="{link controller='User' object=$notificationContent[variables][owner] isHtmlEmail=true}#wall/comment{@$commentID}{/link}">your wall</a>:</p>]]></item>
+               <item name="wcf.user.notification.comment.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} commented on your wall.]]></item>
+               <item name="wcf.user.notification.comment.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} commented on your wall.]]></item>
+               <item name="wcf.user.notification.comment.mail.plaintext"><![CDATA[{@$authorList} commented on your wall [URL:{link controller='User' object=$notificationContent[variables][owner] isEmail=true}#wall/comment{@$commentID}{/link}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
+               <item name="wcf.user.notification.comment.mail.html"><![CDATA[<p>{@$authorList} commented on <a href="{link controller='User' object=$notificationContent[variables][owner] isHtmlEmail=true}#wall/comment{@$commentID}{/link}">your wall</a>:</p>]]></item>
                <item name="wcf.user.notification.comment.like.title"><![CDATA[Reaction to a comment (Wall)]]></item>
                <item name="wcf.user.notification.comment.like.title.stacked"><![CDATA[{#$count} users reacted to your comment (Wall)]]></item>
-               <item name="wcf.user.notification.comment.like.message"><![CDATA[{@$author->getAnchorTag()} reacted to your comment on {if $owner === null}<a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}{/link}">your wall</a>{else}<a href="{link controller='User' object=$owner}#wall/comment{@$commentID}{/link}">{$owner->username}’s wall</a>{/if} ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}×{#$count}{/implode}).]]></item>
-               <item name="wcf.user.notification.comment.like.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$others} other users{/if} reacted to your comment on {if $owner === null}<a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}{/link}">your wall</a>{else}<a href="{link controller='User' object=$owner}#wall/comment{@$commentID}{/link}">{$owner->username}’s wall</a>{/if} ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}×{#$count}{/implode}).]]></item>
+               <item name="wcf.user.notification.comment.like.message"><![CDATA[<strong>{$author}</strong> reacted to your comment on {if $owner === null}your wall{else}<strong>{$owner}</strong>’s</a>{/if} ({@$__wcf->getReactionHandler()->renderInlineList($reactions)}).]]></item>
+               <item name="wcf.user.notification.comment.like.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} reacted to your comment on {if $owner === null}your wall{else}<strong>{$owner}</strong>’s wall{/if} ({@$__wcf->getReactionHandler()->renderInlineList($reactions)}).]]></item>
                <item name="wcf.user.notification.commentResponse.title"><![CDATA[New Reply (Wall)]]></item>
                <item name="wcf.user.notification.commentResponse.title.stacked"><![CDATA[{#$timesTriggered} new replies (Wall)]]></item>
-               <item name="wcf.user.notification.commentResponse.message"><![CDATA[{if !$author->userID}A guest{else}{@$author->getAnchorTag()}{/if} wrote a reply to your comment on <a href="{link controller='User' object=$owner}#wall/comment{@$commentID}/response{@$responseID}{/link}">{if $owner->userID == $__wcf->getUser()->userID}your{else}{$owner->username}’s{/if} wall</a>.]]></item>
-               <item name="wcf.user.notification.commentResponse.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} and {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} and{/if} {#$others} other users {if $guestTimesTriggered}and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{/if} replied to your comment on <a href="{link controller='User' object=$owner}#wall/comment{@$commentID}{/link}">{if $owner->userID == $__wcf->getUser()->userID}your{else}{$owner->username}’s{/if} wall</a>.]]></item>
-               <item name="wcf.user.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to your comment on {if $mailbox->getUser()->userID == $notificationContent[variables][owner]->userID}your{else}{@$notificationContent[variables][owner]->username}’s{/if} wall [URL:{link controller='User' object=$notificationContent[variables][owner] isEmail=true}#wall/comment{@$commentID}/response{@$responseID}{/link}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.user.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to your comment on <a href="{link controller='User' object=$notificationContent[variables][owner] isHtmlEmail=true}#wall/comment{@$commentID}/response{@$responseID}{/link}">{if $mailbox->getUser()->userID == $notificationContent[variables][owner]->userID}your{else}{$notificationContent[variables][owner]->username}’s{/if} wall</a>:</p>]]></item>
+               <item name="wcf.user.notification.commentResponse.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} replied to your comment on {if $owner->userID == $__wcf->getUser()->userID}your{else}<strong>{$owner}</strong>’s{/if} wall.]]></item>
+               <item name="wcf.user.notification.commentResponse.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} replied to your comment on {if $owner->userID == $__wcf->getUser()->userID}your{else}<strong>{$owner}</strong>’s{/if} wall.]]></item>
+               <item name="wcf.user.notification.commentResponse.mail.plaintext"><![CDATA[{@$authorList} replied to your comment on {if $mailbox->getUser()->userID == $notificationContent[variables][owner]->userID}your{else}{@$notificationContent[variables][owner]->username}’s{/if} wall [URL:{link controller='User' object=$notificationContent[variables][owner] isEmail=true}#wall/comment{@$commentID}/response{@$responseID}{/link}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
+               <item name="wcf.user.notification.commentResponse.mail.html"><![CDATA[<p>{@$authorList} replied to your comment on <a href="{link controller='User' object=$notificationContent[variables][owner] isHtmlEmail=true}#wall/comment{@$commentID}/response{@$responseID}{/link}">{if $mailbox->getUser()->userID == $notificationContent[variables][owner]->userID}your{else}{$notificationContent[variables][owner]->username}’s{/if} wall</a>:</p>]]></item>
                <item name="wcf.user.notification.commentResponse.like.title"><![CDATA[Likes your reply to a comment (Wall)]]></item>
                <item name="wcf.user.notification.commentResponse.like.title.stacked"><![CDATA[{#$count} users reacted to your reply to a comment (Wall)]]></item>
-               <item name="wcf.user.notification.commentResponse.like.message"><![CDATA[{@$author->getAnchorTag()} reacted to your reply to a comment on {if $owner === null}<a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}/response{@$responseID}{/link}">your wall</a>{else}<a href="{link controller='User' object=$owner}#wall/comment{@$commentID}/response{@$responseID}{/link}">{$owner->username}’s wall</a>{/if} ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}×{#$count}{/implode}).]]></item>
-               <item name="wcf.user.notification.commentResponse.like.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$others} other users{/if} reacted to your reply to a comment on {if $owner === null}<a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}/response{@$responseID}{/link}">your wall</a>{else}<a href="{link controller='User' object=$owner}#wall/comment{@$commentID}/response{@$responseID}{/link}">{$owner->username}’s wall</a>{/if} ({implode from=$reactions key=reactionID item=count}{@$__wcf->getReactionHandler()->getReactionTypeByID($reactionID)->renderIcon()}×{#$count}{/implode}).]]></item>
+               <item name="wcf.user.notification.commentResponse.like.message"><![CDATA[<strong>{$author}</strong> reacted to your reply to a comment on {if $owner === null}your wall{else}<strong>{$owner}</strong>’s wall{/if} ({@$__wcf->getReactionHandler()->renderInlineList($reactions)}).]]></item>
+               <item name="wcf.user.notification.commentResponse.like.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} reacted to your reply to a comment on {if $owner === null}your wall{else}<strong>{$owner}</strong>’s wall{/if} ({@$__wcf->getReactionHandler()->renderInlineList($reactions)}).]]></item>
                <item name="wcf.user.notification.commentResponseOwner.title"><![CDATA[New Reply (Wall)]]></item>
                <item name="wcf.user.notification.commentResponseOwner.title.stacked"><![CDATA[{#$timesTriggered} new replies (Wall)]]></item>
-               <item name="wcf.user.notification.commentResponseOwner.message"><![CDATA[{if !$author->userID}A guest{else}{@$author->getAnchorTag()}{/if} wrote a reply to {$commentAuthor->username}’s comment on <a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}/response{@$responseID}{/link}">your wall</a>.]]></item>
-               <item name="wcf.user.notification.commentResponseOwner.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} and {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} and{/if} {#$others} other users {if $guestTimesTriggered}and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{/if} replied to the comment by {if $author->userID}<a href="{link controller='User' object=$author}{/link}" class="userLink" data-user-id="{@$author->userID}">{$author->username}</a>{else}{$author->username}{/if} on <a href="{link controller='User' object=$__wcf->getUser()}#wall/comment{@$commentID}{/link}">your wall</a>.]]></item>
-               <item name="wcf.user.notification.commentResponseOwner.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {@$notificationContent[variables][commentAuthor]->username}’s{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} comment on your wall [URL:{link controller='User' object=$notificationContent[variables][owner] isEmail=true}#wall/comment{@$commentID}/response{@$responseID}{/link}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.user.notification.commentResponseOwner.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if}’s comment on <a href="{link controller='User' object=$notificationContent[variables][owner] isHtmlEmail=true}#wall/comment{@$commentID}/response{@$responseID}{/link}">your wall</a>:</p>]]></item>
+               <item name="wcf.user.notification.commentResponseOwner.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} replied to <strong>{$commentAuthor}</strong>’s comment on your wall.]]></item>
+               <item name="wcf.user.notification.commentResponseOwner.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} replied to the comment by <strong>{$author}</strong> on your wall.]]></item>
+               <item name="wcf.user.notification.commentResponseOwner.mail.plaintext"><![CDATA[{@$authorList} replied to {@$notificationContent[variables][commentAuthor]->username}’s{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} comment on your wall [URL:{link controller='User' object=$notificationContent[variables][owner] isEmail=true}#wall/comment{@$commentID}/response{@$responseID}{/link}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
+               <item name="wcf.user.notification.commentResponseOwner.mail.html"><![CDATA[<p>{@$authorList} replied to {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if}’s comment on <a href="{link controller='User' object=$notificationContent[variables][owner] isHtmlEmail=true}#wall/comment{@$commentID}/response{@$responseID}{/link}">your wall</a>:</p>]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.notification.comment"><![CDATA[Notify me of new comments on my wall]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.response.notification.commentResponse"><![CDATA[Notify me of new replies to my comments]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.profileComment.response.notification.commentResponseOwner"><![CDATA[Notify me of new replies to comments on my wall]]></item>
@@ -5070,7 +5086,7 @@ your notifications on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|langu
                <item name="wcf.user.notification.com.woltlab.wcf.paidSubscription.user.expiring"><![CDATA[Notify me before a subscription will expire]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.userTrophy.notification.received"><![CDATA[Notify me when I receive a trophy]]></item>
                <item name="wcf.user.notification.trophy.received.title"><![CDATA[Trophy received]]></item>
-               <item name="wcf.user.notification.trophy.received.message"><![CDATA[You received the trophy <a href="{$userTrophy->getTrophy()->getLink()}">{$userTrophy->getTrophy()->getTitle()}</a>.]]></item>
+               <item name="wcf.user.notification.trophy.received.message"><![CDATA[You received the trophy <strong>{$userTrophy->getTrophy()}</strong>.]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.administration"><![CDATA[Administration]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.user.registration.notification.registration"><![CDATA[Notify me of new user registrations]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.moderation"><![CDATA[Moderation]]></item>
@@ -5080,48 +5096,49 @@ your notifications on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|langu
                <item name="wcf.user.notification.com.woltlab.wcf.page.notification.comment"><![CDATA[Notify me when new comments are written on pages]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.page.response.notification.commentResponse"><![CDATA[Notify me when new replies to comments are written on pages]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.page.response.notification.commentResponseOwner"><![CDATA[Notify me when someone replies to my comments on pages]]></item>
+               <item name="wcf.user.notification.com.woltlab.wcf.likeableArticle.notification.like"><![CDATA[Notify me when someone reacted to my articles]]></item>
                <item name="wcf.user.notification.pageComment.title"><![CDATA[New Comment (Page)]]></item>
                <item name="wcf.user.notification.pageComment.title.stacked"><![CDATA[{#$timesTriggered} new comments (Page)]]></item>
-               <item name="wcf.user.notification.pageComment.message"><![CDATA[{if !$author->userID}A guest{else}{@$author->getAnchorTag()}{/if} wrote a comment on the page <a href="{$page->getLink()}#comment{@$commentID}">{$page->getTitle()}</a>.]]></item>
-               <item name="wcf.user.notification.pageComment.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} and {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} and{/if} {#$others} other users {if $guestTimesTriggered}and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{/if} wrote comments on the page <a href="{$page->getLink()}#comment{@$commentID}">{$page->getTitle()}</a>.]]></item>
-               <item name="wcf.user.notification.pageComment.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a comment{else}comments{/if} on the page “{@$notificationContent[variables][page]->getTitle()}” [URL:{@$notificationContent[variables][page]->getLink()}#comment{@$commentID}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.user.notification.pageComment.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a comment{else}comments{/if} on the page <a href="{$notificationContent[variables][page]->getLink()}#comment{@$commentID}">{$notificationContent[variables][page]->getTitle()}</a>:</p>]]></item>
+               <item name="wcf.user.notification.pageComment.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} commented on the page <strong>{$page->getTitle()}</strong>.]]></item>
+               <item name="wcf.user.notification.pageComment.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} commented on the page <strong>{$page->getTitle()}</strong>.]]></item>
+               <item name="wcf.user.notification.pageComment.mail.plaintext"><![CDATA[{@$authorList} commented on the page “{@$notificationContent[variables][page]->getTitle()}” [URL:{@$notificationContent[variables][page]->getLink()}#comment{@$commentID}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
+               <item name="wcf.user.notification.pageComment.mail.html"><![CDATA[<p>{@$authorList} commented on the page <a href="{$notificationContent[variables][page]->getLink()}#comment{@$commentID}">{$notificationContent[variables][page]->getTitle()}</a>:</p>]]></item>
                <item name="wcf.user.notification.pageComment.response.title"><![CDATA[New Reply (Page)]]></item>
                <item name="wcf.user.notification.pageComment.response.title.stacked"><![CDATA[{#$timesTriggered} new replies (Page)]]></item>
-               <item name="wcf.user.notification.pageComment.response.message"><![CDATA[{if !$author->userID}A guest{else}{@$author->getAnchorTag()}{/if} wrote a reply to {if $commentAuthor->userID}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if}’s comment on the page <a href="{$page->getLink()}#comment{@$commentID}/response{@$responseID}">{$page->getTitle()}</a>.]]></item>
-               <item name="wcf.user.notification.pageComment.response.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} and {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} and{/if} {#$others} other users {if $guestTimesTriggered}and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{/if} replied to {if $commentAuthor->userID}<a href="{link controller='User' object=$commentAuthor}{/link}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if}’s comment on the page <a href="{$page->getLink()}#comment{@$commentID}">{$page->getTitle()}</a>.]]></item>
-               <item name="wcf.user.notification.pageComment.response.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {@$notificationContent[variables][commentAuthor]->username}’s comment on the page “{@$notificationContent[variables][page]->getTitle()}” [URL:{@$notificationContent[variables][page]->getLink()}#comment{@$commentID}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.user.notification.pageComment.response.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {@$notificationContent[variables][commentAuthor]->username}’s comment on the page <a href="{$notificationContent[variables][page]->getLink()}#comment{@$commentID}">{$notificationContent[variables][page]->getTitle()}</a>:</p>]]></item>
+               <item name="wcf.user.notification.pageComment.response.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} replied to <strong>{$commentAuthor}</strong>’s comment on the page {$page->getTitle()}.]]></item>
+               <item name="wcf.user.notification.pageComment.response.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} replied to <strong>{$commentAuthor}</strong>’s comment on the page {$page->getTitle()}.]]></item>
+               <item name="wcf.user.notification.pageComment.response.mail.plaintext"><![CDATA[{@$authorList} replied to {@$notificationContent[variables][commentAuthor]->username}’s comment on the page “{@$notificationContent[variables][page]->getTitle()}” [URL:{@$notificationContent[variables][page]->getLink()}#comment{@$commentID}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
+               <item name="wcf.user.notification.pageComment.response.mail.html"><![CDATA[<p>{@$authorList} replied to {@$notificationContent[variables][commentAuthor]->username}’s comment on the page <a href="{$notificationContent[variables][page]->getLink()}#comment{@$commentID}">{$notificationContent[variables][page]->getTitle()}</a>:</p>]]></item>
                <item name="wcf.user.notification.pageComment.response.title"><![CDATA[New Reply (Page)]]></item>
                <item name="wcf.user.notification.pageComment.responseOwner.title.stacked"><![CDATA[{#$timesTriggered} new replies (Page)]]></item>
-               <item name="wcf.user.notification.pageComment.responseOwner.message"><![CDATA[{if !$author->userID}A guest{else}{@$author->getAnchorTag()}{/if} wrote a reply to your comment on the page <a href="{$page->getLink()}#comment{@$commentID}/response{@$responseID}">{$page->getTitle()}</a>.]]></item>
-               <item name="wcf.user.notification.pageComment.responseOwner.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} and {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} and{/if} {#$others} other users {if $guestTimesTriggered}and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{/if} replied to your comment on the page <a href="{$page->getLink()}#comment{@$commentID}">{$page->getTitle()}</a>.]]></item>
-               <item name="wcf.user.notification.pageComment.responseOwner.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to your comment on the page “{@$notificationContent[variables][page]->getTitle()}” [URL:{@$notificationContent[variables][page]->getLink()}#comment{@$commentID}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.user.notification.pageComment.responseOwner.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to your comment on the page <a href="{$notificationContent[variables][page]->getLink()}#comment{@$commentID}">{$notificationContent[variables][page]->getTitle()}</a>:</p>]]></item>
+               <item name="wcf.user.notification.pageComment.responseOwner.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} replied to your comment on the page <strong>{$page->getTitle()}</strong>.]]></item>
+               <item name="wcf.user.notification.pageComment.responseOwner.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} replied to your comment on the page <strong>{$page->getTitle()}</strong>.]]></item>
+               <item name="wcf.user.notification.pageComment.responseOwner.mail.plaintext"><![CDATA[{@$authorList} replied to your comment on the page “{@$notificationContent[variables][page]->getTitle()}” [URL:{@$notificationContent[variables][page]->getLink()}#comment{@$commentID}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
+               <item name="wcf.user.notification.pageComment.responseOwner.mail.html"><![CDATA[<p>{@$authorList} replied to your comment on the page <a href="{$notificationContent[variables][page]->getLink()}#comment{@$commentID}">{$notificationContent[variables][page]->getTitle()}</a>:</p>]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.article.comment.notification.comment"><![CDATA[Notify me of new comments on my articles]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.article.comment.response.notification.commentResponse"><![CDATA[Notify me of new replies to my comments]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.article.comment.response.notification.commentResponseOwner"><![CDATA[Notify me of new replies to comments on my articles]]></item>
                <item name="wcf.user.notification.articleComment.title"><![CDATA[New Comment (Article)]]></item>
                <item name="wcf.user.notification.articleComment.title.stacked"><![CDATA[{#$timesTriggered} new Comments (Article)]]></item>
-               <item name="wcf.user.notification.articleComment.message"><![CDATA[{if $author->userID}{@$author->getAnchorTag()}{else}A guest{/if} commented on your article <a href="{link controller='Article' object=$article}#comments/comment{@$commentID}{/link}">{$article->getTitle()}</a>.]]></item>
-               <item name="wcf.user.notification.articleComment.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} and {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} and{/if} {#$others} other users {if $guestTimesTriggered}and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{/if} commented on your article <a href="{link controller='Article' object=$article}{/link}">{$article->getTitle()}</a>.]]></item>
+               <item name="wcf.user.notification.articleComment.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} commented on your article <strong>{$article->getTitle()}</strong>.]]></item>
+               <item name="wcf.user.notification.articleComment.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} commented on your article <strong>{$article->getTitle()}</strong>.]]></item>
                <item name="wcf.user.notification.articleComment.mail.plaintext"><![CDATA[{@$authorList} commented on your article “{@$article->getTitle()}” [URL:{link controller='Article' object=$article isEmail=true}#comment{@$commentID}{/link}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
                <item name="wcf.user.notification.articleComment.mail.html"><![CDATA[<p>{@$authorList} commented on your article <a href="{link controller='Article' object=$article isHtmlEmail=true}#comments/comment{@$commentID}{/link}">{$article->getTitle()}</a>:</p>]]></item>
                <item name="wcf.user.notification.articleComment.response.title"><![CDATA[New Reply (Article)]]></item>
                <item name="wcf.user.notification.articleComment.response.title.stacked"><![CDATA[{#$timesTriggered} new Replies (Article)]]></item>
-               <item name="wcf.user.notification.articleComment.response.message"><![CDATA[{if $author->userID}{@$author->getAnchorTag()}{else}A guest{/if} replied to your comment on the article <a href="{link controller='Article' object=$article}#comments/comment{@$commentID}/response{@$responseID}{/link}">{$article->getTitle()}</a>.]]></item>
-               <item name="wcf.user.notification.articleComment.response.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} and {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} and{/if} {#$others} other users {if $guestTimesTriggered}and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{/if} replied to your comment on the article <a href="{link controller='Article' object=$article}#comments/comment{@$commentID}{/link}">{$article->getTitle()}</a>.]]></item>
-               <item name="wcf.user.notification.articleComment.response.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to your comment on the article “{@$article->getTitle()}” [URL:{link controller='Article' object=$article isEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.user.notification.articleComment.response.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to your comment on the article <a href="{link controller='Article' object=$article isHtmlEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}">{$article->getTitle()}</a>:</p>]]></item>
+               <item name="wcf.user.notification.articleComment.response.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} replied to your comment on the article <strong>{$article->getTitle()}</strong>.]]></item>
+               <item name="wcf.user.notification.articleComment.response.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} replied to your comment on the article <strong>{$article->getTitle()}</strong>.]]></item>
+               <item name="wcf.user.notification.articleComment.response.mail.plaintext"><![CDATA[{@$authorList} replied to your comment on the article “{@$article->getTitle()}” [URL:{link controller='Article' object=$article isEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
+               <item name="wcf.user.notification.articleComment.response.mail.html"><![CDATA[<p>{@$authorList} replied to your comment on the article <a href="{link controller='Article' object=$article isHtmlEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}">{$article->getTitle()}</a>:</p>]]></item>
                <item name="wcf.user.notification.articleComment.responseOwner.title"><![CDATA[New Reply (Article)]]></item>
                <item name="wcf.user.notification.articleComment.responseOwner.title.stacked"><![CDATA[{#$timesTriggered} new Replies (Article)]]></item>
-               <item name="wcf.user.notification.articleComment.responseOwner.message"><![CDATA[{if $author->userID}{@$author->getAnchorTag()}{else}A guest{/if} replied to a comment by {$commentAuthor->username} on your article <a href="{link controller='Article' object=$article}#comments/comment{@$commentID}/response{@$responseID}{/link}">{$article->getTitle()}</a>.]]></item>
-               <item name="wcf.user.notification.articleComment.responseOwner.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} and {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} and{/if} {#$others} other users {if $guestTimesTriggered}and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{/if} replied to a comment by {if $commentAuthor->userID}<a href="{link controller='User' object=$commentAuthor}{/link}" class="userLink" data-user-id="{@$commentAuthor->userID}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if} on your article <a href="{link controller='Article' object=$article}#comments/comment{@$commentID}{/link}">{$article->getTitle()}</a>.]]></item>
-               <item name="wcf.user.notification.articleComment.responseOwner.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {@$notificationContent[variables][commentAuthor]->username}’s{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} comment on your article “{@$article->getTitle()}” [URL:{link controller='Article' object=$article isEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
-               <item name="wcf.user.notification.articleComment.responseOwner.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if}’s comment on your article <a href="{link controller='Article' object=$article isHtmlEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}">{$article->getTitle()}</a>:</p>]]></item>
+               <item name="wcf.user.notification.articleComment.responseOwner.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} replied to a comment by <strong>{$commentAuthor}</strong> on your article <strong>{$article->getTitle()}</strong>.]]></item>
+               <item name="wcf.user.notification.articleComment.responseOwner.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} replied to a comment by <strong>{$commentAuthor}</strong> on your article <strong>{$article->getTitle()}</strong>.]]></item>
+               <item name="wcf.user.notification.articleComment.responseOwner.mail.plaintext"><![CDATA[{@$authorList} replied to {@$notificationContent[variables][commentAuthor]->username}’s{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} comment on your article “{@$article->getTitle()}” [URL:{link controller='Article' object=$article isEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]></item>
+               <item name="wcf.user.notification.articleComment.responseOwner.mail.html"><![CDATA[<p>{@$authorList} replied to {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isHtmlEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if}’s comment on your article <a href="{link controller='Article' object=$article isHtmlEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}">{$article->getTitle()}</a>:</p>]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.article.notification.article"><![CDATA[Notify me of new articles in watched categories]]></item>
                <item name="wcf.user.notification.article.title"><![CDATA[New Article]]></item>
-               <item name="wcf.user.notification.article.message"><![CDATA[{if $author->userID}{@$author->getAnchorTag()}{else}A guest{/if} wrote the article <a href="{$article->getLink()}">{$article->getTitle()}</a>.]]></item>
+               <item name="wcf.user.notification.article.message"><![CDATA[{if $author->userID}<strong>{$author}</strong>{else}A guest{/if} wrote the article <strong>{$article->getTitle()}</strong>.]]></item>
                <item name="wcf.user.notification.article.mail.plaintext"><![CDATA[{if $event->getAuthor()->userID}{@$event->getAuthor()->username} [URL:{link controller='User' object=$event->getAuthor() isEmail=true}{/link}]{else}A guest{/if} wrote the article “{@$notificationContent[variables][articleContent]->getTitle()}” [URL:{link controller='Article' object=$notificationContent[variables][articleContent] isEmail=true}{/link}]:]]></item>
                <item name="wcf.user.notification.article.mail.html"><![CDATA[<p>{if $event->getAuthor()->userID}<a href="{link controller='User' object=$event->getAuthor() isHtmlEmail=true}{/link}">{$event->getAuthor()->username}</a>{else}A guest{/if} wrote the article <a href="{link controller='Article' object=$notificationContent[variables][articleContent] isHtmlEmail=true}{/link}">{$notificationContent[variables][articleContent]->getTitle()}</a>:</p>]]></item>
                <item name="wcf.user.notification.com.woltlab.wcf.articleComment.response.notification.commentResponseOwner"><![CDATA[Notify me of new replies to comments on my articles]]></item>
@@ -5130,8 +5147,8 @@ your notifications on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|langu
                <item name="wcf.user.notification.pageComment.responseOwner.title"><![CDATA[New reply (Page)]]></item>
                <item name="wcf.user.notification.userRegistration.title"><![CDATA[New user registration]]></item>
                <item name="wcf.user.notification.userRegistration.title.stacked"><![CDATA[{#$count} new user registrations]]></item>
-               <item name="wcf.user.notification.userRegistration.message"><![CDATA[{@$author->getAnchorTag()} has registered.]]></item>
-               <item name="wcf.user.notification.userRegistration.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->getAnchorTag()}{if $count == 2} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3} and {@$authors[2]->getAnchorTag()}{/if}{else}{@$authors[0]->getAnchorTag()} and {#$others} other users{/if} have registered.]]></item>
+               <item name="wcf.user.notification.userRegistration.message"><![CDATA[<strong>{$author}</strong> has registered.]]></item>
+               <item name="wcf.user.notification.userRegistration.message.stacked"><![CDATA[{@'wcf.user.notification.stacked.authorList'|language} have registered.]]></item>
                <item name="wcf.user.notification.userRegistration.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1}has{else}have{/if} registered.]]></item>
                <item name="wcf.user.notification.userRegistration.mail.html"><![CDATA[<p>{@$authorList} {if $count == 1}has{else}have{/if} registered:</p>]]></item>
        </category>
@@ -5172,7 +5189,6 @@ your notifications on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|langu
                <item name="wcf.user.option.birthday"><![CDATA[Birthday]]></item>
                <item name="wcf.user.option.birthdayShowYear"><![CDATA[Display year of birth]]></item>
                <item name="wcf.user.option.birthdayShowYear.description"><![CDATA[Allows users to view your age.]]></item>
-               <item name="wcf.user.option.canMail"><![CDATA[Can Send Me Emails]]></item>
                <item name="wcf.user.option.canViewEmailAddress"><![CDATA[Can View My Email Address]]></item>
                <item name="wcf.user.option.canViewOnlineStatus"><![CDATA[Can View My Online Status]]></item>
                <item name="wcf.user.option.canViewProfile"><![CDATA[Can View My Profile]]></item>
@@ -5196,7 +5212,6 @@ your notifications on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|langu
                <item name="wcf.user.option.occupation"><![CDATA[Occupation]]></item>
                <item name="wcf.user.option.showSignature"><![CDATA[Display members’ signatures]]></item>
                <item name="wcf.user.option.timezone"><![CDATA[Timezone]]></item>
-               <item name="wcf.user.option.icq"><![CDATA[ICQ]]></item>
                <item name="wcf.user.option.skype"><![CDATA[Skype]]></item>
                <item name="wcf.user.option.facebook"><![CDATA[Facebook]]></item>
                <item name="wcf.user.option.twitter"><![CDATA[Twitter]]></item>
@@ -5208,8 +5223,6 @@ your notifications on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|langu
                <item name="wcf.user.option.searchBooleanOption"><![CDATA[User’s selection for “{$option->getTitle()}”:]]></item>
        </category>
        <category name="wcf.user.mail">
-               <item name="wcf.user.mail.information"><![CDATA[Details]]></item>
-               <item name="wcf.user.mail.mail.subject"><![CDATA[Message From {@$username}: {@$subject}]]></item>
                <item name="wcf.user.mail.mail.plaintext"><![CDATA[Dear {@$mailbox->getUser()->username},
 
 “{@$username}” sent you a message on {@PAGE_TITLE|language} [URL:{link isEmail=true}{/link}]:
@@ -5220,11 +5233,6 @@ your notifications on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|langu
 <p>„{$username}“ sent you a message on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|language}</a>:</p>
 
 <p>{@$message|newlineToBreak}</p>]]></item>
-               <item name="wcf.user.mail.message"><![CDATA[Message]]></item>
-               <item name="wcf.user.mail.senderEmail"><![CDATA[Your email address]]></item>
-               <item name="wcf.user.mail.sent"><![CDATA[The message has been sent to {$user->username}.]]></item>
-               <item name="wcf.user.mail.showAddress"><![CDATA[Use my email address as sender address, the recipient can directly reply to me.]]></item>
-               <item name="wcf.user.mail.subject"><![CDATA[Subject]]></item>
        </category>
        <category name="wcf.user.rank">
                <item name="wcf.user.rank.administrator"><![CDATA[Administrator]]></item>